jsstub-generator/generate.ts (659 lines of code) (raw):

/* * 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.'); }); }