16
import * as ts from "typescript"
17
import { PeerGeneratorConfig } from "./peer-generation/PeerGeneratorConfig"
18
import { isRoot } from "./peer-generation/inheritance";
20
export class Language {
21
public static TS = new Language("TS", ".ts", true)
22
public static ARKTS = new Language("ArkTS", ".ets", true)
23
public static JAVA = new Language("Java", ".java", false)
24
public static CPP = new Language("C++", ".cc", false)
26
private constructor(private name: string, public extension: string, public needsUnionDiscrimination: boolean) {}
33
export interface NameWithType {
34
name?: ts.DeclarationName
39
export function isNodePublic(node: ts.Node): boolean {
40
return (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Public) !== 0
43
const keywords = new Map<string, string>(
45
["callback", "callback_"],
46
["object", "object_"],
47
["attribute", "attribute_"],
51
export function nameOrNullForIdl(name: ts.EntityName | ts.DeclarationName | undefined): string | undefined {
52
if (name == undefined) return undefined
54
if (ts.isIdentifier(name)) {
55
let rawName = ts.idText(name)
56
return keywords.get(rawName) ?? rawName
58
if (ts.isStringLiteral(name)) {
65
export function nameOrNull(name: ts.EntityName | ts.DeclarationName | undefined): string | undefined {
66
if (name == undefined) return undefined
67
if (ts.isIdentifier(name)) {
68
return ts.idText(name)
74
export function isNamedDeclaration(node: ts.Node): node is ts.NamedDeclaration {
75
return ("name" in node)
78
export function asString(node: ts.Node | undefined): string {
79
if (node === undefined) return "undefined node"
80
if (ts.isIdentifier(node)) return ts.idText(node)
81
if (ts.isQualifiedName(node)) return `${identName(node.left)}.${identName(node.right)}`
82
if (ts.isStringLiteral(node)) return node.text
83
if (ts.isTypeReferenceNode(node)) return `${ts.SyntaxKind[node.kind]}(${asString(node.typeName)})`
84
if (ts.isImportTypeNode(node)) return `${ts.SyntaxKind[node.kind]}(${asString(node.qualifier)})`
85
if (isNamedDeclaration(node)) {
86
if (node.name === undefined) {
87
return `${ts.SyntaxKind[node.kind]}(undefined name)`
89
return `${ts.SyntaxKind[node.kind]}(${asString(node.name)})`
92
return `${ts.SyntaxKind[node.kind]}`
97
export function arrayAt<T>(array: T[] | undefined, index: number): T | undefined {
98
return array ? array[index >= 0 ? index : array.length + index] : undefined
101
export function getComment(sourceFile: ts.SourceFile, node: ts.Node): string {
102
const commentRanges = ts.getLeadingCommentRanges(
103
sourceFile.getFullText(),
107
if (!commentRanges) return ""
110
.map(range => sourceFile.getFullText().slice(range.pos, range.end))
114
export function getSymbolByNode(typechecker: ts.TypeChecker, node: ts.Node): ts.Symbol | undefined {
115
return typechecker.getSymbolAtLocation(node)
118
export function getDeclarationsByNode(typechecker: ts.TypeChecker, node: ts.Node): ts.Declaration[] {
119
return getSymbolByNode(typechecker, node)?.getDeclarations() ?? []
122
export function findRealDeclarations(typechecker: ts.TypeChecker, node: ts.Node): ts.Declaration[] {
123
const declarations = getDeclarationsByNode(typechecker, node)
124
const first = declarations[0]
125
if (first && ts.isExportAssignment(first)) {
126
return findRealDeclarations(typechecker, first.expression)
132
export function getExportedDeclarationNameByDecl(declaration: ts.NamedDeclaration): string | undefined {
133
let declName = declaration.name ? ts.idText(declaration.name as ts.Identifier) : undefined
134
let current: ts.Node = declaration
135
while (current != undefined && !ts.isSourceFile(current)) {
136
current = current.parent
138
let source = current as ts.SourceFile
139
let exportedName = declName
140
source.forEachChild(it => {
141
if (ts.isExportDeclaration(it)) {
142
let clause = it.exportClause!
143
if (ts.isNamedExportBindings(clause) && ts.isNamedExports(clause)) {
144
clause.elements.forEach(it => {
145
let propName = it.propertyName ? ts.idText(it.propertyName) : undefined
146
let property = ts.idText(it.name)
147
if (propName == declName) {
148
exportedName = property
157
export function getExportedDeclarationNameByNode(typechecker: ts.TypeChecker, node: ts.Node): string | undefined {
158
let declarations = getDeclarationsByNode(typechecker, node)
159
if (declarations.length == 0) return undefined
160
return getExportedDeclarationNameByDecl(declarations[0])
163
export function isReadonly(modifierLikes: ts.NodeArray<ts.ModifierLike> | undefined): boolean {
164
return modifierLikes?.find(it => it.kind == ts.SyntaxKind.ReadonlyKeyword) != undefined
167
export function isStatic(modifierLikes: ts.NodeArray<ts.ModifierLike> | undefined): boolean {
168
return modifierLikes?.find(it => it.kind == ts.SyntaxKind.StaticKeyword) != undefined
171
export function getLineNumberString(sourceFile: ts.SourceFile, position: number): string {
172
let pos = ts.getLineAndCharacterOfPosition(sourceFile, position)
173
return `${pos.line + 1}:${pos.character}`
176
export function isDefined<T>(value: T | null | undefined): value is T {
180
export function capitalize(string: string): string {
181
return string.charAt(0).toUpperCase() + string.slice(1)
184
export function dropLast(text: string, chars: number): string {
185
return text.substring(0, text.length - chars)
188
export function dropSuffix(text: string, suffix: string): string {
189
if (!text.endsWith(suffix)) return text
190
return dropLast(text, suffix.length)
193
export type stringOrNone = string | undefined
195
export function isCommonMethodOrSubclass(typeChecker: ts.TypeChecker, decl: ts.ClassDeclaration): boolean {
196
let name = identName(decl.name)!
197
let isSubclass = isRoot(name)
198
decl.heritageClauses?.forEach(it => {
199
heritageDeclarations(typeChecker, it).forEach(it => {
200
let name = asString(it.name)
201
isSubclass = isSubclass || isRoot(name)
202
if (!ts.isClassDeclaration(it)) return isSubclass
203
isSubclass = isSubclass || isCommonMethodOrSubclass(typeChecker, it)
209
export function isCustomComponentClass(decl: ts.ClassDeclaration) {
210
return PeerGeneratorConfig.customComponent.includes(identName(decl.name)!)
213
export function toSet(option: string | undefined): Set<string> {
214
let set = new Set<string>()
218
.forEach(it => set.add(it))
223
export function getOrPut<K, V>(map: Map<K, V>, key: K, create: (key: K) => V): V {
224
const gotten = map.get(key)
228
const created = create(key)
229
map.set(key, created)
233
export function indentedBy(input: string, indentedBy: number): string {
234
if (input.length > 0 || input.endsWith('\n')) {
236
for (let i = 0; i < indentedBy; i++) space += " "
237
return `${space}${input}`
243
export function typeOrUndefined(type: ts.TypeNode): ts.TypeNode {
244
let needUndefined = true
245
if (ts.isUnionTypeNode(type)) {
246
type.types?.forEach(it => {
247
if (it.kind == ts.SyntaxKind.UndefinedKeyword) needUndefined = false
250
if (!needUndefined) return type
251
return ts.factory.createUnionTypeNode([
253
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
257
export function forEachExpanding<T>(array: T[], action: (element: T) => void): void {
260
if (i === array.length) break
266
export function isTypeParamSuitableType(type: ts.TypeNode): boolean {
267
if (ts.isTypeReferenceNode(type)) {
268
return !['boolean', 'number', 'string', 'undefined', 'any'].includes(type.typeName.getText())
273
export function heritageTypes(typechecker: ts.TypeChecker, clause: ts.HeritageClause): ts.TypeReferenceNode[] {
277
let type = typechecker.getTypeAtLocation(it.expression)
278
let typeNode = typechecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation)
279
if (typeNode && ts.isTypeReferenceNode(typeNode)) return typeNode
282
.filter(it => it != undefined) as ts.TypeReferenceNode[]
285
export function heritageDeclarations(typechecker: ts.TypeChecker, clause: ts.HeritageClause): ts.NamedDeclaration[] {
289
let decls = getDeclarationsByNode(typechecker, it.expression)
290
return decls[0] ?? undefined
295
export function typeName(type: ts.TypeReferenceNode | ts.TypeQueryNode | ts.ImportTypeNode): string | undefined {
296
const entityName = typeEntityName(type)
297
if (!entityName) return undefined
298
if (ts.isIdentifier(entityName)) return ts.idText(entityName as ts.Identifier)
299
if (ts.isQualifiedName(entityName)) {
301
if (!ts.isIdentifier(entityName.right)) throw new Error(`Unexpected right of QualifiedName ${asString(entityName.right)}`)
302
return ts.idText(entityName.right)
306
export function typeEntityName(type: ts.TypeReferenceNode | ts.TypeQueryNode | ts.ImportTypeNode): ts.EntityName | undefined {
307
if (ts.isTypeReferenceNode(type)) return type.typeName
308
if (ts.isTypeQueryNode(type)) return type.exprName
309
if (ts.isImportTypeNode(type)) return type.qualifier
310
throw new Error("unsupported")
313
export function zip<A, B>(left: readonly A[], right: readonly B[]): [A, B][] {
314
if (left.length != right.length) throw new Error("Arrays of different length")
315
return left.map((_, i) => [left[i], right[i]])
318
export function identNameWithNamespace(node: ts.Node): string {
319
let parent = node.parent
320
while (parent && !ts.isModuleDeclaration(parent)) parent = parent.parent
322
return `${identName(parent.name)}_${identName(node)}`
324
return identName(node)!
328
export function identName(node: ts.Node | undefined): string | undefined {
329
if (!node) return undefined
330
if (node.kind == ts.SyntaxKind.AnyKeyword) return `any`
331
if (node.kind == ts.SyntaxKind.ObjectKeyword) return `object`
332
if (node.kind == ts.SyntaxKind.StringKeyword) return `string`
333
if (node.kind == ts.SyntaxKind.BooleanKeyword) return `boolean`
334
if (node.kind == ts.SyntaxKind.NumberKeyword) return `number`
335
if (node.kind == ts.SyntaxKind.VoidKeyword) return `void`
337
if (ts.isTypeReferenceNode(node)) {
338
return identString(node.typeName)
340
if (ts.isArrayTypeNode(node)) {
343
if (ts.isQualifiedName(node)) {
344
return identName(node.right)
346
if (ts.isModuleDeclaration(node)) {
347
return identString(node.name)
349
if (ts.isFunctionDeclaration(node)) {
350
return identString(node.name)
352
if (ts.isPropertyDeclaration(node)) {
354
return identString(node.name)
356
if (ts.isInterfaceDeclaration(node)) {
357
return identString(node.name)
359
if (ts.isClassDeclaration(node)) {
360
return identString(node.name)
362
if (ts.isEnumMember(node)) {
363
return identString(node.name)
365
if (ts.isComputedPropertyName(node)) {
366
return identString(node)
368
if (ts.isUnionTypeNode(node)) {
371
if (ts.isIdentifier(node)) return identString(node)
372
if (ts.isImportTypeNode(node)) return `imported ${identString(node.qualifier)}`
373
if (ts.isTypeLiteralNode(node)) return `TypeLiteral`
374
if (ts.isTupleTypeNode(node)) return `TupleType`
375
if (ts.isIndexSignatureDeclaration(node)) return `IndexSignature`
376
if (ts.isIndexedAccessTypeNode(node)) return `IndexedAccess`
377
if (ts.isTemplateLiteralTypeNode(node)) return `TemplateLiteral`
378
if (ts.isParenthesizedTypeNode(node)) return identName(node.type)
379
throw new Error(`Unknown: ${ts.SyntaxKind[node.kind]}`)
382
function identString(node: ts.Identifier | ts.PrivateIdentifier | ts.StringLiteral | ts.QualifiedName | ts.NumericLiteral | ts.ComputedPropertyName | undefined): string | undefined {
383
if (!node) return undefined
384
if (ts.isStringLiteral(node)) return node.text
385
if (ts.isNumericLiteral(node)) return node.text
386
if (ts.isIdentifier(node)) return ts.idText(node)
387
if (ts.isQualifiedName(node)) return `${identString(node.left)}.${identName(node.right)}`
388
if (ts.isComputedPropertyName(node)) return "<computed property>"
390
throw new Error("Unknown")
393
export const defaultCompilerOptions: ts.CompilerOptions = {
394
target: ts.ScriptTarget.ES5,
395
module: ts.ModuleKind.CommonJS,
400
export function serializerBaseMethods(): string[] {
401
const program = ts.createProgram([
402
"./utils/ts/SerializerBase.ts",
403
"./utils/ts/types.ts",
404
], defaultCompilerOptions)
406
const serializerDecl = program.getSourceFiles()
407
.find(it => it.fileName.includes("SerializerBase"))
409
if (serializerDecl === undefined) return []
411
const methods: string[] = []
412
visit(serializerDecl)
415
function visit(node: ts.Node) {
416
if (ts.isSourceFile(node)) node.statements.forEach(visit)
417
if (ts.isClassDeclaration(node)) node.members.filter(ts.isMethodDeclaration).forEach(visit)
418
if (ts.isMethodDeclaration(node)) methods.push(node.name.getText(serializerDecl))
422
export function getNameWithoutQualifiersRight(node: ts.EntityName | undefined) : string|undefined {
423
if (!node) return undefined
424
if (ts.isQualifiedName(node)) {
425
return identName(node.right)
427
if (ts.isIdentifier(node)) {
428
return identName(node)
430
throw new Error("Impossible")
433
export function getNameWithoutQualifiersLeft(node: ts.EntityName | undefined) : string|undefined {
434
if (!node) return undefined
435
if (ts.isQualifiedName(node)) {
436
return identName(node.left)
438
if (ts.isIdentifier(node)) {
439
return identName(node)
441
throw new Error("Impossible")
444
function snakeCaseToCamelCase(input: string): string {
451
export function renameDtsToPeer(fileName: string, language: Language, withFileExtension: boolean = true) {
452
const renamed = "Ark"
453
.concat(snakeCaseToCamelCase(fileName))
454
.replace(".d.ts", "")
456
if (withFileExtension) {
457
return renamed.concat(language.extension)
462
export function renameDtsToComponent(fileName: string, language: Language, withFileExtension: boolean = true) {
463
const renamed = "Ark"
464
.concat(snakeCaseToCamelCase(fileName))
465
.replace(".d.ts", "")
467
if (withFileExtension) {
468
return renamed.concat(language.extension)
473
export function renameDtsToInterfaces(fileName: string, language: Language, withFileExtension: boolean = true) {
474
const renamed = "Ark"
475
.concat(snakeCaseToCamelCase(fileName), "Interfaces")
476
.replace(".d.ts", "")
478
if (withFileExtension) {
479
return renamed.concat(language.extension)
484
export function renameClassToBuilderClass(className: string, language: Language, withFileExtension: boolean = true) {
485
const renamed = "Ark"
486
.concat(snakeCaseToCamelCase(className))
489
if (withFileExtension) {
490
return renamed.concat(language.extension)
495
export function renameClassToMaterialized(className: string, language: Language, withFileExtension: boolean = true) {
496
const renamed = "Ark"
497
.concat(snakeCaseToCamelCase(className))
498
.concat("Materialized")
500
if (withFileExtension) {
501
return renamed.concat(language.extension)
506
export function importTypeName(type: ts.ImportTypeNode, asType = false): string {
507
return asType ? "object" : identName(type.qualifier)!
510
export function throwException(message: string): never {
511
throw new Error(message)
514
export function className(node: ts.ClassDeclaration | ts.InterfaceDeclaration): string {
515
return nameOrNull(node.name) ?? throwException(`Nameless component ${asString(node)}`)
518
export function groupBy<K, V>(values: V[], selector: (value: V) => K): Map<K, V[]> {
519
const map = new Map<K, V[]>()
520
values.forEach ( value => {
521
const key = selector(value)
522
getOrPut(map, key, it => []).push(value)