| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * 'License'); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| /** |
| * Convert the typedefinitions bundled with the typescript compiler into the |
| * stub files used by NetBeans (module webcommon/javascript2.editor). |
| * |
| * Usage: |
| * - Run 'npm install' to initialize the node modules |
| * - Run 'npm run generate' to start the build process |
| * |
| * The resulting zip files are placed in the 'out' directory. The generated |
| * sources can be found in the child folders of the 'out' directory. |
| */ |
| |
| import ts from 'typescript'; |
| import process from 'process'; |
| import fs from 'fs'; |
| import JSZip from 'jszip'; |
| |
| /******************************************************************************* |
| * Datastructures to hold type data while parsing |
| ******************************************************************************/ |
| |
| class TypeData { |
| modules: Map<string,ModuleInfo> = new Map(); |
| |
| getModule(moduleName: string): ModuleInfo { |
| if(! this.modules.has(moduleName)) { |
| const mi = new ModuleInfo(); |
| this.modules.set(moduleName, mi); |
| } |
| return this.modules.get(moduleName)!; |
| } |
| } |
| |
| class ModuleInfo { |
| aliases: Map<string,ts.TypeNode> = new Map(); |
| classes: Map<string,ClassInfo> = new Map(); |
| functions: Map<string,MemberInfo> = new Map(); |
| variables: Map<string,MemberInfo> = new Map(); |
| |
| public getClass(className: string): ClassInfo { |
| if (!this.classes.has(className)) { |
| const ci = new ClassInfo(); |
| this.classes.set(className, ci); |
| } |
| return this.classes.get(className)!; |
| } |
| } |
| |
| class ClassInfo { |
| doc: string | undefined; |
| inherits: string[] = []; |
| constructorInfo: MemberInfo[] = []; |
| properties: Map<string,MemberInfo> = new Map(); |
| file: string[] = []; |
| |
| public addInterits(ancestor: string) { |
| if(this.inherits.indexOf(ancestor) < 0) { |
| this.inherits.push(ancestor); |
| } |
| } |
| |
| public addFile(file: string) { |
| if(this.file.indexOf(file) < 0) { |
| this.file.push(file); |
| } |
| } |
| } |
| |
| class MemberInfo { |
| constructor(callable: boolean, returnType: ts.TypeNode, doc: string, file: string, parameters: {name: string, type: ts.TypeNode, doc: string, optional: boolean}[] | undefined = undefined) { |
| this.callable = callable; |
| this.returnType = returnType; |
| this.doc = doc; |
| this.parameters = parameters ? parameters : []; |
| this.file = file; |
| }; |
| |
| doc: string; |
| returnType: ts.TypeNode; |
| callable: boolean; |
| parameters: {name: string, type: ts.TypeNode, doc: string, optional: boolean}[]; |
| file: string; |
| } |
| |
| class TypeInfo { |
| constructor(jsType: string, jsDocType: string[], addDoc: string | undefined = undefined) { |
| this.addDoc = addDoc; |
| this.jsdocType = jsDocType; |
| this.jsType = jsType; |
| } |
| |
| /** |
| * Original definition of the type |
| */ |
| addDoc: string | undefined; |
| /** |
| * JS mapping of the type definition |
| */ |
| jsType: string; |
| /** |
| * JSDoc mapping of the type definition |
| */ |
| jsdocType: string[] = []; |
| |
| toJsDocDeclaration(): string { |
| return this.jsdocType.length == 1 ? this.jsdocType[0] : ('(' + this.jsdocType.join(' | ') + ')'); |
| } |
| } |
| |
| /** |
| * Merge supplied arguments using the separator. False arguements are removed |
| * and not merged into the final string |
| */ |
| function join(separator: string, ...args: Array<string|undefined>) { |
| return args |
| .filter(arg => arg && arg.trim() !== '') |
| .join(separator); |
| } |
| |
| /** |
| * Extract TypeInfo from the supplied type node |
| */ |
| function toTypeName(propertyType: ts.Node | undefined, moduleInfo: ModuleInfo, parentType: string | undefined = undefined, originalDefinition: string | undefined = undefined): TypeInfo { |
| if (!propertyType) { |
| return new TypeInfo('undefined', ['undefined']); |
| } |
| let result = propertyType.getText(); |
| let arrayDeep = 0; |
| while (propertyType && propertyType.kind === ts.SyntaxKind.ArrayType) { |
| arrayDeep++; |
| propertyType = (<ts.ArrayTypeNode>propertyType).elementType; |
| } |
| if (arrayDeep > 0 && propertyType.kind === ts.SyntaxKind.TypeReference) { |
| let realPropertyType = (<ts.TypeReferenceNode>propertyType).typeName; |
| return new TypeInfo('new Array()', |
| [(realPropertyType.kind === ts.SyntaxKind.QualifiedName |
| ? realPropertyType.getText() |
| : 'text' in realPropertyType |
| ? realPropertyType.text |
| : realPropertyType) + '[]'.repeat(arrayDeep)], result); |
| } |
| if (propertyType.kind === ts.SyntaxKind.AnyKeyword) { |
| return new TypeInfo('new Object()', ['Object'], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.StringKeyword) { |
| return new TypeInfo('new String()', ['String'], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.NumberKeyword) { |
| return new TypeInfo('new Number()', ['Number'], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.UnknownKeyword) { |
| return new TypeInfo('undefined', ['Object'], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.BooleanKeyword) { |
| return new TypeInfo('new Boolean()', ['Boolean'], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.VoidKeyword) { |
| return new TypeInfo('undefined', ['undefined'], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.ObjectKeyword) { |
| return new TypeInfo('new Object()', ['Object'], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.UndefinedKeyword) { |
| return new TypeInfo('undefined', ['undefined'], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.LiteralType) { |
| return new TypeInfo(result, [result], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.BigIntKeyword) { |
| return new TypeInfo('BigInt(0)', ['BigInt'], originalDefinition); |
| } else if (parentType && propertyType.kind === ts.SyntaxKind.ThisType) { |
| return new TypeInfo('new ' + parentType + '()', [parentType], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.SymbolKeyword) { |
| return new TypeInfo('new Symbol()', ['Symbol'], originalDefinition); |
| } else if (result === 'unique symbol') { |
| return new TypeInfo('new Symbol()', ['Symbol'], originalDefinition); |
| } else if (propertyType.kind === ts.SyntaxKind.FunctionType) { |
| return new TypeInfo('new Function()', ['Function'], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind === ts.SyntaxKind.ConstructorType) { |
| return new TypeInfo('new Function()', ['Function'], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind === ts.SyntaxKind.TupleType) { |
| return new TypeInfo('new Object()', ['Object'], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind === ts.SyntaxKind.TypeLiteral) { |
| // XXXX |
| return new TypeInfo('new Object()', ['Object'], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind === ts.SyntaxKind.FirstTypeNode) { |
| return new TypeInfo('new Boolean()', ['Boolean'], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind === ts.SyntaxKind.TypeQuery) { |
| const entityName = (propertyType as ts.TypeQueryNode).exprName; |
| let typeName = 'undefined'; |
| if(entityName.kind == ts.SyntaxKind.Identifier) { |
| typeName = (entityName as ts.Identifier).text; |
| } else if (entityName.kind == ts.SyntaxKind.QualifiedName) { |
| typeName = (entityName as ts.QualifiedName).getText(); |
| } |
| return new TypeInfo('new ' + typeName + '()', [typeName], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind === ts.SyntaxKind.TypeOperator) { |
| return toTypeName((propertyType as ts.TypeOperatorNode).type, moduleInfo, parentType, originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind === ts.SyntaxKind.TypeReference) { |
| let realPropertyType = (<ts.TypeReferenceNode>propertyType).typeName; |
| if (moduleInfo.aliases.has(realPropertyType.getText())) { |
| return toTypeName( |
| moduleInfo.aliases.get(realPropertyType.getText())!, |
| moduleInfo, |
| parentType, |
| originalDefinition ? originalDefinition : realPropertyType.getText() |
| ); |
| } else { |
| const type = checker.getTypeAtLocation(propertyType); |
| const symbol = type.symbol || type.aliasSymbol; |
| let jsType = 'new Object()'; |
| let jsdocType = 'Object'; |
| if (symbol) { |
| const decls = symbol.getDeclarations() as ts.Declaration[]; |
| let referencesInterface: boolean = false; |
| decls.forEach(d => { |
| if (d.kind == ts.SyntaxKind.InterfaceDeclaration) { |
| referencesInterface = true; |
| } |
| }); |
| if (referencesInterface) { |
| jsType = 'new ' + realPropertyType.getText() + '()'; |
| jsdocType = realPropertyType.getText(); |
| if ((!moduleInfo.classes.has(jsdocType)) && (moduleInfo.aliases.has(jsdocType))) { |
| return toTypeName( |
| moduleInfo.aliases.get(jsdocType)!, |
| moduleInfo, |
| parentType, |
| originalDefinition ? originalDefinition : realPropertyType.getText() |
| ); |
| } |
| } |
| } else if (type.flags == ts.TypeFlags.Number) { |
| jsType = 'new Number()'; |
| jsdocType = 'Number'; |
| } |
| return new TypeInfo(jsType, [jsdocType], originalDefinition ? originalDefinition : realPropertyType.getText()); |
| } |
| } else if (propertyType.kind === ts.SyntaxKind.IndexedAccessType && result === 'ArrayBufferTypes[keyof ArrayBufferTypes]') { |
| return new TypeInfo('new ArrayBuffer()', ['ArrayBuffer'], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind === ts.SyntaxKind.UnionType) { |
| const types = (<ts.TypeNode[]> ((propertyType as any).types)) |
| .filter(t => t.kind !== ts.SyntaxKind.UndefinedKeyword && !(t.kind === ts.SyntaxKind.LiteralType && t.getText() === 'null') && t.kind !== ts.SyntaxKind.TypeQuery); |
| let reducedTypeInfo: TypeInfo | undefined = undefined; |
| if (types.length == 1) { |
| reducedTypeInfo = toTypeName(types[0], moduleInfo, parentType, originalDefinition ? originalDefinition : result); |
| } |
| |
| let jsdocType = (<ts.TypeNode[]> ((propertyType as any).types)) |
| .map(typeNode => toTypeName(typeNode, moduleInfo, parentType, originalDefinition ? originalDefinition : result)) |
| .map(ti => ti.jsdocType) |
| .flat(); |
| |
| if(reducedTypeInfo) { |
| return new TypeInfo(reducedTypeInfo.jsType, jsdocType, originalDefinition ? originalDefinition : result); |
| } else { |
| return new TypeInfo('new Object()', jsdocType, originalDefinition ? originalDefinition : result); |
| } |
| } else if (propertyType.kind === ts.SyntaxKind.IntersectionType) { |
| const types = (<ts.TypeNode[]> ((propertyType as any).types)) |
| .filter(t => t.kind !== ts.SyntaxKind.UndefinedKeyword && !(t.kind === ts.SyntaxKind.LiteralType && t.getText() === 'null') && t.kind !== ts.SyntaxKind.TypeQuery); |
| if (types.length == 1) { |
| return toTypeName(types[0], moduleInfo, parentType, originalDefinition ? originalDefinition : result); |
| } |
| return new TypeInfo('new Object()', ['Object'], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind == ts.SyntaxKind.ParenthesizedType) { |
| return toTypeName((propertyType as ts.ParenthesizedTypeNode).type, moduleInfo, parentType, originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind == ts.SyntaxKind.ConditionalType && parentType === 'SubtleCrypto') { |
| return toTypeName(moduleInfo.aliases.get('KeyFormat'), moduleInfo, parentType, originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind == ts.SyntaxKind.MappedType && (parentType === 'ResponseInit' || parentType === 'RequestInit' || parentType === 'PushSubscriptionJSON')) { |
| return new TypeInfo('{}', ['Object.<String,String>'], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind == ts.SyntaxKind.MappedType && (parentType === 'AudioWorkletNodeOptions')) { |
| return new TypeInfo('{}', ['Object.<String,Number>'], originalDefinition ? originalDefinition : result); |
| } else if (propertyType.kind == ts.SyntaxKind.MappedType && (parentType === 'Object')) { |
| return new TypeInfo('new Object()', ['Object'], originalDefinition ? originalDefinition : result); |
| } else { |
| console.log('Unhandled type code: ' + ts.SyntaxKind[propertyType.kind] + ' / ' + result + ' / ' + parentType); |
| return new TypeInfo('undefined', ['Object'], originalDefinition ? originalDefinition : result); |
| } |
| } |
| |
| /** |
| * Extract the string version of the member name from the property name node. |
| */ |
| function propertyNameToString(propertyName: ts.PropertyName): string { |
| switch (propertyName.kind) { |
| case ts.SyntaxKind.Identifier: return '.' + (propertyName as ts.Identifier).getText(); |
| case ts.SyntaxKind.StringLiteral: return '[' + (propertyName as ts.StringLiteral).getText() + ']'; |
| case ts.SyntaxKind.ComputedPropertyName: return (propertyName as ts.ComputedPropertyName).getText(); |
| default: 'UNHANDLED: ' + ts.SyntaxKind[propertyName.kind]; |
| } |
| return 'TS KAPUT'; |
| } |
| |
| |
| /******************************************************************************* |
| * Extract the type data from the typescript definition by running a visitor |
| * over the AST generated by the typescript parser. |
| ******************************************************************************/ |
| |
| let result = new TypeData(); |
| |
| let visit = function(sourceFile: ts.SourceFile, modulePrefix: string, iface: string) { |
| return (node: ts.Node) => { |
| switch (node.kind) { |
| // Handle type aliases so that they can be resolved after parsing is done |
| case ts.SyntaxKind.TypeAliasDeclaration: |
| result.getModule(modulePrefix).aliases.set( |
| (node as ts.TypeAliasDeclaration).name.getText(), |
| (node as ts.TypeAliasDeclaration).type |
| ); |
| break; |
| // Handle variable declarations - they are assumed to be instances |
| // declared at the module level |
| case ts.SyntaxKind.VariableStatement: |
| { |
| (<ts.VariableStatement>node).declarationList.declarations.forEach(decl => { |
| let declName = decl.name.getText(); |
| if (declName !== '.prototype' && declName !== '.constructor') { |
| let propertySymbol = checker.getSymbolAtLocation(decl); |
| let propertydoc = ''; |
| if (propertySymbol) { |
| propertydoc = ts.displayPartsToString(propertySymbol.getDocumentationComment(checker)); |
| } |
| |
| const propertyInfo = new MemberInfo( |
| false, |
| decl.type!, |
| propertydoc, |
| sourceFile.fileName |
| ); |
| |
| result.getModule(modulePrefix).variables.set(declName, propertyInfo); |
| }; |
| }); |
| } |
| break; |
| // Handle function declarations - they are assumed to be unbound |
| // functions declared at the module level |
| case ts.SyntaxKind.FunctionDeclaration: |
| if ((<ts.FunctionDeclaration>node).name) { |
| let functionName = propertyNameToString((<ts.FunctionDeclaration>node).name!); |
| let functionSymbol = ((node as any).symbol as ts.Symbol); |
| |
| const parameters: { name: string, type: ts.TypeNode, doc: string, optional: boolean }[] = []; |
| |
| for (const param of (<ts.MethodSignature>node).parameters) { |
| if (param.name.getText() == 'this') { |
| continue; |
| } |
| let paramSymbol = ((param as any).symbol) as ts.Symbol; |
| parameters.push({ |
| name: param.name.getText(), |
| type: param.type!, |
| doc: ts.displayPartsToString(paramSymbol.getDocumentationComment(checker)), |
| optional: !!param.questionToken |
| }); |
| } |
| |
| const functionInfo = new MemberInfo( |
| true, |
| (<ts.PropertySignature>node).type!, |
| ts.displayPartsToString(functionSymbol.getDocumentationComment(checker)), |
| sourceFile.fileName, |
| parameters |
| ); |
| |
| result.getModule(modulePrefix).functions.set(functionName, functionInfo); |
| } |
| break; |
| // Handle constructors |
| case ts.SyntaxKind.ConstructSignature: |
| const parameters: { name: string, type: ts.TypeNode, doc: string, optional: boolean }[] = []; |
| |
| for (const param of (<ts.ConstructSignatureDeclaration>node).parameters) { |
| if (param.name.getText() == 'this') { |
| continue; |
| } |
| let paramSymbol = ((param as any).symbol) as ts.Symbol; |
| parameters.push({ |
| name: param.name.getText(), |
| type: param.type!, |
| doc: ts.displayPartsToString(paramSymbol.getDocumentationComment(checker)), |
| optional: !!param.questionToken |
| }); |
| } |
| |
| let ifaceBase = iface; |
| if(iface.endsWith('Constructor')) { |
| ifaceBase = iface.substring(0, iface.length - 11); |
| } |
| |
| const mi = new MemberInfo( |
| true, |
| (<ts.ConstructSignatureDeclaration>node).type!, |
| '', |
| sourceFile.fileName, |
| parameters |
| ); |
| |
| result.getModule(modulePrefix).getClass(ifaceBase).addFile(sourceFile.fileName); |
| result.getModule(modulePrefix).getClass(ifaceBase).constructorInfo.push(mi); |
| |
| break; |
| // Handle methods |
| case ts.SyntaxKind.MethodSignature: |
| let functionName = propertyNameToString((<ts.MethodSignature>node).name); |
| if (functionName !== '.prototype' && functionName !== '.constructor') { |
| let functionSymbol = ((node as any).symbol as ts.Symbol); |
| |
| const parameters: { name: string, type: ts.TypeNode, doc: string, optional: boolean }[] = []; |
| |
| for (const param of (<ts.MethodSignature>node).parameters) { |
| if (param.name.getText() == 'this') { |
| continue; |
| } |
| let paramSymbol = ((param as any).symbol) as ts.Symbol; |
| parameters.push({ |
| name: param.name.getText(), |
| type: param.type!, |
| doc: ts.displayPartsToString(paramSymbol.getDocumentationComment(checker)), |
| optional: !!param.questionToken |
| }); |
| } |
| |
| const functionInfo = new MemberInfo( |
| true, |
| (<ts.PropertySignature>node).type!, |
| ts.displayPartsToString(functionSymbol.getDocumentationComment(checker)), |
| sourceFile.fileName, |
| parameters |
| ); |
| |
| result.getModule(modulePrefix).getClass(iface).addFile(sourceFile.fileName); |
| result.getModule(modulePrefix).getClass(iface).properties.set(functionName, functionInfo); |
| } |
| break; |
| // Handle properties |
| case ts.SyntaxKind.PropertySignature: |
| let propertyName = propertyNameToString((<ts.PropertySignature>node).name); |
| if (propertyName !== '.prototype' && propertyName !== '.constructor') { |
| let propertySymbol = checker.getSymbolAtLocation(node); |
| let propertydoc = ''; |
| if (propertySymbol) { |
| propertydoc = ts.displayPartsToString(propertySymbol.getDocumentationComment(checker)); |
| } |
| |
| const propertyInfo = new MemberInfo( |
| false, |
| (<ts.PropertySignature>node).type!, |
| propertydoc, |
| sourceFile.fileName |
| ); |
| |
| result.getModule(modulePrefix).getClass(iface).addFile(sourceFile.fileName); |
| result.getModule(modulePrefix).getClass(iface).properties.set(propertyName, propertyInfo); |
| } |
| break; |
| // Handle module declarations - only handled to extract the name of the module |
| case ts.SyntaxKind.ModuleDeclaration: |
| let moduleName = (<ts.ModuleDeclaration>node).name.text; |
| modulePrefix = moduleName; |
| break; |
| // Handle interface declaration |
| case ts.SyntaxKind.InterfaceDeclaration: |
| iface = (<ts.InterfaceDeclaration>node).name.text; |
| break; |
| |
| default: |
| } |
| switch (node.kind) { |
| // For modules decend into their children |
| case ts.SyntaxKind.ModuleBlock: |
| ts.forEachChild(node, visit(sourceFile, modulePrefix, iface)); |
| break; |
| case ts.SyntaxKind.ModuleDeclaration: |
| ts.forEachChild(node, visit(sourceFile, modulePrefix, iface)); |
| modulePrefix = ''; |
| break; |
| // For interfaces decend into their children and extract the inheritence tree |
| case ts.SyntaxKind.InterfaceDeclaration: |
| ts.forEachChild(node, visit(sourceFile, modulePrefix, iface)); |
| const id = node as ts.InterfaceDeclaration; |
| let idSymbol = ((id as any).symbol) as ts.Symbol; |
| let idDoc = ''; |
| if (idSymbol) { |
| idDoc = ts.displayPartsToString(idSymbol.getDocumentationComment(checker)); |
| result.getModule(modulePrefix).getClass(iface).doc = idDoc; |
| } |
| result.getModule(modulePrefix).getClass(iface).addFile(sourceFile.fileName); |
| if (id.heritageClauses) { |
| id.heritageClauses.forEach(hc => { |
| hc.types.forEach(t => result.getModule(modulePrefix).getClass(iface).addInterits(t.expression.getText())); |
| }); |
| } |
| iface = ''; |
| break; |
| } |
| } |
| }; |
| |
| // Create the TS programm from the esnext definition - that definition includes |
| // all relevant definitions |
| let program = ts.createProgram(['node_modules/typescript/lib/lib.esnext.full.d.ts'], {}); |
| let checker = program.getTypeChecker(); |
| |
| program.getSourceFiles().forEach(sourceFile => { |
| if(/node_modules\/typescript\/lib\/.*\.ts$/.test(sourceFile.fileName)) { |
| ts.forEachChild(sourceFile, visit(sourceFile, '', '')); |
| } |
| }); |
| |
| /******************************************************************************* |
| * The extracted types are iterated and converted to JS source |
| ******************************************************************************/ |
| |
| function outputMember( outputName: string, memberInfo: MemberInfo, classes: ModuleInfo, ifaceName: string | undefined = undefined): string { |
| let result = ''; |
| result += '//Source: ' + memberInfo.file + '\n'; |
| result += '/**\n'; |
| result += memberInfo.doc; |
| result += '\n\n'; |
| for (const p of memberInfo.parameters) { |
| const pTypeInfo = toTypeName(p.type, classes, ifaceName); |
| result += '@param {'; |
| result += pTypeInfo.toJsDocDeclaration(); |
| result += '} '; |
| if (p.optional) { |
| result += '['; |
| } |
| result += p.name; |
| if (p.optional) { |
| result += ']'; |
| } |
| const doc = join(' - ', pTypeInfo.addDoc, p.doc); |
| if (doc) { |
| result += ' '; |
| result += doc; |
| } |
| result += '\n'; |
| } |
| result += '@returns {'; |
| |
| let returnInfo = toTypeName(memberInfo.returnType, classes, ifaceName); |
| result += returnInfo.toJsDocDeclaration(); |
| result += '}'; |
| if (returnInfo.addDoc && returnInfo.addDoc != returnInfo.toJsDocDeclaration()) { |
| result += ' '; |
| result += returnInfo.addDoc; |
| } |
| result += '\n'; |
| result += '**/\n'; |
| result += outputName + ' = ' + (memberInfo['callable'] ? 'function(' + (memberInfo.parameters.filter(p => !p.optional).map(p => p.name).join(', ')) + ') {}' : returnInfo.jsType) + ';\n\n'; |
| return result; |
| } |
| |
| function outputClassInfo(baseName: string, classInfo: ClassInfo, moduleInfo: ModuleInfo, modules: TypeData, staticDecl: boolean, ifaceName: string): string { |
| let result = ''; |
| classInfo.properties.forEach((memberInfo, method) => { |
| const outputName = baseName + method; |
| result += outputMember(outputName, memberInfo, moduleInfo, ifaceName); |
| }); |
| |
| classInfo.inherits.forEach(ancestor => { |
| |
| if(moduleInfo.classes.has(ancestor)) { |
| result += outputClassInfo(baseName, moduleInfo.classes.get(ancestor)!, moduleInfo, modules, staticDecl, ifaceName); |
| } else if (modules.modules.has('') && modules.getModule('').classes.has(ancestor)) { |
| result += outputClassInfo(baseName, modules.getModule('').getClass(ancestor), modules.getModule(''), modules, staticDecl, ifaceName); |
| } else { |
| process.stderr.write('Ancestor not found: ' + ancestor + ' for ' + baseName + '\n'); |
| } |
| }); |
| return result; |
| } |
| |
| /** |
| * Filter the output by only outputting type originating from the subset |
| * defined in this function. Definitions that are not targetted at either core |
| * js or the web related. |
| */ |
| function doOutput(sourceFiles: string[]): string[] { |
| const result: string[] = []; |
| for(const sourceFile of sourceFiles) { |
| if(/\/typescript\/lib\/lib\.es(\d+|next)\..*d.ts/.test(sourceFile) && result.indexOf('core') < 0) { |
| result.push('core'); |
| } else if(/\/typescript\/lib\/lib\.dom\..*d.ts/.test(sourceFile) && result.indexOf('dom') < 0) { |
| result.push('dom'); |
| } else if(/\/typescript\/lib\/lib\.webworker\..*d.ts/.test(sourceFile) && result.indexOf('dom') < 0) { |
| result.push('dom'); |
| } |
| } |
| return result; |
| } |
| |
| const modulesCreated: string[] = []; |
| const objectsCreated: string[] = []; |
| |
| fs.rmdirSync('out', {recursive: true}); |
| fs.mkdirSync('out'); |
| fs.mkdirSync('out/core'); |
| fs.mkdirSync('out/dom'); |
| |
| result.modules.forEach( |
| (moduleInfo, module) => { |
| |
| if(module !== '' && modulesCreated.indexOf(module) < 0) { |
| const sources: string[] = []; |
| moduleInfo.classes.forEach((classInfo, className) => { |
| classInfo.file.forEach(sourceFile => { |
| if(sources.indexOf(sourceFile) < 0) { |
| sources.push(sourceFile); |
| } |
| }); |
| }); |
| moduleInfo.functions.forEach(memberInfo => { |
| if (sources.indexOf(memberInfo.file) < 0) { |
| sources.push(memberInfo.file); |
| } |
| }); |
| moduleInfo.variables.forEach(memberInfo => { |
| if (sources.indexOf(memberInfo.file) < 0) { |
| sources.push(memberInfo.file); |
| } |
| }); |
| |
| modulesCreated.push(module); |
| |
| const targets = doOutput(sources); |
| if(targets.length == 1) { |
| fs.appendFileSync('out/' + targets[0] + '/' + module + '._.js', module + ' = function() {};\n\n', {encoding: 'utf-8'}); |
| } else { |
| throw 'Module in multiple areas: ' + module; |
| } |
| } |
| |
| moduleInfo.variables.forEach((memberInfo, memberName) => { |
| if(moduleInfo.classes.has(memberName)) { |
| return; |
| } |
| let name = memberName; |
| if(module === '' && name.startsWith('.')) { |
| name = name.substring(1); |
| } |
| const targets = doOutput([memberInfo.file]); |
| if(targets.length > 0) { |
| fs.appendFileSync('out/' + targets[0] + '/' + module + '._.js', outputMember(module + name, memberInfo, moduleInfo)); |
| } |
| }); |
| |
| moduleInfo.functions.forEach((memberInfo, memberName) => { |
| let name = memberName; |
| if(module === '' && name.startsWith('.')) { |
| name = name.substring(1); |
| } |
| const targets = doOutput([memberInfo.file]); |
| if(targets.length > 0) { |
| fs.appendFileSync('out/' + targets[0] + '/' + module + '._.js', outputMember(module + name, memberInfo, moduleInfo)); |
| } |
| }); |
| |
| moduleInfo.classes.forEach((classInfo, iface) => { |
| let targets = doOutput(classInfo.file); |
| if(targets.length == 0) { |
| return; |
| } else if (targets.length > 1) { |
| console.log('Class in multiple areas: ' + iface); |
| if(targets.indexOf('core') >= 0) { |
| targets = ['core']; |
| } |
| } |
| let ifaceName = iface; |
| if (iface.endsWith('Constructor')) { |
| ifaceName = iface.substring(0, iface.length - 11); |
| } |
| let targetFile; |
| if(module === '') { |
| targetFile = 'out/' + targets[0] + '/_.' + ifaceName + '.js'; |
| } else { |
| targetFile = 'out/' + targets[0] + '/' + module + '.' + (ifaceName === '' ? '_' : ifaceName) + '.js'; |
| } |
| const qualifiedIfaceName = (module === '' ? '' : (module + '.')) + ifaceName; |
| if(ifaceName != '' && qualifiedIfaceName.trim() !== '' && objectsCreated.indexOf(qualifiedIfaceName) < 0) { |
| let result = ''; |
| const constructorInfos = moduleInfo.classes.get(ifaceName)!.constructorInfo |
| .filter(ci => doOutput([ci.file])) |
| .sort((a, b) => b.parameters.length - a.parameters.length); |
| if (constructorInfos.length > 0) { |
| const constructorInfo = constructorInfos[0]; |
| result += '//Source: ' + constructorInfo.file + '\n'; |
| result += '/**\n'; |
| if(moduleInfo.classes.has(ifaceName) && moduleInfo.classes.get(ifaceName)!.doc) { |
| result += moduleInfo.classes.get(ifaceName)!.doc; |
| } |
| result += '\n'; |
| if(moduleInfo.classes.has(ifaceName + 'Constructor') && moduleInfo.classes.get(ifaceName + 'Constructor')!.doc) { |
| result += moduleInfo.classes.get(ifaceName + 'Constructor')!.doc; |
| } |
| result += '\n'; |
| |
| for(const ci of constructorInfos) { |
| let nonOptional: number; |
| for(nonOptional = 0; nonOptional < ci.parameters.length; nonOptional++) { |
| if(ci.parameters[nonOptional].optional) { |
| break; |
| } |
| } |
| for(let i = nonOptional; i <= ci.parameters.length; i++) { |
| result += '@example new ' + qualifiedIfaceName + '('; |
| for(let j = 0; j < i; j++) { |
| if(j != 0) { |
| result += ', '; |
| } |
| result += ci.parameters[j].name; |
| result += ': '; |
| const paramType = toTypeName(ci.parameters[j].type, moduleInfo, qualifiedIfaceName); |
| result += paramType.addDoc ? paramType.addDoc : paramType.toJsDocDeclaration(); |
| } |
| result += ')\n'; |
| } |
| } |
| |
| result += '\n'; |
| |
| for (const p of constructorInfo.parameters) { |
| const pTypeInfo = toTypeName(p.type, moduleInfo, qualifiedIfaceName); |
| result += '@param {'; |
| result += pTypeInfo.toJsDocDeclaration(); |
| result += '} '; |
| if (p.optional) { |
| result += '['; |
| } |
| result += p.name; |
| if (p.optional) { |
| result += ']'; |
| } |
| const doc = join(' - ', pTypeInfo.addDoc, p.doc); |
| if (doc) { |
| result += ' '; |
| result += doc; |
| } |
| result += '\n'; |
| } |
| |
| result += '@returns {' + qualifiedIfaceName + '}\n'; |
| result += '**/\n'; |
| result += qualifiedIfaceName + ' = function(' + (constructorInfo.parameters.filter(p => !p.optional).map(p => p.name).join(', ')) + ') {};\n\n'; |
| } else { |
| result += '/**\n'; |
| if(moduleInfo.classes.has(ifaceName) && moduleInfo.classes.get(ifaceName)!.doc) { |
| result += moduleInfo.classes.get(ifaceName)!.doc; |
| } |
| result += '\n'; |
| if(moduleInfo.classes.has(ifaceName + 'Constructor') && moduleInfo.classes.get(ifaceName + 'Constructor')!.doc) { |
| result += moduleInfo.classes.get(ifaceName + 'Constructor')!.doc; |
| } |
| result += '\n'; |
| result += '@returns {' + qualifiedIfaceName + '}\n'; |
| result += '*/\n'; |
| result += qualifiedIfaceName + ' = function() {};\n\n'; |
| } |
| fs.appendFileSync(targetFile, result, {encoding: 'utf-8'}); |
| objectsCreated.push(qualifiedIfaceName); |
| } |
| const staticDecl = iface.endsWith('Constructor') || iface === 'Math'; |
| let baseName = |
| (module === '' ? '' : (module + '.')) |
| + (ifaceName === '' ? '' : (ifaceName + '.')) |
| + (staticDecl ? '' : 'prototype.'); |
| baseName = baseName.substring(0, baseName.length - 1); |
| |
| fs.appendFileSync(targetFile, outputClassInfo(baseName, classInfo, moduleInfo, result, staticDecl, ifaceName), {encoding: 'utf-8'}); |
| }) |
| } |
| ); |
| |
| /******************************************************************************* |
| * Bundle the generated JS sources into the final corestubs.zip file |
| ******************************************************************************/ |
| |
| for (const subdir of ['core', 'dom']) { |
| let dirent: fs.Dirent | null; |
| let zip = new JSZip(); |
| let dir = zip.folder('jsstubs'); |
| |
| const outDir = fs.opendirSync('out/' + subdir); |
| while ((dirent = outDir.readSync()) != null) { |
| const buffer = fs.readFileSync('out/' + subdir + '/' + dirent.name); |
| dir!.file(dirent.name, buffer); |
| } |
| outDir.closeSync(); |
| |
| zip |
| .generateNodeStream({ streamFiles: true, compression: 'DEFLATE' }) |
| .pipe(fs.createWriteStream('out/' + subdir + '.zip')) |
| .on('finish', function() { |
| console.log('out/' + subdir + '.zip written.'); |
| }); |
| } |