idlize

Форк
0
/
util.ts 
418 строк · 14.4 Кб
1
/*
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
6
 *
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 *
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.
14
 */
15

16
import * as ts from "typescript"
17
import { PeerGeneratorConfig } from "./peer-generation/PeerGeneratorConfig"
18

19
export interface NameWithType {
20
    name?: ts.DeclarationName
21
    type?: ts.TypeNode
22
}
23

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
27
}
28

29
const keywords = new Map<string, string>(
30
    [
31
        ["callback", "callback_"],
32
        ["object", "object_"],
33
        ["attribute", "attribute_"],
34
    ]
35
)
36

37
export function nameOrNullForIdl(name: ts.EntityName | ts.DeclarationName | undefined): string | undefined {
38
    if (name == undefined) return undefined
39

40
    if (ts.isIdentifier(name)) {
41
        let rawName = ts.idText(name)
42
        return keywords.get(rawName) ?? rawName
43
    }
44
    if (ts.isStringLiteral(name)) {
45
        return name.text
46
    }
47

48
    return undefined
49
}
50

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)
55
    }
56
    return undefined
57
}
58

59

60
export function isNamedDeclaration(node: ts.Node): node is ts.NamedDeclaration {
61
    return ("name" in node)
62
}
63

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)`
74
        } else {
75
            return `${ts.SyntaxKind[node.kind]}(${asString(node.name)})`
76
        }
77
    } else {
78
        return `${ts.SyntaxKind[node.kind]}`
79
    }
80
}
81

82

83
export function arrayAt<T>(array: T[] | undefined, index: number): T | undefined {
84
    return array ? array[index >= 0 ? index : array.length + index] : undefined
85
}
86

87
export function getComment(sourceFile: ts.SourceFile, node: ts.Node): string {
88
    const commentRanges = ts.getLeadingCommentRanges(
89
        sourceFile.getFullText(),
90
        node.getFullStart()
91
    )
92

93
    if (!commentRanges) return ""
94

95
    return commentRanges
96
        .map(range => sourceFile.getFullText().slice(range.pos, range.end))
97
        .join("\n")
98
}
99

100
export function getSymbolByNode(typechecker: ts.TypeChecker, node: ts.Node): ts.Symbol | undefined {
101
    return typechecker.getSymbolAtLocation(node)
102
}
103

104
export function getDeclarationsByNode(typechecker: ts.TypeChecker, node: ts.Node): ts.Declaration[] {
105
    return getSymbolByNode(typechecker, node)?.getDeclarations() ?? []
106
}
107

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)
113
    } else {
114
        return declarations
115
    }
116
}
117

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
123
    }
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
135
                    }
136
                })
137
            }
138
        }
139
    })
140
    return exportedName
141
}
142

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])
147
}
148

149
export function isReadonly(modifierLikes: ts.NodeArray<ts.ModifierLike> | undefined): boolean {
150
    return modifierLikes?.find(it => it.kind == ts.SyntaxKind.ReadonlyKeyword) != undefined
151
}
152

153
export function isStatic(modifierLikes: ts.NodeArray<ts.ModifierLike> | undefined): boolean {
154
    return modifierLikes?.find(it => it.kind == ts.SyntaxKind.StaticKeyword) != undefined
155
}
156

157
export function getLineNumberString(sourceFile: ts.SourceFile, position: number): string {
158
    let pos = ts.getLineAndCharacterOfPosition(sourceFile, position)
159
    return `${pos.line + 1}:${pos.character}`
160
}
161

162
export function isDefined<T>(value: T | null | undefined): value is T {
163
    return !!value
164
}
165

166
export function capitalize(string: string): string {
167
    return string.charAt(0).toUpperCase() + string.slice(1)
168
}
169

170
export function dropLast(text: string, chars: number): string {
171
    return text.substring(0, text.length - chars)
172
}
173

174
export function dropSuffix(text: string, suffix: string): string {
175
    if (!text.endsWith(suffix)) return text
176
    return dropLast(text, suffix.length)
177
}
178

179
export type stringOrNone = string | undefined
180

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)
190
        })
191
    })
192
    return isRoot
193
}
194

195
export function toSet(option: string | undefined): Set<string> {
196
    let set = new Set<string>()
197
    if (option) {
198
        option
199
            .split(",")
200
            .forEach(it => set.add(it))
201
    }
202
    return set
203
}
204

205
export function indentedBy(input: string, indentedBy: number): string {
206
    let space = ""
207
    for (let i = 0; i < indentedBy; i++) space += "  "
208
    return `${space}${input}`
209
}
210

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
216
        })
217
    }
218
    if (!needUndefined) return type
219
    return ts.factory.createUnionTypeNode([
220
        type,
221
        ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
222
    ])
223
}
224

225
export function forEachExpanding<T>(array: T[], action: (element: T) => void): void {
226
    let i = 0
227
    while (true) {
228
        if (i === array.length) break
229
        action(array[i])
230
        i += 1
231
    }
232
}
233

234
export function isTypeParamSuitableType(type: ts.TypeNode): boolean {
235
    if (ts.isTypeReferenceNode(type)) {
236
        return !['boolean', 'number', 'string', 'undefined', 'any'].includes(type.typeName.getText())
237
    }
238
    return false
239
}
240

241
export function heritageTypes(typechecker: ts.TypeChecker, clause: ts.HeritageClause): ts.TypeReferenceNode[] {
242
    return clause
243
        .types
244
        .map(it => {
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
248
            return undefined
249
        })
250
        .filter(it => it != undefined) as ts.TypeReferenceNode[]
251
}
252

253
export function heritageDeclarations(typechecker: ts.TypeChecker, clause: ts.HeritageClause): ts.NamedDeclaration[] {
254
    return clause
255
        .types
256
        .map(it => {
257
            let decls = getDeclarationsByNode(typechecker, it.expression)
258
            return decls[0] ?? undefined
259
        })
260
        .filter(isDefined)
261
}
262

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)
271
    }
272
}
273

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")
279
}
280

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]])
284
}
285

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)
293
    }
294
    if (ts.isQualifiedName(node)) {
295
        return identName(node.right)
296
    }
297
    if (ts.isModuleDeclaration(node)) {
298
        return identString(node.name)
299
    }
300
    if (ts.isFunctionDeclaration(node)) {
301
        return identString(node.name)
302
    }
303
    if (ts.isPropertyDeclaration(node)) {
304
        // TODO: mention parent's name
305
        return identString(node.name)
306
    }
307
    if (ts.isInterfaceDeclaration(node)) {
308
        return identString(node.name)
309
    }
310
    if (ts.isClassDeclaration(node)) {
311
        return identString(node.name)
312
    }
313
    if (ts.isEnumMember(node)) {
314
        return identString(node.name)
315
    }
316
    if (ts.isComputedPropertyName(node)) {
317
        return identString(node)
318
    }
319
    if (ts.isUnionTypeNode(node)) {
320
        return `UnionType`
321
    }
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}`)
330
}
331

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>"
339

340
    throw new Error("Unknown")
341
}
342

343
export const defaultCompilerOptions: ts.CompilerOptions = {
344
    target: ts.ScriptTarget.ES5,
345
    module: ts.ModuleKind.CommonJS,
346
    noLib: true,
347
    types: []
348
}
349

350
export function serializerBaseMethods(): string[] {
351
    const program = ts.createProgram([
352
        "./utils/ts/SerializerBase.ts",
353
        "./utils/ts/types.ts",
354
    ], defaultCompilerOptions)
355

356
    const serializerDecl = program.getSourceFiles()
357
        .find(it => it.fileName.includes("SerializerBase"))
358
    // TODO: pack classes with npm package
359
    if (serializerDecl === undefined) return []
360

361
    const methods: string[] = []
362
    visit(serializerDecl)
363
    return methods
364

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))
369
    }
370
}
371

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)
376
    }
377
    if (ts.isIdentifier(node)) {
378
        return identName(node)
379
    }
380
    throw new Error("Impossible")
381
}
382

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)
387
    }
388
    if (ts.isIdentifier(node)) {
389
        return identName(node)
390
    }
391
    throw new Error("Impossible")
392
}
393

394
export function renameDtsToPeer(fileName: string, withFileExtension: boolean = true) {
395
    const renamed = "Ark"
396
        .concat(snakeCaseToCamelCase(fileName))
397
        .replace(".d.ts", "")
398
        .concat("Peer")
399
    if (withFileExtension) {
400
        return renamed.concat(".ts")
401
    }
402
    return renamed
403

404
    function snakeCaseToCamelCase(input: string): string {
405
        return input
406
            .split("_")
407
            .map(capitalize)
408
            .join("")
409
    }
410
}
411

412
export function importTypeName(type: ts.ImportTypeNode, asType = false): string {
413
    return asType ? "object" : identName(type.qualifier)!
414
}
415

416
export function throwException(message: string): never {
417
    throw new Error(message)
418
}
419

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.