From 540689f6191d55e751199686ca345109185305d3 Mon Sep 17 00:00:00 2001 From: Raphael Feng Date: Thu, 7 May 2015 18:21:57 -0700 Subject: [PATCH 1/2] Generate google closurer compiler externs from type file If using any external libraries in you javascript code, you need to declare those APIs in the externs file, so that the Google Closure Compiler will not rename the symbols of the APIs. https://developers.google.com/closure/compiler/docs/api-tutorial3#externs Add an ext.ts to generate the externs file from typescript type file(http://www.typescriptlang.org/Handbook#writing-dts-files) to make typescript work better with google closure compiler. To compile the ext.ts: tsc ext.ts --out ext.js To generate externs file: ./ext a.d.ts a.d.externs --- src/extensions/ext | 2 + src/extensions/ext.ts | 150 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100755 src/extensions/ext create mode 100644 src/extensions/ext.ts diff --git a/src/extensions/ext b/src/extensions/ext new file mode 100755 index 0000000000000..d11141e5fffe1 --- /dev/null +++ b/src/extensions/ext @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./ext.js') diff --git a/src/extensions/ext.ts b/src/extensions/ext.ts new file mode 100644 index 0000000000000..9fce72e6c03e6 --- /dev/null +++ b/src/extensions/ext.ts @@ -0,0 +1,150 @@ +/// +/// +/// +/// + +module ts { + export module ext { + declare var require: any; + + class ExternWriter { + private output: string = ""; + + public writeLine(str: string): void { + this.output += str + '\n'; + } + + public getText(): string { + return this.output; + } + } + + var fs = require('fs'); + var writer = new ExternWriter(); + var checker: TypeChecker = null; + + export function execCmdLine(args: string[]) { + var commandLine = parseCommandLine(args); + var fileNames = commandLine.fileNames; + + if (! fileNames || fileNames.length < 1) { + printUsage(); + return; + } + + if (fileNames.length == 1) { + let fileName = fileNames[0]; + let lastDotIndex = fileName.lastIndexOf('.'); + fileNames.push(fileName.substring(0, lastDotIndex) + '.externs'); + } + + exportExterns(commandLine.fileNames); + } + + function printUsage(): void { + console.log("Usage: ext [file ...]"); + console.log("Example: ext a.d.ts a.externs"); + } + + function exportExterns(fileNames: string[]) { + var exportClassAndInterface = false; + var typeFile = fileNames[0]; + var externFile = fileNames[1]; + console.log('generating ' + externFile + ' from ' + typeFile); + + var compilerOptions: CompilerOptions = { + target: ScriptTarget.ES3, + module: ModuleKind.None + }; + + var compilerHost = ts.createCompilerHost(compilerOptions); + var program = ts.createProgram([typeFile], compilerOptions, compilerHost); + program.emit(); + + var sourceFile = program.getSourceFile(typeFile); + checker = ts.createTypeChecker(program, true); + var stack: ts.Symbol[] = []; + var locals = sourceFile.locals; + for (let key in locals) { + locals[key]['ext.externName'] = locals[key].getName(); + stack.push(locals[key]); + } + + while (stack.length != 0) { + var sym = stack.pop(); + var children = getChildren(sym); + if (children.length == 0) { + visit(sym); + } + else { + for (var child of children) { + if(! child['ext.visited']) { + child['ext.externName'] = sym['ext.externName'] + '.' + child.getName(); + stack.push(child); + } + } + } + sym['ext.visited'] = true; + } + + // write the output extern file + compilerHost.writeFile(externFile, writer.getText(), false); + console.log(writer.getText()); + } + + function getChildren(sym: Symbol): Symbol[] { + // exports + var children = checker.getExportsOfModule(sym); + // members + if (sym.members) { + for (let name in sym.members) { + children.push(sym.members[name]); + } + } + + // a variable or property with a class or interface + var type: Type = sym.valueDeclaration ? + checker.getTypeOfSymbolAtLocation(sym, sym.valueDeclaration) : + null; + if (sym.valueDeclaration && sym.valueDeclaration['type'] && (sym.valueDeclaration['type']['kind'] === SyntaxKind.ArrayType)) { + // hack way to get the element type of array + // because the checker.getSymbolType return an empty array without element type + type = checker.getTypeAtLocation(sym.valueDeclaration['type']['elementType']) + } + if ((sym.getFlags() & (SymbolFlags.Variable | SymbolFlags.Property)) && + (type && (type.flags & TypeFlags.Class || type.flags & TypeFlags.Interface))) { + var properties = checker.getPropertiesOfType(type); + for (let prop of properties) { + let isPublic = true; + if (prop.valueDeclaration.modifiers) { + for (let modifier of prop.valueDeclaration.modifiers) { + if (modifier.kind === SyntaxKind.PrivateKeyword || + modifier.kind === SyntaxKind.ProtectedKeyword) { + isPublic = false; + } + } + } + if (isPublic) { + children.push(prop); + } + } + } + + return children; + } + + function visit(sym: Symbol): void { + // properties, members with primitive types + //var fullName = checker.getFullyQualifiedName(sym); + if (! (sym.flags & SymbolFlags.Prototype)) { + //writer.writeLine(fullName); + writer.writeLine(sym['ext.externName']); + } + } + + function fileExists(path: string) { + return fs.existsSync(path) && fs.statSync(path).isFile(); + } + } +} +ts.ext.execCmdLine(ts.sys.args); From 1f46f1c2dd1d742e3034d5e3d00f128ec0b6f24a Mon Sep 17 00:00:00 2001 From: Raphael Feng Date: Fri, 8 May 2015 00:31:58 -0700 Subject: [PATCH 2/2] Support the Export = case Refer to http://www.typescriptlang.org/Handbook#modules-export- for the case output the exported name instead of "export =". --- src/extensions/ext.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/extensions/ext.ts b/src/extensions/ext.ts index 9fce72e6c03e6..c59c1255357f2 100644 --- a/src/extensions/ext.ts +++ b/src/extensions/ext.ts @@ -66,7 +66,7 @@ module ts { var stack: ts.Symbol[] = []; var locals = sourceFile.locals; for (let key in locals) { - locals[key]['ext.externName'] = locals[key].getName(); + locals[key]['ext.externName'] = getName(locals[key]); stack.push(locals[key]); } @@ -78,10 +78,19 @@ module ts { } else { for (var child of children) { + child['ext.externName'] = sym['ext.externName'] + '.' + getName(child); if(! child['ext.visited']) { - child['ext.externName'] = sym['ext.externName'] + '.' + child.getName(); stack.push(child); } + else { + // still visit it as it's part of the parent symbol + // for example: + // declare var angular: ng.IAngularStatic; + // interface IAngularStatic { + // config: () => void; + // } + visit(child); + } } } sym['ext.visited'] = true; @@ -92,6 +101,19 @@ module ts { console.log(writer.getText()); } + function getName(sym: Symbol): string { + // for the Export = case + // declare angular { + // export = angular; + // } + if (sym.declarations && sym.declarations[0] && + sym.declarations[0]['isExportEquals']) { + return sym.declarations[0]['expressions']['text']; + } + + return sym.getName(); + } + function getChildren(sym: Symbol): Symbol[] { // exports var children = checker.getExportsOfModule(sym);