idlize

Форк
0
/
IDLVisitor.ts 
699 строк · 29.3 Кб
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 {
18
    createAnyType, createContainerType, createEnumType, createNumberType, createReferenceType, createStringType, createTypedef,
19
    createTypeParameterReference, createUndefinedType, createUnionType, getExtAttribute, IDLCallable, IDLCallback, IDLConstructor,
20
    IDLEntry, IDLEnum, IDLEnumMember, IDLExtendedAttribute, IDLFunction, IDLInterface, IDLKind, IDLMethod, IDLModuleType, IDLParameter, IDLProperty, IDLType, IDLTypedef
21
} from "./idl"
22
import {
23
    asString, capitalize, getComment, getDeclarationsByNode, getExportedDeclarationNameByDecl, getExportedDeclarationNameByNode, identName, isCommonMethodOrSubclass, isNodePublic, isReadonly, isStatic, nameOrNullForIdl as nameOrUndefined, stringOrNone
24
} from "./util"
25
import { GenericVisitor } from "./options"
26
import { PeerGeneratorConfig } from "./peer-generation/PeerGeneratorConfig"
27
import { OptionValues } from "commander"
28

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

43
export class CompileContext {
44
    functionCounter = 0
45
    objectCounter = 0
46
}
47

48
export class IDLVisitor implements GenericVisitor<IDLEntry[]> {
49
    private output: IDLEntry[] = []
50
    private currentScope:  IDLEntry[] = []
51
    scopes: IDLEntry[][] = []
52
    globalScope: IDLMethod[] = []
53

54
    startScope() {
55
        this.scopes.push(this.currentScope)
56
        this.currentScope = []
57
    }
58

59
    endScope() {
60
        const result = this.currentScope
61
        this.currentScope = this.scopes.pop()!
62
        return result
63
    }
64

65
    constructor(
66
        private sourceFile: ts.SourceFile,
67
        private typeChecker: ts.TypeChecker,
68
        private compileContext: CompileContext,
69
        private options: OptionValues) { }
70

71
    visitWholeFile(): IDLEntry[] {
72
        ts.forEachChild(this.sourceFile, (node) => this.visit(node))
73
        if (this.globalScope.length > 0) {
74
            this.output.push({
75
                kind: IDLKind.Interface,
76
                name: `GlobalScope_${path.basename(this.sourceFile.fileName).replace(".d.ts", "")}`,
77
                extendedAttributes: [ {name: "GlobalScope" } ],
78
                methods: this.globalScope,
79
                properties: [],
80
                constructors: [],
81
                callables: [],
82
                inheritance: []
83
            } as IDLInterface)
84
        }
85
        return this.output
86
    }
87

88
    /** visit nodes finding exported classes */
89
    visit(node: ts.Node) {
90
        if (ts.isClassDeclaration(node)) {
91
            this.output.push(this.serializeClass(node))
92
        } else if (ts.isInterfaceDeclaration(node)) {
93
            this.output.push(this.serializeInterface(node))
94
        } else if (ts.isModuleDeclaration(node)) {
95
            if (this.isKnownAmbientModuleDeclaration(node)) {
96
                this.output.push(this.serializeAmbientModuleDeclaration(node))
97
            } else {
98
                // This is a namespace, visit its children
99
                ts.forEachChild(node, (node) => this.visit(node));
100
            }
101

102
        } else if (ts.isEnumDeclaration(node)) {
103
            this.output.push(this.serializeEnum(node))
104
        } else if (ts.isTypeAliasDeclaration(node)) {
105
            this.output.push(this.serializeTypeAlias(node))
106
        } else if (ts.isFunctionDeclaration(node)) {
107
            this.globalScope.push(this.serializeMethod(node))
108
        }
109
    }
110

111
    serializeAmbientModuleDeclaration(node: ts.ModuleDeclaration): IDLModuleType {
112
        const name = nameOrUndefined(node.name) ?? "UNDEFINED_Module"
113
        return {
114
            kind: IDLKind.ModuleType,
115
            name: name,
116
            extendedAttributes: [ {name: "VerbatimDts", value: `"${escapeAmbientModuleContent(this.sourceFile, node)}"`}]
117
        }
118
    }
119

120
    serializeTypeAlias(node: ts.TypeAliasDeclaration): IDLTypedef | IDLFunction | IDLInterface {
121
        const name = nameOrUndefined(node.name) ?? "UNDEFINED_TYPE_NAME"
122
        if (ts.isImportTypeNode(node.type)) {
123
            let original = node.type.getText()
124
            return {
125
                kind: IDLKind.Typedef,
126
                name: name,
127
                extendedAttributes: [ { name: "VerbatimDts", value: `"${original}"` }],
128
                type: createReferenceType(`Imported${name}`)
129
            }
130
        }
131
        if (ts.isFunctionTypeNode(node.type)) {
132
            return this.serializeFunctionType(name, node.type)
133
        }
134
        if (ts.isTypeLiteralNode(node.type)) {
135
            return this.serializeObjectType(name, node.type)
136
        }
137
        return createTypedef(name, this.serializeType(node.type))
138
    }
139

140
    heritageIdentifiers(heritage: ts.HeritageClause): ts.Identifier[] {
141
        return heritage.types.map(it => {
142
            return ts.isIdentifier(it.expression) ? it.expression : undefined
143
        }).filter(it => !!it) as ts.Identifier[]
144
    }
145

146
    baseDeclarations(heritage: ts.HeritageClause): ts.Declaration[] {
147
        return this.heritageIdentifiers(heritage)
148
            .map(it => getDeclarationsByNode(this.typeChecker, it)[0])
149
            .filter(it => !!it)
150
    }
151

152
    serializeHeritage(heritage: ts.HeritageClause): IDLType[] {
153
        return heritage.types.map(it => {
154
            const name =
155
            (ts.isIdentifier(it.expression)) ?
156
                ts.idText(it.expression) :
157
                    `NON_IDENTIFIER_HERITAGE ${asString(it)}`
158
            return createReferenceType(name)
159
        })
160
    }
161

162
    serializeInheritance(inheritance: ts.NodeArray<ts.HeritageClause> | undefined): IDLType[] {
163
        return inheritance?.map(it => this.serializeHeritage(it)).flat() ?? []
164
    }
165

166
    computeExtendedAttributes(isClass: boolean, node: ts.ClassDeclaration | ts.InterfaceDeclaration): IDLExtendedAttribute[] | undefined {
167
        let result: IDLExtendedAttribute[] = []
168
        if (isClass) result.push({name: "Class"})
169
        let name = identName(node.name)
170
        if (name && ts.isClassDeclaration(node) && isCommonMethodOrSubclass(this.typeChecker, node)) {
171
            result.push({name: "Component", value: PeerGeneratorConfig.mapComponentName(name)})
172
        }
173
        if (PeerGeneratorConfig.isKnownParametrized(name)) {
174
            result.push({name: "Parametrized", value: "T"})
175
        }
176
        return result.length > 0 ? result : undefined
177
    }
178

179
    /** Serialize a class information */
180
    serializeClass(node: ts.ClassDeclaration): IDLInterface {
181
        this.startScope()
182
        const result: IDLInterface = {
183
            kind: IDLKind.Class,
184
            extendedAttributes: this.computeExtendedAttributes(true, node),
185
            name: getExportedDeclarationNameByDecl(node) ?? "UNDEFINED",
186
            documentation: getDocumentation(this.sourceFile, node, this.options.docs),
187
            inheritance: this.serializeInheritance(node.heritageClauses),
188
            constructors: node.members.filter(ts.isConstructorDeclaration).map(it => this.serializeConstructor(it as ts.ConstructorDeclaration)),
189
            properties: this.pickProperties(node.members),
190
            methods: this.pickMethods(node.members),
191
            callables: []
192
        }
193
        result.scope = this.endScope()
194
        return result
195
    }
196

197
    pickConstructors(members: ReadonlyArray<ts.TypeElement>): IDLConstructor[] {
198
        return members.filter(ts.isConstructSignatureDeclaration)
199
            .map(it => this.serializeConstructor(it as ts.ConstructSignatureDeclaration))
200
    }
201
    pickProperties(members: ReadonlyArray<ts.TypeElement | ts.ClassElement>): IDLProperty[] {
202
        return members
203
            .filter(it => ts.isPropertySignature(it) || ts.isPropertyDeclaration(it) || this.isCommonMethodUsedAsProperty(it))
204
            .map(it => this.serializeProperty(it))
205
    }
206
    pickMethods(members: ReadonlyArray<ts.TypeElement | ts.ClassElement>): IDLMethod[] {
207
        return members
208
            .filter(it => (ts.isMethodSignature(it) || ts.isMethodDeclaration(it) || ts.isIndexSignatureDeclaration(it)) && !this.isCommonMethodUsedAsProperty(it))
209
            .map(it => this.serializeMethod(it as ts.MethodDeclaration|ts.MethodSignature))
210
    }
211
    pickCallables(members: ReadonlyArray<ts.TypeElement>): IDLFunction[] {
212
        return members.filter(ts.isCallSignatureDeclaration)
213
            .map(it => this.serializeCallable(it))
214
    }
215

216
    fakeOverrides(node: ts.InterfaceDeclaration): ts.TypeElement[] {
217
        return node.heritageClauses
218
            ?.flatMap(it => this.baseDeclarations(it))
219
            ?.flatMap(it => ts.isInterfaceDeclaration(it) ? it.members : [])
220
            ?.filter(it => !!it) ?? []
221
    }
222

223
    filterNotOverridden(overridden: Set<string>, node: ts.InterfaceDeclaration): ts.TypeElement[] {
224
        return node.members.filter(it =>
225
            it.name && ts.isIdentifier(it.name) && !overridden.has(ts.idText(it.name))
226
        )
227
    }
228

229
    membersWithFakeOverrides(node: ts.InterfaceDeclaration): ts.TypeElement[] {
230
        const result: ts.TypeElement[] = []
231
        const worklist: ts.InterfaceDeclaration[] = [node]
232
        const overridden = new Set<string>()
233
        while (worklist.length != 0) {
234
            const next = worklist.shift()!
235
            const fakeOverrides = this.filterNotOverridden(overridden, next)
236
            fakeOverrides
237
                .map(it => nameOrUndefined(it.name))
238
                .forEach(it => it ? overridden.add(it) : undefined)
239
            result.push(...fakeOverrides)
240
            const bases = next.heritageClauses
241
                ?.flatMap(it => this.baseDeclarations(it))
242
                ?.filter(it => ts.isInterfaceDeclaration(it)) as ts.InterfaceDeclaration[]
243
                ?? []
244
            worklist.push(...bases)
245
        }
246
        return result
247
    }
248

249
    // TODO: class and interface look identical, but their elements' types are different
250
    serializeInterface(node: ts.InterfaceDeclaration): IDLInterface {
251
        this.startScope()
252
        const allMembers = this.membersWithFakeOverrides(node)
253
        const result: IDLInterface = {
254
            kind: IDLKind.Interface,
255
            name: getExportedDeclarationNameByDecl(node) ?? "UNDEFINED",
256
            extendedAttributes: this.computeExtendedAttributes(false, node),
257
            documentation: getDocumentation(this.sourceFile, node, this.options.docs),
258
            inheritance: this.serializeInheritance(node.heritageClauses),
259
            constructors: this.pickConstructors(node.members),
260
            properties: this.pickProperties(allMembers),
261
            methods: this.pickMethods(allMembers),
262
            callables: this.pickCallables(node.members)
263
        }
264
        result.scope = this.endScope()
265
        return result
266
    }
267

268
    serializeObjectType(name: string, node: ts.TypeLiteralNode): IDLInterface {
269
        return {
270
            kind: IDLKind.AnonymousInterface,
271
            name: name,
272
            inheritance: [],
273
            constructors: this.pickConstructors(node.members),
274
            properties: this.pickProperties(node.members),
275
            methods: this.pickMethods(node.members),
276
            callables: this.pickCallables(node.members)
277
        }
278
    }
279

280
    serializeEnum(node: ts.EnumDeclaration): IDLEnum {
281
        return {
282
            kind: IDLKind.Enum,
283
            name: ts.idText(node.name),
284
            documentation: getDocumentation(this.sourceFile, node, this.options.docs),
285
            elements: node.members.filter(ts.isEnumMember)
286
                .map(it => this.serializeEnumMember(it))
287
        }
288
    }
289

290
    serializeEnumMember(node: ts.EnumMember): IDLEnumMember {
291
        let isString = false
292
        let initializer: string|number|undefined = undefined
293
        if (!node.initializer) {
294
            // Nothing
295
        } else if (ts.isStringLiteral(node.initializer)) {
296
            isString = true
297
            initializer = node.initializer.text
298
        } else if (ts.isNumericLiteral(node.initializer)) {
299
            isString = false
300
            initializer = node.initializer.text
301
        } else if (
302
            ts.isBinaryExpression(node.initializer) &&
303
            node.initializer.operatorToken.kind == ts.SyntaxKind.LessThanLessThanToken &&
304
            ts.isNumericLiteral(node.initializer.right) &&
305
            ts.isNumericLiteral(node.initializer.left)
306
        ) {
307
            isString = false
308
            initializer = (+node.initializer.left.text) << (+node.initializer.right.text)
309
            // console.log(`Computed ${node.initializer.getText(this.sourceFile)} to `, initializer)
310
        } else {
311
            isString = false
312
            initializer = node.initializer.getText(this.sourceFile)
313
            console.log("Unrepresentable enum initializer: ", initializer)
314
        }
315
        return {
316
            kind: IDLKind.EnumMember,
317
            name: nameOrUndefined(node.name)!,
318
            type: isString ? createStringType() : createNumberType(),
319
            initializer: initializer
320
        }
321
    }
322

323
    serializeFunctionType(name: string, signature: ts.SignatureDeclarationBase): IDLCallback {
324
        return {
325
            kind: IDLKind.Callback,
326
            name: name,
327
            parameters: signature.parameters.map(it => this.serializeParameter(it)),
328
            returnType: this.serializeType(signature.type),
329
        };
330
    }
331

332
    addToScope(callback: IDLEntry) {
333
        this.currentScope.push(callback)
334
    }
335

336
    isTypeParameterReference(type: ts.TypeNode): boolean {
337
        if (!ts.isTypeReferenceNode(type)) return false
338
        const name = type.typeName
339

340
        const declaration = getDeclarationsByNode(this.typeChecker, name)[0]
341
        if (!declaration) return false
342
        if (ts.isTypeParameterDeclaration(declaration)) return true
343
        return false
344
    }
345

346
    isKnownParametrizedType(type: ts.TypeNode): boolean {
347
        if (!ts.isTypeReferenceNode(type)) return false
348
        let parent = type.parent
349
        while (parent && !ts.isClassDeclaration(parent) && !ts.isInterfaceDeclaration(parent)) {
350
            parent = parent.parent
351
        }
352
        if (!parent) return false
353
        const name = identName(parent.name)
354
        return PeerGeneratorConfig.isKnownParametrized(name)
355
    }
356

357
    isKnownAmbientModuleDeclaration(type: ts.Node): boolean {
358
        if (!ts.isModuleDeclaration(type)) return false
359
        const name = identName(type)
360
        const ambientModuleNames = this.typeChecker.getAmbientModules().map(it=>it.name.replaceAll('\"',""))
361
        return name != undefined && ambientModuleNames.includes(name)
362
    }
363

364
    warn(message: string) {
365
        console.log(`WARNING: ${message}`)
366
    }
367

368
    serializeType(type: ts.TypeNode | undefined, nameSuggestion: string|undefined = undefined): IDLType {
369
        if (type == undefined) return createUndefinedType() // TODO: can we have implicit types in d.ts?
370

371
        if (type.kind == ts.SyntaxKind.UndefinedKeyword ||
372
            type.kind == ts.SyntaxKind.NullKeyword ||
373
            type.kind == ts.SyntaxKind.VoidKeyword) {
374
            return createUndefinedType()
375
        }
376
        if (type.kind == ts.SyntaxKind.Unknown) {
377
            return createReferenceType("unknown")
378
        }
379

380
        if (type.kind == ts.SyntaxKind.NumberKeyword) {
381
            return createNumberType()
382
        }
383
        if (type.kind == ts.SyntaxKind.StringKeyword) {
384
            return createStringType()
385
        }
386
        if (ts.isUnionTypeNode(type)) {
387
            return createUnionType(
388
                type.types.map(it => this.serializeType(it))
389
            )
390
        }
391
        if (this.isTypeParameterReference(type)) {
392
            if (this.isTypeParameterReferenceOfCommonMethod(type) || this.isKnownParametrizedType(type)) {
393
                return createReferenceType("this")
394
            }
395
            return createTypeParameterReference(nameOrUndefined((type as ts.TypeReferenceNode).typeName) ?? "UNEXPECTED_TYPE_PARAMETER")
396
        }
397
        if (ts.isTypeReferenceNode(type)) {
398
            if (ts.isQualifiedName(type.typeName)) {
399
                let left = type.typeName.left
400
                let declaration = getDeclarationsByNode(this.typeChecker, left)
401
                if (declaration.length > 0) {
402
                    if (ts.isEnumDeclaration(declaration[0])) {
403
                        return createEnumType(left.getText(left.getSourceFile()))
404
                    }
405
                    if (ts.isModuleDeclaration(declaration[0])) {
406
                        let rightName = type.typeName.right.getText(left.getSourceFile())
407
                        // TODO: is it right?
408
                        return createEnumType(`${rightName}`)
409
                    }
410
                    throw new Error(`Not supported for now: ${type.getText(this.sourceFile)}`)
411
                }
412
            }
413
            let declaration = getDeclarationsByNode(this.typeChecker, type.typeName)
414
            if (declaration.length == 0) {
415
                let name = type.typeName.getText(type.typeName.getSourceFile())
416
                this.warn(`Do not know type ${name}`)
417
                return createReferenceType(name)
418
            }
419
            let isEnum = ts.isEnumDeclaration(declaration[0])
420
            const rawType = sanitize(getExportedDeclarationNameByNode(this.typeChecker, type.typeName))!
421
            const transformedType = typeMapper.get(rawType) ?? rawType
422
            if (rawType == "AnimationRange") {
423
                let typeArg = type.typeArguments![0]
424
                return createReferenceType(`AnimationRange${capitalize(typeArg.getText(this.sourceFile))}`)
425
            }
426
            if (rawType == "Array" || rawType == "Promise" || rawType == "Map") {
427
                return createContainerType(transformedType, type.typeArguments!.map(it => this.serializeType(it)))
428
            }
429
            return isEnum ? createEnumType(transformedType) : createReferenceType(transformedType)
430
        }
431
        if (ts.isArrayTypeNode(type)) {
432
            return createContainerType("sequence", [this.serializeType(type.elementType)])
433
        }
434
        if (ts.isTupleTypeNode(type)) {
435
            // TODO: handle heterogeneous types.
436
            return createContainerType("sequence", [this.serializeType(type.elements[0])])
437
        }
438
        if (ts.isParenthesizedTypeNode(type)) {
439
            return this.serializeType(type.type)
440
        }
441
        if (ts.isFunctionTypeNode(type)) {
442
            const counter = this.compileContext.functionCounter++
443
            const name = `${nameSuggestion??"callback"}__${counter}`
444
            const callback = this.serializeFunctionType(name, type)
445
            this.addToScope(callback)
446
            return createReferenceType(name)
447
        }
448
        if (ts.isIndexedAccessTypeNode(type)) {
449
            // TODO: plain wrong.
450
            return createStringType()
451
        }
452
        if (ts.isTypeLiteralNode(type)) {
453
            const counter = this.compileContext.objectCounter++
454
            const name = `${nameSuggestion ?? "anonymous_interface"}__${counter}`
455
            const literal = this.serializeObjectType(name, type)
456
            this.addToScope(literal)
457
            return createReferenceType(name)
458
        }
459
        if (ts.isLiteralTypeNode(type)) {
460
            const literal = type.literal
461
            if (ts.isStringLiteral(literal) || ts.isNoSubstitutionTemplateLiteral(literal) || ts.isRegularExpressionLiteral(literal)) {
462
                return createStringType()
463
            }
464
            if (ts.isNumericLiteral(literal)) {
465
                return createNumberType()
466
            }
467
            if (literal.kind == ts.SyntaxKind.NullKeyword) {
468
                // TODO: Is it correct to have undefined for null?
469
                return createUndefinedType()
470
            }
471
            throw new Error(`Non-representable type: ${asString(type)}`)
472
            return createAnyType("/* Non-representable literal type */ ")
473
        }
474
        if (ts.isTemplateLiteralTypeNode(type)) {
475
            return createStringType()
476
        }
477
        if (ts.isImportTypeNode(type)) {
478
            let originalText = `${type.getText(this.sourceFile)}`
479
            this.warn(`import type: ${originalText}`)
480
            let where = type.argument.getText(type.getSourceFile()).split("/").map(it => it.replaceAll("'", ""))
481
            let what = asString(type.qualifier)
482
            let typeName = `/* ${type.getText(this.sourceFile)} */ ` + sanitize(what == "default" ? "Imported" + where[where.length - 1] : "Imported" +  what)
483
            let result = createReferenceType(typeName)
484
            result.extendedAttributes = [{ name: "Import", value: originalText}]
485
            return result
486
        }
487
        if (ts.isNamedTupleMember(type)) {
488
            return this.serializeType(type.type)
489
        }
490
        // Falling back to original TS text
491
        // TODO: this doesn't work when the node is in another source file.
492
        // Such types can come from fake overrides.
493
        let rawType = type.getText(this.sourceFile)
494
        const transformedType = typeMapper.get(rawType) ?? rawType
495
        return createReferenceType(transformedType)
496
    }
497

498
    isTypeParameterReferenceOfCommonMethod(type: ts.TypeNode): boolean {
499
        if (!ts.isTypeReferenceNode(type)) return false
500
        const name = type.typeName
501
        const declaration = getDeclarationsByNode(this.typeChecker, name)[0]
502
        if (!declaration) return false
503
        if (ts.isTypeParameterDeclaration(declaration)) {
504
            let parent = declaration.parent
505
            if (ts.isClassDeclaration(parent)) {
506
                return isCommonMethodOrSubclass(this.typeChecker, parent)
507
            }
508
        }
509
        return false
510
    }
511

512

513
    deduceFromComputedProperty(name: ts.PropertyName): string | undefined {
514
        if (!ts.isComputedPropertyName(name)) return undefined
515
        const expression = name.expression
516
        if (!ts.isPropertyAccessExpression(expression)) return undefined
517
        const receiver = expression.expression
518
        if (!ts.isIdentifier(receiver)) return undefined
519
        const field = expression.name
520
        if (!ts.isIdentifier(field)) return undefined
521

522
        const enumDeclaration = getDeclarationsByNode(this.typeChecker, receiver)[0]
523
        if (!enumDeclaration || !ts.isEnumDeclaration(enumDeclaration)) return undefined
524
        const enumMember = getDeclarationsByNode(this.typeChecker, field)[0]
525
        if (!enumMember || !ts.isEnumMember(enumMember)) return undefined
526
        const initializer = enumMember.initializer
527
        if (!initializer || !ts.isStringLiteral(initializer)) return undefined
528

529
        return initializer.text
530
    }
531

532
    propertyName(name: ts.PropertyName): string | undefined {
533
        return this.deduceFromComputedProperty(name) ?? nameOrUndefined(name)
534
    }
535

536
    serializeProperty(property: ts.TypeElement | ts.ClassElement): IDLProperty {
537
        if (ts.isMethodDeclaration(property) || ts.isMethodSignature(property)) {
538
            const name = asString(property.name)
539
            if (!this.isCommonMethodUsedAsProperty(property)) throw new Error("Wrong")
540
            return {
541
                kind: IDLKind.Property,
542
                name: name,
543
                extendedAttributes: [{ name: "CommonMethod" } ],
544
                documentation: getDocumentation(this.sourceFile, property, this.options.docs),
545
                type: this.serializeType(property.parameters[0].type),
546
                isReadonly: false,
547
                isStatic: false,
548
                isOptional: false
549
            }
550
        }
551

552
        if (ts.isPropertyDeclaration(property) || ts.isPropertySignature(property)) {
553
            const name = this.propertyName(property.name)
554
            return {
555
                kind: IDLKind.Property,
556
                name: name!,
557
                documentation: getDocumentation(this.sourceFile, property, this.options.docs),
558
                type: this.serializeType(property.type, name),
559
                isReadonly: isReadonly(property.modifiers),
560
                isStatic: isStatic(property.modifiers),
561
                isOptional: !!property.questionToken,
562
                extendedAttributes: !!property.questionToken ? [{name: 'Optional'}] : undefined,
563
            }
564
        }
565
        throw new Error("Unknown")
566
    }
567

568
    serializeParameter(parameter: ts.ParameterDeclaration): IDLParameter {
569
        const name = nameOrUndefined(parameter.name)
570
        return {
571
            kind: IDLKind.Parameter,
572
            name: name ?? "Unexpected property name",
573
            type: this.serializeType(parameter.type, name),
574
            isVariadic: !!parameter.dotDotDotToken,
575
            isOptional: !!parameter.questionToken
576
        }
577
    }
578

579
    isCommonAttributeMethod(method: ts.MethodDeclaration|ts.MethodSignature): boolean {
580
        let parent = method.parent
581
        if (ts.isClassDeclaration(parent)) {
582
            return isCommonMethodOrSubclass(this.typeChecker, parent)
583
        }
584
        return false
585
    }
586

587
    isCommonMethodUsedAsProperty(member: ts.ClassElement | ts.TypeElement): member is (ts.MethodDeclaration | ts.MethodSignature) {
588
        return (this.options.commonToAttributes ?? true) &&
589
            (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) &&
590
            this.isCommonAttributeMethod(member) &&
591
            member.parameters.length == 1
592
    }
593

594
    /** Serialize a signature (call or construct) */
595
    serializeMethod(method: ts.MethodDeclaration | ts.MethodSignature | ts.IndexSignatureDeclaration | ts.FunctionDeclaration): IDLMethod {
596
        if (ts.isIndexSignatureDeclaration(method)) {
597
            return {
598
                kind: IDLKind.Method,
599
                name: "indexSignature",
600
                documentation: getDocumentation(this.sourceFile, method, this.options.docs),
601
                returnType: this.serializeType(method.type),
602
                extendedAttributes: [{name: 'IndexSignature' }],
603
                isStatic: false,
604
                parameters: method.parameters.map(it => this.serializeParameter(it))
605
            }
606
        }
607
        let [methodName, escapedName] = escapeMethodName(method.name!.getText(this.sourceFile))
608
        return {
609
            kind: IDLKind.Method,
610
            name: escapedName,
611
            extendedAttributes: (methodName != escapedName) ? [ { name: "DtsName", value: `"${methodName}"`} ] : undefined,
612
            documentation: getDocumentation(this.sourceFile, method, this.options.docs),
613
            parameters: method.parameters.map(it => this.serializeParameter(it)),
614
            returnType: this.serializeType(method.type),
615
            isStatic: isStatic(method.modifiers)
616
        };
617
    }
618

619
    serializeCallable(method: ts.CallSignatureDeclaration): IDLCallable {
620
        return {
621
            kind: IDLKind.Callable,
622
            name: "invoke",
623
            extendedAttributes: [{name: "CallSignature"}],
624
            documentation: getDocumentation(this.sourceFile, method, this.options.docs),
625
            parameters: method.parameters.map(it => this.serializeParameter(it)),
626
            returnType: this.serializeType(method.type),
627
            isStatic: false
628
        };
629
    }
630

631
    serializeConstructor(constr: ts.ConstructorDeclaration|ts.ConstructSignatureDeclaration): IDLConstructor {
632
        constr.parameters.forEach(it => {
633
            if (isNodePublic(it)) console.log("TODO: count public/private/protected constructor args as properties")
634
        })
635

636
        return {
637
            kind: IDLKind.Constructor,
638
            // documentation: getDocumentationComment(constr),
639
            parameters: constr.parameters.map(it => this.serializeParameter(it)),
640
            returnType: this.serializeType(constr.type),
641
        };
642
    }
643
}
644

645
function sanitize(type: stringOrNone): stringOrNone {
646
    if (!type) return undefined
647
    let dotIndex = type.lastIndexOf(".")
648
    if (dotIndex >= 0) {
649
        return type.substring(dotIndex + 1)
650
    } else {
651
        return type
652
    }
653
}
654

655
function escapeMethodName(name: string) : [string, string] {
656
    if (name.startsWith("$")) return [name, name.replace("$", "dollar_")]
657
    return [name, name]
658
}
659

660
function escapeAmbientModuleContent(sourceFile: ts.SourceFile, node: ts.Node) : string {
661
    const { pos, end} = node
662
    const content = sourceFile.text.substring(pos,end)
663
    return content.replaceAll('"', "'")
664
}
665

666
function getDocumentation(sourceFile: ts.SourceFile, node: ts.Node, docsOption: string): string | undefined {
667
    switch (docsOption) {
668
        case 'all': return getComment(sourceFile, node)
669
        case 'opt': return dedupDocumentation(getComment(sourceFile, node))
670
        case 'none': return undefined
671
        default: throw new Error(`Unknown option docs=${docsOption}`)
672
    }
673
}
674

675
function dedupDocumentation(documentation: string): string {
676
    let seen: Set<string> = new Set()
677
    let firstLine = false
678
    return documentation
679
        .split('\n')
680
        .filter(it => {
681
            let t = it.trim()
682
            if (t.startsWith('/*')) {
683
                firstLine = true
684
                return true
685
            }
686
            if (t == '' || t === '*') {
687
                // skip empty line at start of a comment
688
                return !firstLine
689
            }
690
            if (t.startsWith('*/')) return true
691
            if (!seen.has(it)) {
692
                seen.add(it)
693
                firstLine = false
694
                return true
695
            }
696
            return false
697
        })
698
        .join('\n')
699
}
700

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

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

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

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