idlize

Форк
0
/
IDLVisitor.ts 
878 строк · 37.1 Кб
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
import * as ts from "typescript"
16
import * as path from "path"
17
import { parse } from 'comment-parser'
18
import {
19
    createBooleanType, createContainerType, createEnumType, createNumberType, createReferenceType, createStringType, createTypedef,
20
    createTypeParameterReference, createUndefinedType, createUnionType, IDLCallable, IDLCallback, IDLConstant, IDLConstructor,
21
    IDLEntity, IDLEntry, IDLEnum, IDLEnumMember, IDLExtendedAttribute, IDLFunction, IDLInterface, IDLKind, IDLMethod, IDLModuleType, IDLParameter, IDLProperty, IDLType, IDLTypedef,
22
    printType
23
} from "./idl"
24
import {
25
    asString, capitalize, getComment, getDeclarationsByNode, getExportedDeclarationNameByDecl, getExportedDeclarationNameByNode, identName, isCommonMethodOrSubclass, isNodePublic, isReadonly, isStatic, nameOrNull, nameOrNullForIdl as nameOrUndefined, stringOrNone
26
} from "./util"
27
import { GenericVisitor } from "./options"
28
import { PeerGeneratorConfig } from "./peer-generation/PeerGeneratorConfig"
29
import { OptionValues } from "commander"
30
import { PrimitiveType } from "./peer-generation/DeclarationTable"
31

32
const typeMapper = new Map<string, string>(
33
    [
34
        ["null", "undefined"],
35
        ["void", "undefined"],
36
        ["object", "Object"],
37
        ["Array", "sequence"],
38
        ["string", "DOMString"],
39
        ["Map", "record"],
40
        // TODO: rethink that
41
        ["\"2d\"", "string"],
42
        ["\"auto\"", "string"]
43
    ]
44
)
45

46
export class CompileContext {
47
    functionCounter = 0
48
    objectCounter = 0
49
    tupleCounter = 0
50
    unionCounter = 0
51
}
52

53
export class IDLVisitor implements GenericVisitor<IDLEntry[]> {
54
    private output: IDLEntry[] = []
55
    private currentScope:  IDLEntry[] = []
56
    scopes: IDLEntry[][] = []
57
    globalConstants: IDLConstant[] = []
58
    globalFunctions: IDLMethod[] = []
59

60
    startScope() {
61
        this.scopes.push(this.currentScope)
62
        this.currentScope = []
63
    }
64

65
    endScope() {
66
        const result = this.currentScope
67
        this.currentScope = this.scopes.pop()!
68
        return result
69
    }
70

71
    constructor(
72
        private sourceFile: ts.SourceFile,
73
        private typeChecker: ts.TypeChecker,
74
        private compileContext: CompileContext,
75
        private options: OptionValues) { }
76

77
    visitWholeFile(): IDLEntry[] {
78
        this.addMeta()
79
        ts.forEachChild(this.sourceFile, (node) => this.visit(node))
80
        if (this.globalConstants.length > 0 || this.globalFunctions.length > 0) {
81
            this.output.push({
82
                kind: IDLKind.Interface,
83
                name: `GlobalScope_${path.basename(this.sourceFile.fileName).replace(".d.ts", "")}`,
84
                extendedAttributes: [ {name: "GlobalScope" } ],
85
                methods: this.globalFunctions,
86
                constants: this.globalConstants,
87
                properties: [],
88
                constructors: [],
89
                callables: [],
90
                inheritance: []
91
            } as IDLInterface)
92
        }
93
        return this.output
94
    }
95

96
    makeEnumMember(name: string, value: string): IDLEnumMember {
97
        return { name: name, kind: IDLKind.EnumMember, type: { name: "DOMString", kind: IDLKind.PrimitiveType }, initializer: value }
98
    }
99

100
    addMeta() {
101
        this.output.push({
102
            kind: IDLKind.Enum,
103
            name: `Metadata`,
104
            extendedAttributes: [ {name: "Synthetic" } ],
105
            elements: [
106
                this.makeEnumMember("package", "org.openharmony.arkui"),
107
                this.makeEnumMember("imports", "'./one', './two'"),
108
            ]
109
        } as IDLEnum)
110
    }
111

112
    /** visit nodes finding exported classes */
113
    visit(node: ts.Node) {
114
        if (ts.isClassDeclaration(node)) {
115
            this.output.push(this.serializeClass(node))
116
        } else if (ts.isInterfaceDeclaration(node)) {
117
            this.output.push(this.serializeInterface(node))
118
        } else if (ts.isModuleDeclaration(node)) {
119
            if (this.isKnownAmbientModuleDeclaration(node)) {
120
                this.output.push(this.serializeAmbientModuleDeclaration(node))
121
            } else {
122
                // This is a namespace, visit its children
123
                ts.forEachChild(node, (node) => this.visit(node));
124
            }
125
        } else if (ts.isEnumDeclaration(node)) {
126
            this.output.push(this.serializeEnum(node))
127
        } else if (ts.isTypeAliasDeclaration(node)) {
128
            this.output.push(this.serializeTypeAlias(node))
129
        } else if (ts.isFunctionDeclaration(node)) {
130
            this.globalFunctions.push(this.serializeMethod(node))
131
        } else if (ts.isVariableStatement(node)) {
132
            this.globalConstants.push(...this.serializeConstants(node))
133
        }
134
    }
135

136
    serializeAmbientModuleDeclaration(node: ts.ModuleDeclaration): IDLModuleType {
137
        const name = nameOrUndefined(node.name) ?? "UNDEFINED_Module"
138
        return {
139
            kind: IDLKind.ModuleType,
140
            name: name,
141
            extendedAttributes: [ {name: "VerbatimDts", value: `"${escapeAmbientModuleContent(this.sourceFile, node)}"`}]
142
        }
143
    }
144

145
    serializeTypeAlias(node: ts.TypeAliasDeclaration): IDLTypedef | IDLFunction | IDLInterface {
146
        const name = nameOrUndefined(node.name) ?? "UNDEFINED_TYPE_NAME"
147
        if (ts.isImportTypeNode(node.type)) {
148
            let original = node.type.getText()
149
            return {
150
                kind: IDLKind.Typedef,
151
                name: name,
152
                extendedAttributes: this.computeDeprecatedExtendAttributes(node, [ { name: "VerbatimDts", value: `"${original}"` }]),
153
                type: createReferenceType(`Imported${name}`)
154
            }
155
        }
156
        if (ts.isFunctionTypeNode(node.type)) {
157
            return this.serializeFunctionType(name, node.type)
158
        }
159
        if (ts.isTypeLiteralNode(node.type)) {
160
            return this.serializeObjectType(name, node.type, node.typeParameters)
161
        }
162
        if (ts.isTupleTypeNode(node.type)) {
163
            return this.serializeTupleType(name, node.type, node.typeParameters)
164
        }
165
        if (ts.isTypeOperatorNode(node.type)) {
166
            if (ts.isTupleTypeNode(node.type.type)) {
167
                return this.serializeTupleType(name, node.type.type, node.typeParameters, true)
168
            }
169
        }
170
        this.startScope()
171
        return {
172
            kind: IDLKind.Typedef,
173
            name: name,
174
            extendedAttributes: this.computeDeprecatedExtendAttributes(node),
175
            type: this.serializeType(node.type),
176
            scope: this.endScope()
177
        }
178
    }
179

180
    heritageIdentifiers(heritage: ts.HeritageClause): ts.Identifier[] {
181
        return heritage.types.map(it => {
182
            return ts.isIdentifier(it.expression) ? it.expression : undefined
183
        }).filter(it => !!it) as ts.Identifier[]
184
    }
185

186
    baseDeclarations(heritage: ts.HeritageClause): ts.Declaration[] {
187
        return this.heritageIdentifiers(heritage)
188
            .map(it => getDeclarationsByNode(this.typeChecker, it)[0])
189
            .filter(it => !!it)
190
    }
191

192
    serializeHeritage(heritage: ts.HeritageClause): IDLType[] {
193
        return heritage.types.map(it => {
194
            const name =
195
            (ts.isIdentifier(it.expression)) ?
196
                ts.idText(it.expression) :
197
                    `NON_IDENTIFIER_HERITAGE ${asString(it)}`
198
            return createReferenceType(name)
199
        })
200
    }
201

202
    serializeInheritance(inheritance: ts.NodeArray<ts.HeritageClause> | undefined): IDLType[] {
203
        return inheritance?.map(it => this.serializeHeritage(it)).flat() ?? []
204
    }
205

206
    computeExtendedAttributes(
207
        node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeLiteralNode | ts.TupleTypeNode,
208
        typeParameters?: ts.NodeArray<ts.TypeParameterDeclaration>
209
    ): IDLExtendedAttribute[] {
210
        let entityValue: string = IDLEntity.Interface
211
        if (ts.isClassDeclaration(node)) entityValue = IDLEntity.Class
212
        else if (ts.isTypeLiteralNode(node)) entityValue = IDLEntity.Literal
213
        else if (ts.isTupleTypeNode(node)) {
214
            const isNamedTuple = node.elements.some(it => ts.isNamedTupleMember(it))
215
            entityValue = isNamedTuple ? IDLEntity.NamedTuple : IDLEntity.Tuple
216
        }
217
        const result = [ {name: "Entity", value: entityValue }]
218
        if (typeParameters) {
219
            result.push({
220
                name : "TypeParameters",
221
                value: typeParameters.map(it => identName(it.name)).join(",")})
222
        }
223
        return result
224
    }
225

226
    computeComponentExtendedAttributes(isClass: boolean, node: ts.ClassDeclaration | ts.InterfaceDeclaration): IDLExtendedAttribute[] | undefined {
227
        let result: IDLExtendedAttribute[] = this.computeExtendedAttributes(node, node.typeParameters)
228
        let name = identName(node.name)
229
        if (name && ts.isClassDeclaration(node) && isCommonMethodOrSubclass(this.typeChecker, node)) {
230
            result.push({name: "Component", value: PeerGeneratorConfig.mapComponentName(name)})
231
        }
232
        this.computeDeprecatedExtendAttributes(node, result)
233

234
        return result.length > 0 ? result : undefined
235
    }
236

237
    computeDeprecatedExtendAttributes(node: ts.Node, attributes: IDLExtendedAttribute[] = []): IDLExtendedAttribute[] | undefined {
238
        if (isDeprecatedNode(this.sourceFile,node)) {
239
            attributes.push({ name: "Deprecated" })
240
        }
241
        return attributes
242
    }
243

244

245

246
    /** Serialize a class information */
247
    serializeClass(node: ts.ClassDeclaration): IDLInterface {
248
        this.startScope()
249
        return {
250
            kind: IDLKind.Class,
251
            extendedAttributes: this.computeComponentExtendedAttributes(true, node),
252
            name: getExportedDeclarationNameByDecl(node) ?? "UNDEFINED",
253
            documentation: getDocumentation(this.sourceFile, node, this.options.docs),
254
            inheritance: this.serializeInheritance(node.heritageClauses),
255
            constructors: node.members.filter(ts.isConstructorDeclaration).map(it => this.serializeConstructor(it as ts.ConstructorDeclaration)),
256
            constants: [],
257
            properties: this.pickProperties(node.members),
258
            methods: this.pickMethods(node.members),
259
            callables: [],
260
            scope: this.endScope()
261
        }
262
    }
263

264
    pickConstructors(members: ReadonlyArray<ts.TypeElement>): IDLConstructor[] {
265
        return members.filter(ts.isConstructSignatureDeclaration)
266
            .map(it => this.serializeConstructor(it as ts.ConstructSignatureDeclaration))
267
    }
268
    pickProperties(members: ReadonlyArray<ts.TypeElement | ts.ClassElement>): IDLProperty[] {
269
        return members
270
            .filter(it => ts.isPropertySignature(it) || ts.isPropertyDeclaration(it) || this.isCommonMethodUsedAsProperty(it))
271
            .map(it => this.serializeProperty(it))
272
    }
273
    pickMethods(members: ReadonlyArray<ts.TypeElement | ts.ClassElement>): IDLMethod[] {
274
        return members
275
            .filter(it => (ts.isMethodSignature(it) || ts.isMethodDeclaration(it) || ts.isIndexSignatureDeclaration(it)) && !this.isCommonMethodUsedAsProperty(it))
276
            .map(it => this.serializeMethod(it as ts.MethodDeclaration|ts.MethodSignature))
277
    }
278
    pickCallables(members: ReadonlyArray<ts.TypeElement>): IDLFunction[] {
279
        return members.filter(ts.isCallSignatureDeclaration)
280
            .map(it => this.serializeCallable(it))
281
    }
282

283
    fakeOverrides(node: ts.InterfaceDeclaration): ts.TypeElement[] {
284
        return node.heritageClauses
285
            ?.flatMap(it => this.baseDeclarations(it))
286
            ?.flatMap(it => ts.isInterfaceDeclaration(it) ? it.members : [])
287
            ?.filter(it => !!it) ?? []
288
    }
289

290
    filterNotOverridden(overridden: Set<string>, node: ts.InterfaceDeclaration): ts.TypeElement[] {
291
        return node.members.filter(it =>
292
            it.name && ts.isIdentifier(it.name) && !overridden.has(ts.idText(it.name))
293
        )
294
    }
295

296
    membersWithFakeOverrides(node: ts.InterfaceDeclaration): ts.TypeElement[] {
297
        const result: ts.TypeElement[] = []
298
        const worklist: ts.InterfaceDeclaration[] = [node]
299
        const overridden = new Set<string>()
300
        while (worklist.length != 0) {
301
            const next = worklist.shift()!
302
            const fakeOverrides = this.filterNotOverridden(overridden, next)
303
            fakeOverrides
304
                .map(it => nameOrUndefined(it.name))
305
                .forEach(it => it ? overridden.add(it) : undefined)
306
            result.push(...fakeOverrides)
307
            const bases = next.heritageClauses
308
                ?.flatMap(it => this.baseDeclarations(it))
309
                ?.filter(it => ts.isInterfaceDeclaration(it)) as ts.InterfaceDeclaration[]
310
                ?? []
311
            worklist.push(...bases)
312
        }
313
        return result
314
    }
315

316
    // TODO: class and interface look identical, but their elements' types are different
317
    serializeInterface(node: ts.InterfaceDeclaration): IDLInterface {
318
        this.startScope()
319
        const allMembers = this.membersWithFakeOverrides(node)
320
        return {
321
            kind: IDLKind.Interface,
322
            name: getExportedDeclarationNameByDecl(node) ?? "UNDEFINED",
323
            extendedAttributes: this.computeComponentExtendedAttributes(false, node),
324
            documentation: getDocumentation(this.sourceFile, node, this.options.docs),
325
            inheritance: this.serializeInheritance(node.heritageClauses),
326
            constructors: this.pickConstructors(node.members),
327
            constants: [],
328
            properties: this.pickProperties(allMembers),
329
            methods: this.pickMethods(allMembers),
330
            callables: this.pickCallables(node.members),
331
            scope: this.endScope()
332
        }
333
    }
334

335
    serializeObjectType(name: string, node: ts.TypeLiteralNode, typeParameters?: ts.NodeArray<ts.TypeParameterDeclaration>): IDLInterface {
336
        return {
337
            kind: IDLKind.AnonymousInterface,
338
            name: name,
339
            inheritance: [],
340
            constructors: this.pickConstructors(node.members),
341
            constants: [],
342
            properties: this.pickProperties(node.members),
343
            methods: this.pickMethods(node.members),
344
            callables: this.pickCallables(node.members),
345
            extendedAttributes: this.computeExtendedAttributes(node, typeParameters),
346
        }
347
    }
348

349
    serializeTupleType(name: string, node: ts.TupleTypeNode, typeParameters?: ts.NodeArray<ts.TypeParameterDeclaration>, withOperator: boolean = false): IDLInterface {
350
        return {
351
            kind: IDLKind.TupleInterface,
352
            name: name,
353
            extendedAttributes: this.computeExtendedAttributes(node, typeParameters),
354
            inheritance: [],
355
            constants: [],
356
            constructors: [],
357
            properties: node.elements.map((it, index) => this.serializeTupleProperty(it, `value${index}`, withOperator)),
358
            methods: [],
359
            callables: [],
360
        }
361
    }
362

363
    serializeEnum(node: ts.EnumDeclaration): IDLEnum {
364
        return {
365
            kind: IDLKind.Enum,
366
            name: ts.idText(node.name),
367
            extendedAttributes: this.computeDeprecatedExtendAttributes(node),
368
            documentation: getDocumentation(this.sourceFile, node, this.options.docs),
369
            elements: node.members.filter(ts.isEnumMember)
370
                .map(it => this.serializeEnumMember(it))
371
        }
372
    }
373

374
    serializeEnumMember(node: ts.EnumMember): IDLEnumMember {
375
        let isString = false
376
        let initializer: string|number|undefined = undefined
377
        if (!node.initializer) {
378
            // Nothing
379
        } else if (ts.isStringLiteral(node.initializer)) {
380
            isString = true
381
            initializer = node.initializer.text
382
        } else if (ts.isNumericLiteral(node.initializer)) {
383
            isString = false
384
            initializer = node.initializer.text
385
        } else if (
386
            ts.isBinaryExpression(node.initializer) &&
387
            node.initializer.operatorToken.kind == ts.SyntaxKind.LessThanLessThanToken &&
388
            ts.isNumericLiteral(node.initializer.right) &&
389
            ts.isNumericLiteral(node.initializer.left)
390
        ) {
391
            isString = false
392
            initializer = (+node.initializer.left.text) << (+node.initializer.right.text)
393
            // console.log(`Computed ${node.initializer.getText(this.sourceFile)} to `, initializer)
394
        } else {
395
            isString = false
396
            initializer = node.initializer.getText(this.sourceFile)
397
            console.log("Unrepresentable enum initializer: ", initializer)
398
        }
399
        return {
400
            kind: IDLKind.EnumMember,
401
            extendedAttributes: this.computeDeprecatedExtendAttributes(node),
402
            name: nameOrUndefined(node.name)!,
403
            documentation: getDocumentation(this.sourceFile, node, this.options.docs),
404
            type: isString ? createStringType() : createNumberType(),
405
            initializer: initializer
406
        }
407
    }
408

409
    serializeFunctionType(name: string, signature: ts.SignatureDeclarationBase): IDLCallback {
410
        return {
411
            kind: IDLKind.Callback,
412
            name: name,
413
            parameters: signature.parameters.map(it => this.serializeParameter(it)),
414
            returnType: this.serializeType(signature.type),
415
        };
416
    }
417

418
    addToScope(callback: IDLEntry) {
419
        this.currentScope.push(callback)
420
    }
421

422
    isTypeParameterReference(type: ts.TypeNode): boolean {
423
        if (!ts.isTypeReferenceNode(type)) return false
424
        const name = type.typeName
425

426
        const declaration = getDeclarationsByNode(this.typeChecker, name)[0]
427
        if (!declaration) return false
428
        if (ts.isTypeParameterDeclaration(declaration)) return true
429
        return false
430
    }
431

432
    isKnownParametrizedType(type: ts.TypeNode): boolean {
433
        if (!ts.isTypeReferenceNode(type)) return false
434
        let parent = type.parent
435
        while (parent && !ts.isClassDeclaration(parent) && !ts.isInterfaceDeclaration(parent)) {
436
            parent = parent.parent
437
        }
438
        if (!parent) return false
439
        const name = identName(parent.name)
440
        return PeerGeneratorConfig.isKnownParametrized(name)
441
    }
442

443
    isKnownAmbientModuleDeclaration(type: ts.Node): boolean {
444
        if (!ts.isModuleDeclaration(type)) return false
445
        const name = identName(type)
446
        const ambientModuleNames = this.typeChecker.getAmbientModules().map(it=>it.name.replaceAll('\"',""))
447
        return name != undefined && ambientModuleNames.includes(name)
448
    }
449

450
    warn(message: string) {
451
        console.log(`WARNING: ${message}`)
452
    }
453

454
    serializeType(type: ts.TypeNode | undefined, nameSuggestion: string|undefined = undefined, createAlias = false): IDLType {
455
        if (type == undefined) return createUndefinedType() // TODO: can we have implicit types in d.ts?
456

457
        if (type.kind == ts.SyntaxKind.UndefinedKeyword ||
458
            type.kind == ts.SyntaxKind.NullKeyword ||
459
            type.kind == ts.SyntaxKind.VoidKeyword) {
460
            return createUndefinedType()
461
        }
462
        if (type.kind == ts.SyntaxKind.UnknownKeyword) {
463
            return createReferenceType("unknown")
464
        }
465
        if (type.kind == ts.SyntaxKind.AnyKeyword) {
466
            return createReferenceType("any")
467
        }
468
        if (type.kind == ts.SyntaxKind.ObjectKeyword) {
469
            return createReferenceType("object")
470
        }
471
        if (type.kind == ts.SyntaxKind.NumberKeyword) {
472
            return createNumberType()
473
        }
474
        if (type.kind == ts.SyntaxKind.BooleanKeyword) {
475
            return createBooleanType()
476
        }
477
        if (type.kind == ts.SyntaxKind.StringKeyword) {
478
            return createStringType()
479
        }
480
        if (this.isTypeParameterReference(type)) {
481
            return createTypeParameterReference(nameOrUndefined((type as ts.TypeReferenceNode).typeName) ?? "UNEXPECTED_TYPE_PARAMETER")
482
        }
483
        if (ts.isTypeReferenceNode(type)) {
484
            if (ts.isQualifiedName(type.typeName)) {
485
                let left = type.typeName.left
486
                let declaration = getDeclarationsByNode(this.typeChecker, left)
487
                if (declaration.length > 0) {
488
                    if (ts.isEnumDeclaration(declaration[0])) {
489
                        return createEnumType(left.getText())
490
                    }
491
                    if (ts.isModuleDeclaration(declaration[0])) {
492
                        let declaration = getDeclarationsByNode(this.typeChecker, type.typeName.right)
493
                        if (ts.isEnumDeclaration(declaration[0])) {
494
                            return createEnumType(identName(declaration[0].name)!)
495
                        }
496
                        throw new Error(`Unsupported module declaration: ${declaration[0].getText()}`)
497
                    }
498
                    if (ts.isClassDeclaration(declaration[0])) {
499
                        return createReferenceType(`${left.getText()}_${type.typeName.right.getText()}`)
500
                    }
501
                    throw new Error(`Not supported for now: ${type.getText(this.sourceFile)} ${asString(declaration[0])}`)
502
                }
503
            }
504
            let declaration = getDeclarationsByNode(this.typeChecker, type.typeName)
505
            if (declaration.length == 0) {
506
                let name = type.typeName.getText(type.typeName.getSourceFile())
507
                this.warn(`Do not know type ${name}`)
508
                return createReferenceType(name)
509
            }
510
            let isEnum = ts.isEnumDeclaration(declaration[0])
511
            const rawType = sanitize(getExportedDeclarationNameByNode(this.typeChecker, type.typeName))!
512
            const transformedType = typeMapper.get(rawType) ?? rawType
513
            if (rawType == "Array" || rawType == "Promise" || rawType == "Map") {
514
                return createContainerType(transformedType, type.typeArguments!.map(it => this.serializeType(it)))
515
            }
516
            if (isEnum) {
517
                return createEnumType(transformedType)
518
            }
519
            let result = createReferenceType(transformedType)
520
            if (type.typeArguments) {
521
                result.extendedAttributes = [{
522
                    name : "TypeParameters",
523
                    value: type.typeArguments!
524
                        .map(it => it.getText())
525
                        .join(",")
526
                }]
527
            }
528
            return result;
529
        }
530
        if (ts.isThisTypeNode(type)) {
531
            return createReferenceType("this")
532
        }
533
        if (ts.isArrayTypeNode(type)) {
534
            return createContainerType("sequence", [this.serializeType(type.elementType)])
535
        }
536
        if (ts.isUnionTypeNode(type)) {
537
            const union = createUnionType(type.types.map(it => this.serializeType(it)))
538
            if (createAlias) {
539
                const counter = this.compileContext.unionCounter++
540
                const name = `${nameSuggestion ?? "union"}__${counter}`
541
                const typedef = createTypedef(name, union)
542
                this.addToScope(typedef)
543
                return createReferenceType(name)
544
            }
545
            return union
546
        }
547
        if (ts.isTupleTypeNode(type)) {
548
            //TODO: template tuple not include
549
            const counter = this.compileContext.tupleCounter++
550
            const name = `${nameSuggestion ?? "anonymous_tuple_interface"}__${counter}`
551
            const literal = this.serializeTupleType(name,type)
552
            this.addToScope(literal)
553
            return createReferenceType(name)
554
        }
555
        if (ts.isParenthesizedTypeNode(type)) {
556
            return this.serializeType(type.type)
557
        }
558
        if (ts.isFunctionTypeNode(type) || ts.isConstructorTypeNode(type)) {
559
            const counter = this.compileContext.functionCounter++
560
            const name = `${nameSuggestion??"callback"}__${counter}`
561
            const callback = this.serializeFunctionType(name, type)
562
            this.addToScope(callback)
563
            return createReferenceType(name)
564
        }
565
        if (ts.isIndexedAccessTypeNode(type)) {
566
            // TODO: plain wrong.
567
            return createStringType()
568
        }
569
        if (ts.isTypeLiteralNode(type)) {
570
            const counter = this.compileContext.objectCounter++
571
            const name = `${nameSuggestion ?? "anonymous_interface"}__${counter}`
572
            const literal = this.serializeObjectType(name, type)
573
            this.addToScope(literal)
574
            return createReferenceType(name)
575
        }
576
        if (ts.isLiteralTypeNode(type)) {
577
            const literal = type.literal
578
            if (ts.isStringLiteral(literal) || ts.isNoSubstitutionTemplateLiteral(literal) || ts.isRegularExpressionLiteral(literal)) {
579
                return createStringType()
580
            }
581
            if (ts.isNumericLiteral(literal)) {
582
                return createNumberType()
583
            }
584
            if (literal.kind == ts.SyntaxKind.NullKeyword) {
585
                // TODO: Is it correct to have undefined for null?
586
                return createUndefinedType()
587
            }
588
            throw new Error(`Non-representable type: ${asString(type)}`)
589
        }
590
        if (ts.isTemplateLiteralTypeNode(type)) {
591
            return createStringType()
592
        }
593
        if (ts.isImportTypeNode(type)) {
594
            let originalText = `${type.getText(this.sourceFile)}`
595
            this.warn(`import type: ${originalText}`)
596
            let where = type.argument.getText(type.getSourceFile()).split("/").map(it => it.replaceAll("'", ""))
597
            let what = asString(type.qualifier)
598
            let typeName = `/* ${type.getText(this.sourceFile)} */ ` + sanitize(what == "default" ? "Imported" + where[where.length - 1] : "Imported" +  what)
599
            let result = createReferenceType(typeName)
600
            result.extendedAttributes = [{ name: "Import", value: originalText}]
601
            return result
602
        }
603
        if (ts.isNamedTupleMember(type)) {
604
            return this.serializeType(type.type)
605
        }
606
        throw new Error(`Unsupported ${type.getText()} ${type.kind}`)
607
    }
608

609
    isTypeParameterReferenceOfCommonMethod(type: ts.TypeNode): boolean {
610
        if (!ts.isTypeReferenceNode(type)) return false
611
        const name = type.typeName
612
        const declaration = getDeclarationsByNode(this.typeChecker, name)[0]
613
        if (!declaration) return false
614
        if (ts.isTypeParameterDeclaration(declaration)) {
615
            let parent = declaration.parent
616
            if (ts.isClassDeclaration(parent)) {
617
                return isCommonMethodOrSubclass(this.typeChecker, parent)
618
            }
619
        }
620
        return false
621
    }
622

623

624
    deduceFromComputedProperty(name: ts.PropertyName): string | undefined {
625
        if (!ts.isComputedPropertyName(name)) return undefined
626
        const expression = name.expression
627
        if (!ts.isPropertyAccessExpression(expression)) return undefined
628
        const receiver = expression.expression
629
        if (!ts.isIdentifier(receiver)) return undefined
630
        const field = expression.name
631
        if (!ts.isIdentifier(field)) return undefined
632

633
        const enumDeclaration = getDeclarationsByNode(this.typeChecker, receiver)[0]
634
        if (!enumDeclaration || !ts.isEnumDeclaration(enumDeclaration)) return undefined
635
        const enumMember = getDeclarationsByNode(this.typeChecker, field)[0]
636
        if (!enumMember || !ts.isEnumMember(enumMember)) return undefined
637
        const initializer = enumMember.initializer
638
        if (!initializer || !ts.isStringLiteral(initializer)) return undefined
639

640
        return initializer.text
641
    }
642

643
    propertyName(name: ts.PropertyName): string | undefined {
644
        return this.deduceFromComputedProperty(name) ?? nameOrUndefined(name)
645
    }
646

647
    serializeProperty(property: ts.TypeElement | ts.ClassElement): IDLProperty {
648
        if (ts.isMethodDeclaration(property) || ts.isMethodSignature(property)) {
649
            const name = asString(property.name)
650
            if (!this.isCommonMethodUsedAsProperty(property)) throw new Error("Wrong")
651
            return {
652
                kind: IDLKind.Property,
653
                name: name,
654
                extendedAttributes: this.computeDeprecatedExtendAttributes(property,[{ name: "CommonMethod" } ]),
655
                documentation: getDocumentation(this.sourceFile, property, this.options.docs),
656
                type: this.serializeType(property.parameters[0].type),
657
                isReadonly: false,
658
                isStatic: false,
659
                isOptional: false
660
            }
661
        }
662

663
        if (ts.isPropertyDeclaration(property) || ts.isPropertySignature(property)) {
664
            const name = this.propertyName(property.name)
665
            let extendedAttributes = !!property.questionToken ? [{name: 'Optional'}] : undefined
666
            return {
667
                kind: IDLKind.Property,
668
                name: name!,
669
                documentation: getDocumentation(this.sourceFile, property, this.options.docs),
670
                type: this.serializeType(property.type, name),
671
                isReadonly: isReadonly(property.modifiers),
672
                isStatic: isStatic(property.modifiers),
673
                isOptional: !!property.questionToken,
674
                extendedAttributes: this.computeDeprecatedExtendAttributes(property,extendedAttributes),
675
            }
676
        }
677
        throw new Error("Unknown")
678
    }
679

680
    serializeTupleProperty(property: ts.NamedTupleMember | ts.TypeNode, anonymousName: string = "", isReadonly: boolean = false): IDLProperty {
681
        if (ts.isNamedTupleMember(property)) {
682
            const name = this.propertyName(property.name)
683
            return {
684
                kind: IDLKind.Property,
685
                name: name!,
686
                documentation: undefined,
687
                type: this.serializeType(property.type),
688
                isReadonly: isReadonly,
689
                isStatic: false,
690
                isOptional: !!property.questionToken,
691
                extendedAttributes: undefined,
692
            }
693
        }
694
        const isOptional = ts.isOptionalTypeNode(property)
695

696
        return {
697
            kind: IDLKind.Property,
698
            name: anonymousName,
699
            documentation: undefined,
700
            type: this.serializeType(isOptional ? property.type : property),
701
            isReadonly: isReadonly,
702
            isStatic: false,
703
            isOptional: isOptional,
704
            extendedAttributes: undefined,
705
        }
706
    }
707

708
    serializeParameter(parameter: ts.ParameterDeclaration): IDLParameter {
709
        const name = nameOrUndefined(parameter.name)
710
        return {
711
            kind: IDLKind.Parameter,
712
            name: name ?? "Unexpected property name",
713
            type: this.serializeType(parameter.type, name),
714
            isVariadic: !!parameter.dotDotDotToken,
715
            isOptional: !!parameter.questionToken
716
        }
717
    }
718

719
    isCommonAttributeMethod(method: ts.MethodDeclaration|ts.MethodSignature): boolean {
720
        let parent = method.parent
721
        if (ts.isClassDeclaration(parent)) {
722
            return isCommonMethodOrSubclass(this.typeChecker, parent)
723
        }
724
        return false
725
    }
726

727
    isCommonMethodUsedAsProperty(member: ts.ClassElement | ts.TypeElement): member is (ts.MethodDeclaration | ts.MethodSignature) {
728
        return (this.options.commonToAttributes ?? true) &&
729
            (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) &&
730
            this.isCommonAttributeMethod(member) &&
731
            member.parameters.length == 1
732
    }
733

734
    /** Serialize a signature (call or construct) */
735
    serializeMethod(method: ts.MethodDeclaration | ts.MethodSignature | ts.IndexSignatureDeclaration | ts.FunctionDeclaration): IDLMethod {
736
        if (ts.isIndexSignatureDeclaration(method)) {
737
            return {
738
                kind: IDLKind.Method,
739
                name: "indexSignature",
740
                documentation: getDocumentation(this.sourceFile, method, this.options.docs),
741
                returnType: this.serializeType(method.type),
742
                extendedAttributes: this.computeDeprecatedExtendAttributes(method,[{name: 'IndexSignature' }]),
743
                isStatic: false,
744
                isOptional: false,
745
                parameters: method.parameters.map(it => this.serializeParameter(it))
746
            }
747
        }
748
        const [methodName, escapedName] = escapeMethodName(method.name!.getText(this.sourceFile))
749
        const returnType = this.serializeType(method.type)
750
        const extendedAttributes = this.liftExtendedAttributes([], returnType)
751
        if (methodName !== escapedName)
752
            extendedAttributes.push({ name: "DtsName", value: `"${methodName}"`})
753
        if (!!method.questionToken)
754
            extendedAttributes.push({name: 'Optional'})
755
        return {
756
            kind: IDLKind.Method,
757
            name: escapedName,
758
            extendedAttributes: this.computeDeprecatedExtendAttributes(method,extendedAttributes),
759
            documentation: getDocumentation(this.sourceFile, method, this.options.docs),
760
            parameters: method.parameters.map(it => this.serializeParameter(it)),
761
            returnType: returnType,
762
            isStatic: isStatic(method.modifiers),
763
            isOptional: !!method.questionToken
764
        };
765
    }
766

767
    serializeCallable(method: ts.CallSignatureDeclaration): IDLCallable {
768
        const returnType = this.serializeType(method.type)
769
        const extendedAttributes = this.liftExtendedAttributes([{name: "CallSignature"}], returnType)
770
        return {
771
            kind: IDLKind.Callable,
772
            name: "invoke",
773
            extendedAttributes: this.computeDeprecatedExtendAttributes(method, extendedAttributes),
774
            documentation: getDocumentation(this.sourceFile, method, this.options.docs),
775
            parameters: method.parameters.map(it => this.serializeParameter(it)),
776
            returnType: returnType,
777
            isStatic: false
778
        };
779
    }
780

781
    private liftExtendedAttributes(extendedAttributes: IDLExtendedAttribute[], returnType: IDLType): IDLExtendedAttribute[] {
782
        if (returnType.extendedAttributes) {
783
            // Lift return type's attributes to method level
784
            extendedAttributes.push(...returnType.extendedAttributes)
785
            returnType.extendedAttributes = undefined
786
        }
787
        return extendedAttributes
788
    }
789

790
    serializeConstructor(constr: ts.ConstructorDeclaration|ts.ConstructSignatureDeclaration): IDLConstructor {
791
        constr.parameters.forEach(it => {
792
            if (isNodePublic(it)) console.log("TODO: count public/private/protected constructor args as properties")
793
        })
794

795
        return {
796
            kind: IDLKind.Constructor,
797
            // documentation: getDocumentationComment(constr),
798
            extendedAttributes: this.computeDeprecatedExtendAttributes(constr),
799
            parameters: constr.parameters.map(it => this.serializeParameter(it)),
800
            returnType: this.serializeType(constr.type),
801
        };
802
    }
803

804
    serializeConstants(stmt: ts.VariableStatement): IDLConstant[] {
805
        return stmt.declarationList.declarations
806
            .filter(decl => decl.initializer)
807
            .map(decl => {
808
                return {
809
                    kind: IDLKind.Const,
810
                    name: nameOrNull(decl.name)!,
811
                    type: this.serializeType(decl.type),
812
                    value: decl.initializer?.getText()!,
813
                    documentation: getDocumentation(this.sourceFile, decl, this.options.docs),
814
                }})
815
    }
816
}
817

818
function sanitize(type: stringOrNone): stringOrNone {
819
    if (!type) return undefined
820
    let dotIndex = type.lastIndexOf(".")
821
    if (dotIndex >= 0) {
822
        return type.substring(dotIndex + 1)
823
    } else {
824
        return type
825
    }
826
}
827

828
function escapeMethodName(name: string) : [string, string] {
829
    if (name.startsWith("$")) return [name, name.replace("$", "dollar_")]
830
    return [name, name]
831
}
832

833
function escapeAmbientModuleContent(sourceFile: ts.SourceFile, node: ts.Node) : string {
834
    const { pos, end} = node
835
    const content = sourceFile.text.substring(pos,end)
836
    return content.replaceAll('"', "'")
837
}
838

839
function getDocumentation(sourceFile: ts.SourceFile, node: ts.Node, docsOption: string|undefined): string | undefined {
840
    switch (docsOption) {
841
        case 'all': return getComment(sourceFile, node)
842
        case 'opt': return dedupDocumentation(getComment(sourceFile, node))
843
        case 'none': case undefined:  return undefined
844
        default: throw new Error(`Unknown option docs=${docsOption}`)
845
    }
846
}
847

848
function isDeprecatedNode(sourceFile: ts.SourceFile, node: ts.Node): boolean {
849
    const docs = getComment(sourceFile,node)
850
    const comments = parse(docs)
851
    return comments.map(it => it.tags).flatMap(it => it.map(i => i.tag)).some(it => it == 'deprecated')
852

853
}
854
function dedupDocumentation(documentation: string): string {
855
    let seen: Set<string> = new Set()
856
    let firstLine = false
857
    return documentation
858
        .split('\n')
859
        .filter(it => {
860
            let t = it.trim()
861
            if (t.startsWith('/*')) {
862
                firstLine = true
863
                return true
864
            }
865
            if (t == '' || t === '*') {
866
                // skip empty line at start of a comment
867
                return !firstLine
868
            }
869
            if (t.startsWith('*/')) return true
870
            if (!seen.has(it)) {
871
                seen.add(it)
872
                firstLine = false
873
                return true
874
            }
875
            return false
876
        })
877
        .join('\n')
878
}
879

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

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

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

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