idlize

Форк
0
/
util.ts 
525 строк · 17.7 Кб
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
import { isRoot } from "./peer-generation/inheritance";
19

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)
25

26
    private constructor(private name: string, public extension: string, public needsUnionDiscrimination: boolean) {}
27

28
    toString(): string {
29
        return this.name
30
    }
31
}
32

33
export interface NameWithType {
34
    name?: ts.DeclarationName
35
    type?: ts.TypeNode
36
}
37

38
/** True if this is visible outside this file, false otherwise */
39
export function isNodePublic(node: ts.Node): boolean {
40
    return (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Public) !== 0
41
}
42

43
const keywords = new Map<string, string>(
44
    [
45
        ["callback", "callback_"],
46
        ["object", "object_"],
47
        ["attribute", "attribute_"],
48
    ]
49
)
50

51
export function nameOrNullForIdl(name: ts.EntityName | ts.DeclarationName | undefined): string | undefined {
52
    if (name == undefined) return undefined
53

54
    if (ts.isIdentifier(name)) {
55
        let rawName = ts.idText(name)
56
        return keywords.get(rawName) ?? rawName
57
    }
58
    if (ts.isStringLiteral(name)) {
59
        return name.text
60
    }
61

62
    return undefined
63
}
64

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)
69
    }
70
    return undefined
71
}
72

73

74
export function isNamedDeclaration(node: ts.Node): node is ts.NamedDeclaration {
75
    return ("name" in node)
76
}
77

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)`
88
        } else {
89
            return `${ts.SyntaxKind[node.kind]}(${asString(node.name)})`
90
        }
91
    } else {
92
        return `${ts.SyntaxKind[node.kind]}`
93
    }
94
}
95

96

97
export function arrayAt<T>(array: T[] | undefined, index: number): T | undefined {
98
    return array ? array[index >= 0 ? index : array.length + index] : undefined
99
}
100

101
export function getComment(sourceFile: ts.SourceFile, node: ts.Node): string {
102
    const commentRanges = ts.getLeadingCommentRanges(
103
        sourceFile.getFullText(),
104
        node.getFullStart()
105
    )
106

107
    if (!commentRanges) return ""
108

109
    return commentRanges
110
        .map(range => sourceFile.getFullText().slice(range.pos, range.end))
111
        .join("\n")
112
}
113

114
export function getSymbolByNode(typechecker: ts.TypeChecker, node: ts.Node): ts.Symbol | undefined {
115
    return typechecker.getSymbolAtLocation(node)
116
}
117

118
export function getDeclarationsByNode(typechecker: ts.TypeChecker, node: ts.Node): ts.Declaration[] {
119
    return getSymbolByNode(typechecker, node)?.getDeclarations() ?? []
120
}
121

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)
127
    } else {
128
        return declarations
129
    }
130
}
131

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
137
    }
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
149
                    }
150
                })
151
            }
152
        }
153
    })
154
    return exportedName
155
}
156

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])
161
}
162

163
export function isReadonly(modifierLikes: ts.NodeArray<ts.ModifierLike> | undefined): boolean {
164
    return modifierLikes?.find(it => it.kind == ts.SyntaxKind.ReadonlyKeyword) != undefined
165
}
166

167
export function isStatic(modifierLikes: ts.NodeArray<ts.ModifierLike> | undefined): boolean {
168
    return modifierLikes?.find(it => it.kind == ts.SyntaxKind.StaticKeyword) != undefined
169
}
170

171
export function getLineNumberString(sourceFile: ts.SourceFile, position: number): string {
172
    let pos = ts.getLineAndCharacterOfPosition(sourceFile, position)
173
    return `${pos.line + 1}:${pos.character}`
174
}
175

176
export function isDefined<T>(value: T | null | undefined): value is T {
177
    return !!value
178
}
179

180
export function capitalize(string: string): string {
181
    return string.charAt(0).toUpperCase() + string.slice(1)
182
}
183

184
export function dropLast(text: string, chars: number): string {
185
    return text.substring(0, text.length - chars)
186
}
187

188
export function dropSuffix(text: string, suffix: string): string {
189
    if (!text.endsWith(suffix)) return text
190
    return dropLast(text, suffix.length)
191
}
192

193
export type stringOrNone = string | undefined
194

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)
204
        })
205
    })
206
    return isSubclass
207
}
208

209
export function isCustomComponentClass(decl: ts.ClassDeclaration) {
210
    return PeerGeneratorConfig.customComponent.includes(identName(decl.name)!)
211
}
212

213
export function toSet(option: string | undefined): Set<string> {
214
    let set = new Set<string>()
215
    if (option) {
216
        option
217
            .split(",")
218
            .forEach(it => set.add(it))
219
    }
220
    return set
221
}
222

223
export function getOrPut<K, V>(map: Map<K, V>, key: K, create: (key: K) => V): V {
224
    const gotten = map.get(key)
225
    if (gotten) {
226
        return gotten
227
    }
228
    const created = create(key)
229
    map.set(key, created)
230
    return created
231
}
232

233
export function indentedBy(input: string, indentedBy: number): string {
234
    if (input.length > 0 || input.endsWith('\n')) {
235
        let space = ""
236
        for (let i = 0; i < indentedBy; i++) space += "    "
237
        return `${space}${input}`
238
    } else {
239
        return ""
240
    }
241
}
242

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
248
        })
249
    }
250
    if (!needUndefined) return type
251
    return ts.factory.createUnionTypeNode([
252
        type,
253
        ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
254
    ])
255
}
256

257
export function forEachExpanding<T>(array: T[], action: (element: T) => void): void {
258
    let i = 0
259
    while (true) {
260
        if (i === array.length) break
261
        action(array[i])
262
        i += 1
263
    }
264
}
265

266
export function isTypeParamSuitableType(type: ts.TypeNode): boolean {
267
    if (ts.isTypeReferenceNode(type)) {
268
        return !['boolean', 'number', 'string', 'undefined', 'any'].includes(type.typeName.getText())
269
    }
270
    return false
271
}
272

273
export function heritageTypes(typechecker: ts.TypeChecker, clause: ts.HeritageClause): ts.TypeReferenceNode[] {
274
    return clause
275
        .types
276
        .map(it => {
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
280
            return undefined
281
        })
282
        .filter(it => it != undefined) as ts.TypeReferenceNode[]
283
}
284

285
export function heritageDeclarations(typechecker: ts.TypeChecker, clause: ts.HeritageClause): ts.NamedDeclaration[] {
286
    return clause
287
        .types
288
        .map(it => {
289
            let decls = getDeclarationsByNode(typechecker, it.expression)
290
            return decls[0] ?? undefined
291
        })
292
        .filter(isDefined)
293
}
294

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)) {
300
        // a.b.c is QualifiedName((QualifiedName a, b), c) so the right one is always an Identifier?
301
        if (!ts.isIdentifier(entityName.right)) throw new Error(`Unexpected right of QualifiedName ${asString(entityName.right)}`)
302
        return ts.idText(entityName.right)
303
    }
304
}
305

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")
311
}
312

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]])
316
}
317

318
export function identNameWithNamespace(node: ts.Node): string {
319
    let parent = node.parent
320
    while (parent && !ts.isModuleDeclaration(parent)) parent = parent.parent
321
    if (parent) {
322
        return `${identName(parent.name)}_${identName(node)}`
323
    } else {
324
        return identName(node)!
325
    }
326
}
327

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`
336

337
    if (ts.isTypeReferenceNode(node)) {
338
        return identString(node.typeName)
339
    }
340
    if (ts.isArrayTypeNode(node)) {
341
        return `Array`
342
    }
343
    if (ts.isQualifiedName(node)) {
344
        return identName(node.right)
345
    }
346
    if (ts.isModuleDeclaration(node)) {
347
        return identString(node.name)
348
    }
349
    if (ts.isFunctionDeclaration(node)) {
350
        return identString(node.name)
351
    }
352
    if (ts.isPropertyDeclaration(node)) {
353
        // TODO: mention parent's name
354
        return identString(node.name)
355
    }
356
    if (ts.isInterfaceDeclaration(node)) {
357
        return identString(node.name)
358
    }
359
    if (ts.isClassDeclaration(node)) {
360
        return identString(node.name)
361
    }
362
    if (ts.isEnumMember(node)) {
363
        return identString(node.name)
364
    }
365
    if (ts.isComputedPropertyName(node)) {
366
        return identString(node)
367
    }
368
    if (ts.isUnionTypeNode(node)) {
369
        return `UnionType`
370
    }
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]}`)
380
}
381

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

390
    throw new Error("Unknown")
391
}
392

393
export const defaultCompilerOptions: ts.CompilerOptions = {
394
    target: ts.ScriptTarget.ES5,
395
    module: ts.ModuleKind.CommonJS,
396
    noLib: true,
397
    types: []
398
}
399

400
export function serializerBaseMethods(): string[] {
401
    const program = ts.createProgram([
402
        "./utils/ts/SerializerBase.ts",
403
        "./utils/ts/types.ts",
404
    ], defaultCompilerOptions)
405

406
    const serializerDecl = program.getSourceFiles()
407
        .find(it => it.fileName.includes("SerializerBase"))
408
    // TODO: pack classes with npm package
409
    if (serializerDecl === undefined) return []
410

411
    const methods: string[] = []
412
    visit(serializerDecl)
413
    return methods
414

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))
419
    }
420
}
421

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)
426
    }
427
    if (ts.isIdentifier(node)) {
428
        return identName(node)
429
    }
430
    throw new Error("Impossible")
431
}
432

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)
437
    }
438
    if (ts.isIdentifier(node)) {
439
        return identName(node)
440
    }
441
    throw new Error("Impossible")
442
}
443

444
function snakeCaseToCamelCase(input: string): string {
445
    return input
446
        .split("_")
447
        .map(capitalize)
448
        .join("")
449
}
450

451
export function renameDtsToPeer(fileName: string, language: Language, withFileExtension: boolean = true) {
452
    const renamed = "Ark"
453
        .concat(snakeCaseToCamelCase(fileName))
454
        .replace(".d.ts", "")
455
        .concat("Peer")
456
    if (withFileExtension) {
457
        return renamed.concat(language.extension)
458
    }
459
    return renamed
460
}
461

462
export function renameDtsToComponent(fileName: string, language: Language, withFileExtension: boolean = true) {
463
    const renamed = "Ark"
464
        .concat(snakeCaseToCamelCase(fileName))
465
        .replace(".d.ts", "")
466

467
    if (withFileExtension) {
468
        return renamed.concat(language.extension)
469
    }
470
    return renamed
471
}
472

473
export function renameDtsToInterfaces(fileName: string, language: Language, withFileExtension: boolean = true) {
474
    const renamed = "Ark"
475
        .concat(snakeCaseToCamelCase(fileName), "Interfaces")
476
        .replace(".d.ts", "")
477

478
    if (withFileExtension) {
479
        return renamed.concat(language.extension)
480
    }
481
    return renamed
482
}
483

484
export function renameClassToBuilderClass(className: string, language: Language, withFileExtension: boolean = true) {
485
    const renamed = "Ark"
486
        .concat(snakeCaseToCamelCase(className))
487
        .concat("Builder")
488

489
    if (withFileExtension) {
490
        return renamed.concat(language.extension)
491
    }
492
    return renamed
493
}
494

495
export function renameClassToMaterialized(className: string, language: Language, withFileExtension: boolean = true) {
496
    const renamed = "Ark"
497
        .concat(snakeCaseToCamelCase(className))
498
        .concat("Materialized")
499

500
    if (withFileExtension) {
501
        return renamed.concat(language.extension)
502
    }
503
    return renamed
504
}
505

506
export function importTypeName(type: ts.ImportTypeNode, asType = false): string {
507
    return asType ? "object" : identName(type.qualifier)!
508
}
509

510
export function throwException(message: string): never {
511
    throw new Error(message)
512
}
513

514
export function className(node: ts.ClassDeclaration | ts.InterfaceDeclaration): string {
515
    return nameOrNull(node.name) ?? throwException(`Nameless component ${asString(node)}`)
516
}
517

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)
523
    })
524
    return map
525
}

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

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

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

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