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..c59c1255357f2
--- /dev/null
+++ b/src/extensions/ext.ts
@@ -0,0 +1,172 @@
+///
+///
+///
+///
+
+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'] = getName(locals[key]);
+ 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) {
+ child['ext.externName'] = sym['ext.externName'] + '.' + getName(child);
+ if(! child['ext.visited']) {
+ 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;
+ }
+
+ // write the output extern file
+ compilerHost.writeFile(externFile, writer.getText(), false);
+ 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);
+ // 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);