15
import * as ts from "typescript"
16
import * as path from "path"
17
import { parse } from 'comment-parser'
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,
25
asString, capitalize, getComment, getDeclarationsByNode, getExportedDeclarationNameByDecl, getExportedDeclarationNameByNode, identName, isCommonMethodOrSubclass, isNodePublic, isReadonly, isStatic, nameOrNull, nameOrNullForIdl as nameOrUndefined, stringOrNone
27
import { GenericVisitor } from "./options"
28
import { PeerGeneratorConfig } from "./peer-generation/PeerGeneratorConfig"
29
import { OptionValues } from "commander"
30
import { PrimitiveType } from "./peer-generation/DeclarationTable"
32
const typeMapper = new Map<string, string>(
34
["null", "undefined"],
35
["void", "undefined"],
37
["Array", "sequence"],
38
["string", "DOMString"],
42
["\"auto\"", "string"]
46
export class CompileContext {
53
export class IDLVisitor implements GenericVisitor<IDLEntry[]> {
54
private output: IDLEntry[] = []
55
private currentScope: IDLEntry[] = []
56
scopes: IDLEntry[][] = []
57
globalConstants: IDLConstant[] = []
58
globalFunctions: IDLMethod[] = []
61
this.scopes.push(this.currentScope)
62
this.currentScope = []
66
const result = this.currentScope
67
this.currentScope = this.scopes.pop()!
72
private sourceFile: ts.SourceFile,
73
private typeChecker: ts.TypeChecker,
74
private compileContext: CompileContext,
75
private options: OptionValues) { }
77
visitWholeFile(): IDLEntry[] {
79
ts.forEachChild(this.sourceFile, (node) => this.visit(node))
80
if (this.globalConstants.length > 0 || this.globalFunctions.length > 0) {
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,
96
makeEnumMember(name: string, value: string): IDLEnumMember {
97
return { name: name, kind: IDLKind.EnumMember, type: { name: "DOMString", kind: IDLKind.PrimitiveType }, initializer: value }
104
extendedAttributes: [ {name: "Synthetic" } ],
106
this.makeEnumMember("package", "org.openharmony.arkui"),
107
this.makeEnumMember("imports", "'./one', './two'"),
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))
123
ts.forEachChild(node, (node) => this.visit(node));
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))
136
serializeAmbientModuleDeclaration(node: ts.ModuleDeclaration): IDLModuleType {
137
const name = nameOrUndefined(node.name) ?? "UNDEFINED_Module"
139
kind: IDLKind.ModuleType,
141
extendedAttributes: [ {name: "VerbatimDts", value: `"${escapeAmbientModuleContent(this.sourceFile, node)}"`}]
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()
150
kind: IDLKind.Typedef,
152
extendedAttributes: this.computeDeprecatedExtendAttributes(node, [ { name: "VerbatimDts", value: `"${original}"` }]),
153
type: createReferenceType(`Imported${name}`)
156
if (ts.isFunctionTypeNode(node.type)) {
157
return this.serializeFunctionType(name, node.type)
159
if (ts.isTypeLiteralNode(node.type)) {
160
return this.serializeObjectType(name, node.type, node.typeParameters)
162
if (ts.isTupleTypeNode(node.type)) {
163
return this.serializeTupleType(name, node.type, node.typeParameters)
165
if (ts.isTypeOperatorNode(node.type)) {
166
if (ts.isTupleTypeNode(node.type.type)) {
167
return this.serializeTupleType(name, node.type.type, node.typeParameters, true)
172
kind: IDLKind.Typedef,
174
extendedAttributes: this.computeDeprecatedExtendAttributes(node),
175
type: this.serializeType(node.type),
176
scope: this.endScope()
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[]
186
baseDeclarations(heritage: ts.HeritageClause): ts.Declaration[] {
187
return this.heritageIdentifiers(heritage)
188
.map(it => getDeclarationsByNode(this.typeChecker, it)[0])
192
serializeHeritage(heritage: ts.HeritageClause): IDLType[] {
193
return heritage.types.map(it => {
195
(ts.isIdentifier(it.expression)) ?
196
ts.idText(it.expression) :
197
`NON_IDENTIFIER_HERITAGE ${asString(it)}`
198
return createReferenceType(name)
202
serializeInheritance(inheritance: ts.NodeArray<ts.HeritageClause> | undefined): IDLType[] {
203
return inheritance?.map(it => this.serializeHeritage(it)).flat() ?? []
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
217
const result = [ {name: "Entity", value: entityValue }]
218
if (typeParameters) {
220
name : "TypeParameters",
221
value: typeParameters.map(it => identName(it.name)).join(",")})
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)})
232
this.computeDeprecatedExtendAttributes(node, result)
234
return result.length > 0 ? result : undefined
237
computeDeprecatedExtendAttributes(node: ts.Node, attributes: IDLExtendedAttribute[] = []): IDLExtendedAttribute[] | undefined {
238
if (isDeprecatedNode(this.sourceFile,node)) {
239
attributes.push({ name: "Deprecated" })
247
serializeClass(node: ts.ClassDeclaration): IDLInterface {
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)),
257
properties: this.pickProperties(node.members),
258
methods: this.pickMethods(node.members),
260
scope: this.endScope()
264
pickConstructors(members: ReadonlyArray<ts.TypeElement>): IDLConstructor[] {
265
return members.filter(ts.isConstructSignatureDeclaration)
266
.map(it => this.serializeConstructor(it as ts.ConstructSignatureDeclaration))
268
pickProperties(members: ReadonlyArray<ts.TypeElement | ts.ClassElement>): IDLProperty[] {
270
.filter(it => ts.isPropertySignature(it) || ts.isPropertyDeclaration(it) || this.isCommonMethodUsedAsProperty(it))
271
.map(it => this.serializeProperty(it))
273
pickMethods(members: ReadonlyArray<ts.TypeElement | ts.ClassElement>): IDLMethod[] {
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))
278
pickCallables(members: ReadonlyArray<ts.TypeElement>): IDLFunction[] {
279
return members.filter(ts.isCallSignatureDeclaration)
280
.map(it => this.serializeCallable(it))
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) ?? []
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))
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)
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[]
311
worklist.push(...bases)
317
serializeInterface(node: ts.InterfaceDeclaration): IDLInterface {
319
const allMembers = this.membersWithFakeOverrides(node)
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),
328
properties: this.pickProperties(allMembers),
329
methods: this.pickMethods(allMembers),
330
callables: this.pickCallables(node.members),
331
scope: this.endScope()
335
serializeObjectType(name: string, node: ts.TypeLiteralNode, typeParameters?: ts.NodeArray<ts.TypeParameterDeclaration>): IDLInterface {
337
kind: IDLKind.AnonymousInterface,
340
constructors: this.pickConstructors(node.members),
342
properties: this.pickProperties(node.members),
343
methods: this.pickMethods(node.members),
344
callables: this.pickCallables(node.members),
345
extendedAttributes: this.computeExtendedAttributes(node, typeParameters),
349
serializeTupleType(name: string, node: ts.TupleTypeNode, typeParameters?: ts.NodeArray<ts.TypeParameterDeclaration>, withOperator: boolean = false): IDLInterface {
351
kind: IDLKind.TupleInterface,
353
extendedAttributes: this.computeExtendedAttributes(node, typeParameters),
357
properties: node.elements.map((it, index) => this.serializeTupleProperty(it, `value${index}`, withOperator)),
363
serializeEnum(node: ts.EnumDeclaration): IDLEnum {
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))
374
serializeEnumMember(node: ts.EnumMember): IDLEnumMember {
376
let initializer: string|number|undefined = undefined
377
if (!node.initializer) {
379
} else if (ts.isStringLiteral(node.initializer)) {
381
initializer = node.initializer.text
382
} else if (ts.isNumericLiteral(node.initializer)) {
384
initializer = node.initializer.text
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)
392
initializer = (+node.initializer.left.text) << (+node.initializer.right.text)
396
initializer = node.initializer.getText(this.sourceFile)
397
console.log("Unrepresentable enum initializer: ", initializer)
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
409
serializeFunctionType(name: string, signature: ts.SignatureDeclarationBase): IDLCallback {
411
kind: IDLKind.Callback,
413
parameters: signature.parameters.map(it => this.serializeParameter(it)),
414
returnType: this.serializeType(signature.type),
418
addToScope(callback: IDLEntry) {
419
this.currentScope.push(callback)
422
isTypeParameterReference(type: ts.TypeNode): boolean {
423
if (!ts.isTypeReferenceNode(type)) return false
424
const name = type.typeName
426
const declaration = getDeclarationsByNode(this.typeChecker, name)[0]
427
if (!declaration) return false
428
if (ts.isTypeParameterDeclaration(declaration)) return true
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
438
if (!parent) return false
439
const name = identName(parent.name)
440
return PeerGeneratorConfig.isKnownParametrized(name)
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)
450
warn(message: string) {
451
console.log(`WARNING: ${message}`)
454
serializeType(type: ts.TypeNode | undefined, nameSuggestion: string|undefined = undefined, createAlias = false): IDLType {
455
if (type == undefined) return createUndefinedType()
457
if (type.kind == ts.SyntaxKind.UndefinedKeyword ||
458
type.kind == ts.SyntaxKind.NullKeyword ||
459
type.kind == ts.SyntaxKind.VoidKeyword) {
460
return createUndefinedType()
462
if (type.kind == ts.SyntaxKind.UnknownKeyword) {
463
return createReferenceType("unknown")
465
if (type.kind == ts.SyntaxKind.AnyKeyword) {
466
return createReferenceType("any")
468
if (type.kind == ts.SyntaxKind.ObjectKeyword) {
469
return createReferenceType("object")
471
if (type.kind == ts.SyntaxKind.NumberKeyword) {
472
return createNumberType()
474
if (type.kind == ts.SyntaxKind.BooleanKeyword) {
475
return createBooleanType()
477
if (type.kind == ts.SyntaxKind.StringKeyword) {
478
return createStringType()
480
if (this.isTypeParameterReference(type)) {
481
return createTypeParameterReference(nameOrUndefined((type as ts.TypeReferenceNode).typeName) ?? "UNEXPECTED_TYPE_PARAMETER")
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())
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)!)
496
throw new Error(`Unsupported module declaration: ${declaration[0].getText()}`)
498
if (ts.isClassDeclaration(declaration[0])) {
499
return createReferenceType(`${left.getText()}_${type.typeName.right.getText()}`)
501
throw new Error(`Not supported for now: ${type.getText(this.sourceFile)} ${asString(declaration[0])}`)
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)
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)))
517
return createEnumType(transformedType)
519
let result = createReferenceType(transformedType)
520
if (type.typeArguments) {
521
result.extendedAttributes = [{
522
name : "TypeParameters",
523
value: type.typeArguments!
524
.map(it => it.getText())
530
if (ts.isThisTypeNode(type)) {
531
return createReferenceType("this")
533
if (ts.isArrayTypeNode(type)) {
534
return createContainerType("sequence", [this.serializeType(type.elementType)])
536
if (ts.isUnionTypeNode(type)) {
537
const union = createUnionType(type.types.map(it => this.serializeType(it)))
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)
547
if (ts.isTupleTypeNode(type)) {
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)
555
if (ts.isParenthesizedTypeNode(type)) {
556
return this.serializeType(type.type)
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)
565
if (ts.isIndexedAccessTypeNode(type)) {
567
return createStringType()
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)
576
if (ts.isLiteralTypeNode(type)) {
577
const literal = type.literal
578
if (ts.isStringLiteral(literal) || ts.isNoSubstitutionTemplateLiteral(literal) || ts.isRegularExpressionLiteral(literal)) {
579
return createStringType()
581
if (ts.isNumericLiteral(literal)) {
582
return createNumberType()
584
if (literal.kind == ts.SyntaxKind.NullKeyword) {
586
return createUndefinedType()
588
throw new Error(`Non-representable type: ${asString(type)}`)
590
if (ts.isTemplateLiteralTypeNode(type)) {
591
return createStringType()
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}]
603
if (ts.isNamedTupleMember(type)) {
604
return this.serializeType(type.type)
606
throw new Error(`Unsupported ${type.getText()} ${type.kind}`)
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)
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
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
640
return initializer.text
643
propertyName(name: ts.PropertyName): string | undefined {
644
return this.deduceFromComputedProperty(name) ?? nameOrUndefined(name)
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")
652
kind: IDLKind.Property,
654
extendedAttributes: this.computeDeprecatedExtendAttributes(property,[{ name: "CommonMethod" } ]),
655
documentation: getDocumentation(this.sourceFile, property, this.options.docs),
656
type: this.serializeType(property.parameters[0].type),
663
if (ts.isPropertyDeclaration(property) || ts.isPropertySignature(property)) {
664
const name = this.propertyName(property.name)
665
let extendedAttributes = !!property.questionToken ? [{name: 'Optional'}] : undefined
667
kind: IDLKind.Property,
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),
677
throw new Error("Unknown")
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)
684
kind: IDLKind.Property,
686
documentation: undefined,
687
type: this.serializeType(property.type),
688
isReadonly: isReadonly,
690
isOptional: !!property.questionToken,
691
extendedAttributes: undefined,
694
const isOptional = ts.isOptionalTypeNode(property)
697
kind: IDLKind.Property,
699
documentation: undefined,
700
type: this.serializeType(isOptional ? property.type : property),
701
isReadonly: isReadonly,
703
isOptional: isOptional,
704
extendedAttributes: undefined,
708
serializeParameter(parameter: ts.ParameterDeclaration): IDLParameter {
709
const name = nameOrUndefined(parameter.name)
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
719
isCommonAttributeMethod(method: ts.MethodDeclaration|ts.MethodSignature): boolean {
720
let parent = method.parent
721
if (ts.isClassDeclaration(parent)) {
722
return isCommonMethodOrSubclass(this.typeChecker, parent)
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
735
serializeMethod(method: ts.MethodDeclaration | ts.MethodSignature | ts.IndexSignatureDeclaration | ts.FunctionDeclaration): IDLMethod {
736
if (ts.isIndexSignatureDeclaration(method)) {
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' }]),
745
parameters: method.parameters.map(it => this.serializeParameter(it))
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'})
756
kind: IDLKind.Method,
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
767
serializeCallable(method: ts.CallSignatureDeclaration): IDLCallable {
768
const returnType = this.serializeType(method.type)
769
const extendedAttributes = this.liftExtendedAttributes([{name: "CallSignature"}], returnType)
771
kind: IDLKind.Callable,
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,
781
private liftExtendedAttributes(extendedAttributes: IDLExtendedAttribute[], returnType: IDLType): IDLExtendedAttribute[] {
782
if (returnType.extendedAttributes) {
784
extendedAttributes.push(...returnType.extendedAttributes)
785
returnType.extendedAttributes = undefined
787
return extendedAttributes
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")
796
kind: IDLKind.Constructor,
798
extendedAttributes: this.computeDeprecatedExtendAttributes(constr),
799
parameters: constr.parameters.map(it => this.serializeParameter(it)),
800
returnType: this.serializeType(constr.type),
804
serializeConstants(stmt: ts.VariableStatement): IDLConstant[] {
805
return stmt.declarationList.declarations
806
.filter(decl => decl.initializer)
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),
818
function sanitize(type: stringOrNone): stringOrNone {
819
if (!type) return undefined
820
let dotIndex = type.lastIndexOf(".")
822
return type.substring(dotIndex + 1)
828
function escapeMethodName(name: string) : [string, string] {
829
if (name.startsWith("$")) return [name, name.replace("$", "dollar_")]
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('"', "'")
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}`)
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')
854
function dedupDocumentation(documentation: string): string {
855
let seen: Set<string> = new Set()
856
let firstLine = false
861
if (t.startsWith('/*')) {
865
if (t == '' || t === '*') {
869
if (t.startsWith('*/')) return true