From 1d4a428284232892ae56e1926587b5c2b7cb2e5e Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Tue, 4 Dec 2018 18:48:05 +0100 Subject: [PATCH] Added a own declaration builder --- build/dtsgen/declarator.ts | 357 +++++++++++++++++++++++++++++++++++ build/dtsgen/index.ts | 90 +++++++++ build/dtsgen/test/test_01.ts | 33 ++++ build/dtsgen/test/test_02.ts | 49 +++++ 4 files changed, 529 insertions(+) create mode 100644 build/dtsgen/declarator.ts create mode 100644 build/dtsgen/index.ts create mode 100644 build/dtsgen/test/test_01.ts create mode 100644 build/dtsgen/test/test_02.ts diff --git a/build/dtsgen/declarator.ts b/build/dtsgen/declarator.ts new file mode 100644 index 00000000..98b02a90 --- /dev/null +++ b/build/dtsgen/declarator.ts @@ -0,0 +1,357 @@ +import * as ts from "typescript"; +import {SyntaxKind} from "typescript"; + +if (!Array.prototype.last){ + Array.prototype.last = function(){ + if(this.length == 0) return undefined; + return this[this.length - 1]; + }; +} + +function has_modifier(modifiers: ts.ModifiersArray | undefined, target: T) : boolean { + if(modifiers) { + for(const modifier of modifiers) + if(modifier.kind == target) + return true; + } + return false; +} + +function append_modifier(modifiers: ts.ModifiersArray | undefined, target: T) : ts.ModifiersArray { + if(has_modifier(modifiers, target)) return modifiers; + + return ts.createNodeArray([ts.createModifier(target as number), ...(modifiers || [])], (modifiers || {hasTrailingComma: false}).hasTrailingComma); +} + +function remove_modifier(modifiers: ts.ModifiersArray | undefined, target: T) : ts.ModifiersArray { + if(!has_modifier(modifiers, target)) return modifiers; + + const new_modifiers: ts.Modifier[] = []; + for(const modifier of (modifiers || [])) + if(modifier.kind != target) + new_modifiers.push(modifier); + + return new_modifiers.length == 0 ? undefined : ts.createNodeArray(new_modifiers, (modifiers || {hasTrailingComma: false}).hasTrailingComma); +} + +const has_declare = (modifiers?: ts.ModifiersArray) => has_modifier(modifiers, SyntaxKind.DeclareKeyword); +const has_private = (modifiers?: ts.ModifiersArray) => has_modifier(modifiers, SyntaxKind.PrivateKeyword); +const append_declare = (modifiers?: ts.ModifiersArray, flag: boolean = true) => flag ? append_modifier(modifiers, SyntaxKind.DeclareKeyword) : remove_modifier(modifiers, SyntaxKind.DeclareKeyword); +const append_export = (modifiers?: ts.ModifiersArray, flag: boolean = true) => flag ? append_modifier(modifiers, SyntaxKind.ExportKeyword) : remove_modifier(modifiers, SyntaxKind.ExportKeyword); + +interface StackParameter { + flag_declare: boolean, + flag_namespace: boolean + flag_class: boolean; +} + +class StackParameters implements StackParameter { + flag_declare: boolean = false; + flag_namespace: boolean = false; + flag_class: boolean = false; + + stack: StackParameter[] = []; + + recalculate() { + { + this.flag_declare = false; + for(const layer of this.stack) { + if(layer.flag_declare) { + this.flag_declare = true; + break; + } + } + } + { + this.flag_namespace = false; + for(const layer of this.stack) { + if(layer.flag_namespace) { + this.flag_namespace = true; + break; + } + } + } + { + this.flag_class = false; + for(const layer of this.stack) { + if(layer.flag_class) { + this.flag_class = true; + break; + } + } + } + } + + push(layer: StackParameter) { + this.stack.push(layer); + this.recalculate(); + } + + pop?() : StackParameter { + const result = this.stack.pop(); + if(result) + this.recalculate(); + return result; + } +} + + +const generators: {[key: number]:((settings: _Settings, stack: StackParameters, node: ts.Node) => ts.Node | undefined) | undefined} = {}; + +function _generate(settings: _Settings, stack: StackParameters, layer: ts.Node[], node: ts.Node) { + //console.log(SyntaxKind[node.kind]); + if(generators[node.kind]) { + const result = generators[node.kind](settings, stack, node); + if(result) + layer.push(result); + return; + } + + switch(node.kind) { + case SyntaxKind.Identifier: + console.log("Unknown identifier %s", (node).text); + break; + case SyntaxKind.SourceFile: /* yeah we have something */ + node.forEachChild(n => _generate(settings, stack, layer, n)); + break; + case SyntaxKind.EndOfFileToken: /* oh no, we're at the end */ + break; + default: + console.log("Unhandled type %s", SyntaxKind[node.kind]); + //node.forEachChild(n => _generate(settings, stack, layer, n)); + } +} + +export interface Settings { + remove_private?: { + field?: boolean; + method?: boolean; + } | boolean; + + log?: { + unhandled_types: boolean; + } | boolean; +} + +class _Settings implements Settings { + remove_private: { + field: boolean; + method: boolean; + } = { + field: false, + method: false + }; + + log?: { + unhandled_types: boolean; + } = { + unhandled_types: false + } +} + +function specify_settings(settings?: Settings) : _Settings { + const result: _Settings = new _Settings(); + Object.assign(result, settings); + if(!settings) + settings = {}; + + if(typeof(settings.remove_private) === "boolean") + result.remove_private = { + field: settings.remove_private, + method: settings.remove_private + }; + + if(typeof(settings.log) === "boolean") + result.log = { + unhandled_types: settings.log, + }; + + return result; +} + +export function generate(file: ts.SourceFile, settings?: Settings) : ts.Node[]{ + const layer: ts.Node[] = []; + const stack = new StackParameters(); + const _settings = specify_settings(settings); + + _generate(_settings, stack, layer, file); + + return layer; +} + +export function print(nodes: ts.Node[]) : string { + const dummy_file = ts.createSourceFile( + "dummy_file", + "", + ts.ScriptTarget.ES2016, + false, + ts.ScriptKind.TS + ); + + const printer = ts.createPrinter({ + newLine: ts.NewLineKind.LineFeed + }); + + return printer.printList( + ts.ListFormat.SpaceBetweenBraces | ts.ListFormat.MultiLine | ts.ListFormat.PreferNewLine, + nodes as any, + dummy_file + ); +} + +/* generator impl */ +generators[SyntaxKind.ModuleBlock] = (settings, stack, node: ts.ModuleBlock) => { + const layer = [] as ts.Node[]; + node.forEachChild(n => _generate(settings, stack, layer, n)); + return ts.createModuleBlock(layer as any); +}; + +generators[SyntaxKind.ModuleDeclaration] = (settings, stack, node: ts.ModuleDeclaration) => { + switch (node.flags) { + case ts.NodeFlags.Namespace: + break; + default: + //throw "flag " + node.flags + " isn't supported yet!"; /* TODO wrap with more info */ + } + + + stack.push({ + flag_declare: true, + flag_namespace: true, + flag_class: false + }); + const body = generators[node.body.kind](settings, stack, node.body) as ts.ModuleBlock; + stack.pop(); + + return ts.createModuleDeclaration(node.decorators, append_declare(node.modifiers, !stack.flag_declare), node.name, body, node.flags); +}; + + +const _generate_param_declare = (settings, stack, params: ts.NodeArray) => { + const parms: any[] = []; + for(const parm of params) + parms.push(generators[parm.kind](settings, stack, parm)); + return parms; +}; + +/* functions */ +generators[SyntaxKind.Parameter] = (settings, stack, node: ts.ParameterDeclaration) => { + return ts.createParameter(node.decorators, node.modifiers, node.dotDotDotToken, node.name, node.questionToken || (node.initializer ? ts.createToken(SyntaxKind.QuestionToken) : undefined), node.type, undefined); +}; + +generators[SyntaxKind.Constructor] = (settings, stack, node: ts.ConstructorDeclaration) => { + if(settings.remove_private.method && has_private(node.modifiers)) return; + + return ts.createConstructor(node.decorators, node.modifiers, _generate_param_declare(settings, stack, node.parameters), undefined); +}; + +generators[SyntaxKind.FunctionDeclaration] = (settings, stack, node: ts.FunctionDeclaration) => { + if(stack.flag_namespace && !has_modifier(node.modifiers, SyntaxKind.ExportKeyword)) return; + + return ts.createFunctionDeclaration(node.decorators, append_declare(node.modifiers, !stack.flag_declare), node.asteriskToken, node.name, node.typeParameters, _generate_param_declare(settings, stack, node.parameters), node.type, undefined); +}; + + +generators[SyntaxKind.MethodDeclaration] = (settings, stack, node: ts.MethodDeclaration) => { + if(settings.remove_private.method && has_private(node.modifiers)) return; + + return ts.createMethod(node.decorators, node.modifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, _generate_param_declare(settings, stack, node.parameters), node.type, undefined); +}; + +generators[SyntaxKind.GetAccessor] = (settings, stack, node: ts.GetAccessorDeclaration) => { + if(settings.remove_private.method && has_private(node.modifiers)) return; + + node = ts.createGetAccessor(node.decorators, node.modifiers, node.name, _generate_param_declare(settings, stack, node.parameters), node.type, undefined); + return ts.addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, " @ts-ignore", true); +}; +generators[SyntaxKind.SetAccessor] = (settings, stack, node: ts.SetAccessorDeclaration) => { + if(settings.remove_private.method && has_private(node.modifiers)) return; + node = ts.createSetAccessor(node.decorators, node.modifiers, node.name, _generate_param_declare(settings, stack, node.parameters), undefined); + return ts.addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, " @ts-ignore", true); +}; + +/* variables or properties */ +generators[SyntaxKind.PropertyDeclaration] = (settings, stack, node: ts.PropertyDeclaration) => { + if(settings.remove_private.field && has_private(node.modifiers)) return; + + return ts.createProperty(node.decorators, append_declare(node.modifiers, !stack.flag_declare), node.name, node.questionToken, node.type, undefined); +}; + +/* class types */ +generators[SyntaxKind.ClassDeclaration] = (settings, stack, node: ts.ClassDeclaration) => { + const members = [] as ts.Node[]; + { + stack.push({ + flag_declare: true, + flag_namespace: false, + flag_class: true + }); + + node.forEachChild(n => { + if(n.kind == SyntaxKind.Identifier) return; /* class identifier */ + if(ts.isModifier(n)) return; /* we have already class modifiers */ + + _generate(settings, stack, members, n) + }); + stack.pop(); + } + + /* + members.sort((a, b) => { + if(a.kind > b.kind) return 1; + if(a.kind < b.kind) return -1; + + if(a.kind == SyntaxKind.FunctionDeclaration) + return (b).name.escapedText.toString().localeCompare((a).name.escapedText.toString()); + + return 0; + }); + */ + + return ts.createClassDeclaration(node.decorators, append_export(append_declare(node.modifiers, !stack.flag_declare), stack.flag_namespace), node.name, node.typeParameters, node.heritageClauses, members as any); +}; + +generators[SyntaxKind.InterfaceDeclaration] = (settings, stack, node: ts.InterfaceDeclaration) => { + if(settings.remove_private.field && has_private(node.modifiers)) return; + if(stack.flag_namespace && !has_modifier(node.modifiers, SyntaxKind.ExportKeyword)) return; + + return node; +}; + +generators[SyntaxKind.VariableDeclaration] = (settings, stack, node: ts.VariableDeclaration) => { + return ts.createVariableDeclaration(node.name, node.type, undefined); +}; + +generators[SyntaxKind.VariableDeclarationList] = (settings, stack, node: ts.VariableDeclarationList) => { + const decls: any[] = []; + for(const decl of node.declarations) + decls.push(generators[SyntaxKind.VariableDeclaration](settings, stack, decl) as any); + return ts.createVariableDeclarationList(decls, node.flags); +}; + +generators[SyntaxKind.VariableStatement] = (settings, stack, node: ts.VariableStatement) => { + if(settings.remove_private.field && has_private(node.modifiers)) return; + + if(stack.flag_class) { + + } else if(stack.flag_namespace) { + if(!has_modifier(node.modifiers, SyntaxKind.ExportKeyword)) return; + } + + return ts.createVariableStatement(append_declare(node.modifiers, !stack.flag_declare), generators[node.declarationList.kind](settings, stack, node.declarationList) as any); +}; + +generators[SyntaxKind.TypeAliasDeclaration] = (settings, stack, node: ts.TypeAliasDeclaration) => { + return node; +}; + +generators[SyntaxKind.EnumMember] = (settings, stack, node: ts.EnumMember) => { + return ts.createEnumMember(node.name, undefined); +}; + +generators[SyntaxKind.EnumDeclaration] = (settings, stack, node: ts.EnumDeclaration) => { + const members: any[] = []; + for(const member of node.members) + members.push(generators[SyntaxKind.EnumMember](settings, stack, member)); + return ts.createEnumDeclaration(undefined, append_export(append_declare(node.modifiers, !stack.flag_declare), stack.flag_namespace), node.name, members); +}; \ No newline at end of file diff --git a/build/dtsgen/index.ts b/build/dtsgen/index.ts new file mode 100644 index 00000000..a09f765a --- /dev/null +++ b/build/dtsgen/index.ts @@ -0,0 +1,90 @@ +import {readFileSync, writeFileSync} from "fs"; +import {isArray, isString} from "util"; +import * as ts from "typescript"; +import * as decl from "./declarator"; +import * as glob from "glob"; +import * as path from "path"; + +let source_files: string[] = []; +let exclude_files: string[] = []; +let target_file: string = "out.d.ts"; +let verbose: boolean = false; +let config_file: string = undefined; +let base_path = process.cwd(); + +let args = process.argv.slice(2); +while(args.length > 0) { + if(args[0] == "--file") { + source_files.push(args[1]); + args = args.slice(2); + } else if(args[0] == "--exclude") { + exclude_files.push(args[1]); + args = args.slice(2); + } else if(args[0] == "--destination") { + target_file = args[1]; + args = args.slice(2); + } else if(args[0] == "-v" || args[0] == "--verbose") { + verbose = true; + args = args.slice(1); + } else if(args[0] == "-c" || args[0] == "--config") { + config_file = args[1]; + base_path = path.normalize(path.dirname(config_file)); + args = args.slice(2); + } else { + console.error("Invalid command line option %s", args[0]); + process.exit(1); + } +} + +if(config_file) { + console.log("Loading config file"); + const json = JSON.parse(readFileSync(config_file).toString()); + if(!json) { + console.error("Failed to parse config!"); + process.exit(1); + } + + if(isArray(json["source_files"])) + source_files.push(...json["source_files"]); + if(isArray(json["exclude"])) + exclude_files.push(...json["exclude"]); + if(isString(json["target_file"])) + target_file = json["target_file"]; +} + +if(verbose) { + console.log("Base path: " + base_path); + console.log("Input files:"); + for(const file of source_files) + console.log(" - " + file); + console.log("Target file: " + target_file); +} + +const negate_files: string[] = [].concat.apply([], exclude_files.map(file => glob.sync(base_path + "/" + file))).map(file => path.normalize(file)); + +let result = ""; +source_files.forEach(file => { + glob.sync(base_path + "/" + file).forEach(_file => { + _file = path.normalize(_file); + for(const n_file of negate_files) { + if(n_file == _file) { + console.log("Skipping %s", _file); + return; + } + } + + let source = ts.createSourceFile( + _file, + readFileSync(_file).toString(), + ts.ScriptTarget.ES2015, + true + ); + + console.log("Compile " + _file); + result += "\n/* File: " + _file + " */\n" + decl.print(decl.generate(source, { + remove_private: false + })); + }); +}); + +writeFileSync(base_path + "/" + target_file, result); \ No newline at end of file diff --git a/build/dtsgen/test/test_01.ts b/build/dtsgen/test/test_01.ts new file mode 100644 index 00000000..da9773ce --- /dev/null +++ b/build/dtsgen/test/test_01.ts @@ -0,0 +1,33 @@ +function test() : string { + + return "IDK!"; +} + +class C { + +} +function test2() : C { + + return undefined; +} +function test4() : D { + + return undefined; +} + +namespace T { + function C() { + + } + export function D() { + + } +} + +namespace T { + namespace Y { + function T() {} + + export function Y() {} + } +} \ No newline at end of file diff --git a/build/dtsgen/test/test_02.ts b/build/dtsgen/test/test_02.ts new file mode 100644 index 00000000..477cfc9c --- /dev/null +++ b/build/dtsgen/test/test_02.ts @@ -0,0 +1,49 @@ +class A { + constructor() {} + + a() : void{ } +} + +namespace B { + export class C { + constructor() {} + + a() : void{ } + + get c() { return undefined; } + set c(_) { } + } +} + + +declare class XXX { + private static _audioInstances; + private static _globalReplayScheduler; + private static _timeIndex; + private static _audioDestinationStream; + static initializeAudioController(): void; + speakerContext: AudioContext; + private playerState; + private audioCache; + private playingAudioCache; + private _volume; + private _codecCache; + private _timeIndex; + private _latencyBufferLength; + private _buffer_timeout; + allowBuffering: boolean; + onSpeaking: () => void; + onSilence: () => void; + constructor(); + initialize(): void; + close(): void; + playBuffer(buffer: AudioBuffer): void; + private playQueue; + private removeNode; + stopAudio(now?: boolean): void; + private testBufferQueue; + private reset_buffer_timeout; + volume: number; + private applyVolume; + codecCache(codec: number): CodecClientCache; +} \ No newline at end of file