2
* Copyright (c) 2024 Huawei Device Co., Ltd.
3
* Licensed under the Apache License, Version 2.0 (the "License");
4
* you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at
7
* http://www.apache.org/licenses/LICENSE-2.0
9
* Unless required by applicable law or agreed to in writing, software
10
* distributed under the License is distributed on an "AS IS" BASIS,
11
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
* See the License for the specific language governing permissions and
13
* limitations under the License.
16
import * as ts from "typescript"
17
import { PeerGeneratorConfig } from "./peer-generation/PeerGeneratorConfig"
19
export interface NameWithType {
20
name?: ts.DeclarationName
24
/** True if this is visible outside this file, false otherwise */
25
export function isNodePublic(node: ts.Node): boolean {
26
return (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Public) !== 0
29
const keywords = new Map<string, string>(
31
["callback", "callback_"],
32
["object", "object_"],
33
["attribute", "attribute_"],
37
export function nameOrNullForIdl(name: ts.EntityName | ts.DeclarationName | undefined): string | undefined {
38
if (name == undefined) return undefined
40
if (ts.isIdentifier(name)) {
41
let rawName = ts.idText(name)
42
return keywords.get(rawName) ?? rawName
44
if (ts.isStringLiteral(name)) {
51
export function nameOrNull(name: ts.EntityName | ts.DeclarationName | undefined): string | undefined {
52
if (name == undefined) return undefined
53
if (ts.isIdentifier(name)) {
54
return ts.idText(name)
60
export function isNamedDeclaration(node: ts.Node): node is ts.NamedDeclaration {
61
return ("name" in node)
64
export function asString(node: ts.Node | undefined): string {
65
if (node === undefined) return "undefined node"
66
if (ts.isIdentifier(node)) return ts.idText(node)
67
if (ts.isQualifiedName(node)) return `${identName(node.left)}.${identName(node.right)}`
68
if (ts.isStringLiteral(node)) return node.text
69
if (ts.isTypeReferenceNode(node)) return `${ts.SyntaxKind[node.kind]}(${asString(node.typeName)})`
70
if (ts.isImportTypeNode(node)) return `${ts.SyntaxKind[node.kind]}(${asString(node.qualifier)})`
71
if (isNamedDeclaration(node)) {
72
if (node.name === undefined) {
73
return `${ts.SyntaxKind[node.kind]}(undefined name)`
75
return `${ts.SyntaxKind[node.kind]}(${asString(node.name)})`
78
return `${ts.SyntaxKind[node.kind]}`
83
export function arrayAt<T>(array: T[] | undefined, index: number): T | undefined {
84
return array ? array[index >= 0 ? index : array.length + index] : undefined
87
export function getComment(sourceFile: ts.SourceFile, node: ts.Node): string {
88
const commentRanges = ts.getLeadingCommentRanges(
89
sourceFile.getFullText(),
93
if (!commentRanges) return ""
96
.map(range => sourceFile.getFullText().slice(range.pos, range.end))
100
export function getSymbolByNode(typechecker: ts.TypeChecker, node: ts.Node): ts.Symbol | undefined {
101
return typechecker.getSymbolAtLocation(node)
104
export function getDeclarationsByNode(typechecker: ts.TypeChecker, node: ts.Node): ts.Declaration[] {
105
return getSymbolByNode(typechecker, node)?.getDeclarations() ?? []
108
export function findRealDeclarations(typechecker: ts.TypeChecker, node: ts.Node): ts.Declaration[] {
109
const declarations = getDeclarationsByNode(typechecker, node)
110
const first = declarations[0]
111
if (first && ts.isExportAssignment(first)) {
112
return findRealDeclarations(typechecker, first.expression)
118
export function getExportedDeclarationNameByDecl(declaration: ts.NamedDeclaration): string | undefined {
119
let declName = declaration.name ? ts.idText(declaration.name as ts.Identifier) : undefined
120
let current: ts.Node = declaration
121
while (current != undefined && !ts.isSourceFile(current)) {
122
current = current.parent
124
let source = current as ts.SourceFile
125
let exportedName = declName
126
source.forEachChild(it => {
127
if (ts.isExportDeclaration(it)) {
128
let clause = it.exportClause!
129
if (ts.isNamedExportBindings(clause) && ts.isNamedExports(clause)) {
130
clause.elements.forEach(it => {
131
let propName = it.propertyName ? ts.idText(it.propertyName) : undefined
132
let property = ts.idText(it.name)
133
if (propName == declName) {
134
exportedName = property
143
export function getExportedDeclarationNameByNode(typechecker: ts.TypeChecker, node: ts.Node): string | undefined {
144
let declarations = getDeclarationsByNode(typechecker, node)
145
if (declarations.length == 0) return undefined
146
return getExportedDeclarationNameByDecl(declarations[0])
149
export function isReadonly(modifierLikes: ts.NodeArray<ts.ModifierLike> | undefined): boolean {
150
return modifierLikes?.find(it => it.kind == ts.SyntaxKind.ReadonlyKeyword) != undefined
153
export function isStatic(modifierLikes: ts.NodeArray<ts.ModifierLike> | undefined): boolean {
154
return modifierLikes?.find(it => it.kind == ts.SyntaxKind.StaticKeyword) != undefined
157
export function getLineNumberString(sourceFile: ts.SourceFile, position: number): string {
158
let pos = ts.getLineAndCharacterOfPosition(sourceFile, position)
159
return `${pos.line + 1}:${pos.character}`
162
export function isDefined<T>(value: T | null | undefined): value is T {
166
export function capitalize(string: string): string {
167
return string.charAt(0).toUpperCase() + string.slice(1)
170
export function dropLast(text: string, chars: number): string {
171
return text.substring(0, text.length - chars)
174
export function dropSuffix(text: string, suffix: string): string {
175
if (!text.endsWith(suffix)) return text
176
return dropLast(text, suffix.length)
179
export type stringOrNone = string | undefined
181
export function isCommonMethodOrSubclass(typeChecker: ts.TypeChecker, decl: ts.ClassDeclaration): boolean {
182
let name = identName(decl.name)!
183
let isRoot = PeerGeneratorConfig.rootComponents.includes(name)
184
decl.heritageClauses?.forEach(it => {
185
heritageDeclarations(typeChecker, it).forEach(it => {
186
let name = asString(it.name)
187
isRoot = isRoot || PeerGeneratorConfig.rootComponents.includes(name)
188
if (!ts.isClassDeclaration(it)) return
189
isRoot = isRoot || isCommonMethodOrSubclass(typeChecker, it)
195
export function toSet(option: string | undefined): Set<string> {
196
let set = new Set<string>()
200
.forEach(it => set.add(it))
205
export function indentedBy(input: string, indentedBy: number): string {
207
for (let i = 0; i < indentedBy; i++) space += " "
208
return `${space}${input}`
211
export function typeOrUndefined(type: ts.TypeNode): ts.TypeNode {
212
let needUndefined = true
213
if (ts.isUnionTypeNode(type)) {
214
type.types?.forEach(it => {
215
if (it.kind == ts.SyntaxKind.UndefinedKeyword) needUndefined = false
218
if (!needUndefined) return type
219
return ts.factory.createUnionTypeNode([
221
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
225
export function forEachExpanding<T>(array: T[], action: (element: T) => void): void {
228
if (i === array.length) break
234
export function isTypeParamSuitableType(type: ts.TypeNode): boolean {
235
if (ts.isTypeReferenceNode(type)) {
236
return !['boolean', 'number', 'string', 'undefined', 'any'].includes(type.typeName.getText())
241
export function heritageTypes(typechecker: ts.TypeChecker, clause: ts.HeritageClause): ts.TypeReferenceNode[] {
245
let type = typechecker.getTypeAtLocation(it.expression)
246
let typeNode = typechecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation)
247
if (typeNode && ts.isTypeReferenceNode(typeNode)) return typeNode
250
.filter(it => it != undefined) as ts.TypeReferenceNode[]
253
export function heritageDeclarations(typechecker: ts.TypeChecker, clause: ts.HeritageClause): ts.NamedDeclaration[] {
257
let decls = getDeclarationsByNode(typechecker, it.expression)
258
return decls[0] ?? undefined
263
export function typeName(type: ts.TypeReferenceNode | ts.TypeQueryNode | ts.ImportTypeNode): string | undefined {
264
const entityName = typeEntityName(type)
265
if (!entityName) return undefined
266
if (ts.isIdentifier(entityName)) return ts.idText(entityName as ts.Identifier)
267
if (ts.isQualifiedName(entityName)) {
268
// a.b.c is QualifiedName((QualifiedName a, b), c) so the right one is always an Identifier?
269
if (!ts.isIdentifier(entityName.right)) throw new Error(`Unexpected right of QualifiedName ${asString(entityName.right)}`)
270
return ts.idText(entityName.right)
274
export function typeEntityName(type: ts.TypeReferenceNode | ts.TypeQueryNode | ts.ImportTypeNode): ts.EntityName | undefined {
275
if (ts.isTypeReferenceNode(type)) return type.typeName
276
if (ts.isTypeQueryNode(type)) return type.exprName
277
if (ts.isImportTypeNode(type)) return type.qualifier
278
throw new Error("unsupported")
281
export function zip<A, B>(left: readonly A[], right: readonly B[]): [A, B][] {
282
if (left.length != right.length) throw new Error("Arrays of different length")
283
return left.map((_, i) => [left[i], right[i]])
286
export function identName(node: ts.Node | undefined): string | undefined {
287
if (!node) return undefined
288
if (node.kind == ts.SyntaxKind.AnyKeyword) return `any`
289
if (node.kind == ts.SyntaxKind.ObjectKeyword) return `object`
290
if (node.kind == ts.SyntaxKind.StringKeyword) return `string`
291
if (ts.isTypeReferenceNode(node)) {
292
return identString(node.typeName)
294
if (ts.isQualifiedName(node)) {
295
return identName(node.right)
297
if (ts.isModuleDeclaration(node)) {
298
return identString(node.name)
300
if (ts.isFunctionDeclaration(node)) {
301
return identString(node.name)
303
if (ts.isPropertyDeclaration(node)) {
304
// TODO: mention parent's name
305
return identString(node.name)
307
if (ts.isInterfaceDeclaration(node)) {
308
return identString(node.name)
310
if (ts.isClassDeclaration(node)) {
311
return identString(node.name)
313
if (ts.isEnumMember(node)) {
314
return identString(node.name)
316
if (ts.isComputedPropertyName(node)) {
317
return identString(node)
319
if (ts.isUnionTypeNode(node)) {
322
if (ts.isIdentifier(node)) return identString(node)
323
if (ts.isImportTypeNode(node)) return `imported ${identString(node.qualifier)}`
324
if (ts.isTypeLiteralNode(node)) return `TypeLiteral`
325
if (ts.isTupleTypeNode(node)) return `TupleType`
326
if (ts.isIndexSignatureDeclaration(node)) return `IndexSignature`
327
if (ts.isIndexedAccessTypeNode(node)) return `IndexedAccess`
328
if (ts.isTemplateLiteralTypeNode(node)) return `TemplateLiteral`
329
throw new Error(`Unknown: ${node.kind}`)
332
function identString(node: ts.Identifier | ts.PrivateIdentifier | ts.StringLiteral | ts.QualifiedName | ts.NumericLiteral | ts.ComputedPropertyName | undefined): string | undefined {
333
if (!node) return undefined
334
if (ts.isStringLiteral(node)) return node.text
335
if (ts.isNumericLiteral(node)) return node.text
336
if (ts.isIdentifier(node)) return ts.idText(node)
337
if (ts.isQualifiedName(node)) return `${identString(node.left)}.${identName(node.right)}`
338
if (ts.isComputedPropertyName(node)) return "<computed property>"
340
throw new Error("Unknown")
343
export const defaultCompilerOptions: ts.CompilerOptions = {
344
target: ts.ScriptTarget.ES5,
345
module: ts.ModuleKind.CommonJS,
350
export function serializerBaseMethods(): string[] {
351
const program = ts.createProgram([
352
"./utils/ts/SerializerBase.ts",
353
"./utils/ts/types.ts",
354
], defaultCompilerOptions)
356
const serializerDecl = program.getSourceFiles()
357
.find(it => it.fileName.includes("SerializerBase"))
358
// TODO: pack classes with npm package
359
if (serializerDecl === undefined) return []
361
const methods: string[] = []
362
visit(serializerDecl)
365
function visit(node: ts.Node) {
366
if (ts.isSourceFile(node)) node.statements.forEach(visit)
367
if (ts.isClassDeclaration(node)) node.members.filter(ts.isMethodDeclaration).forEach(visit)
368
if (ts.isMethodDeclaration(node)) methods.push(node.name.getText(serializerDecl))
372
export function getNameWithoutQualifiersRight(node: ts.EntityName | undefined) : string|undefined {
373
if (!node) return undefined
374
if (ts.isQualifiedName(node)) {
375
return identName(node.right)
377
if (ts.isIdentifier(node)) {
378
return identName(node)
380
throw new Error("Impossible")
383
export function getNameWithoutQualifiersLeft(node: ts.EntityName | undefined) : string|undefined {
384
if (!node) return undefined
385
if (ts.isQualifiedName(node)) {
386
return identName(node.left)
388
if (ts.isIdentifier(node)) {
389
return identName(node)
391
throw new Error("Impossible")
394
export function renameDtsToPeer(fileName: string, withFileExtension: boolean = true) {
395
const renamed = "Ark"
396
.concat(snakeCaseToCamelCase(fileName))
397
.replace(".d.ts", "")
399
if (withFileExtension) {
400
return renamed.concat(".ts")
404
function snakeCaseToCamelCase(input: string): string {
412
export function importTypeName(type: ts.ImportTypeNode, asType = false): string {
413
return asType ? "object" : identName(type.qualifier)!
416
export function throwException(message: string): never {
417
throw new Error(message)