Converts Closure-style JSDoc type annotations to TypeScript definition files.
The purpose of this tool is to assist developers writing TypeScript definitions on large libraries. Our goal is to generate correct type definitions in most cases, and to provide a useful starting point for manually fixing the difficult cases.
git clone git@github.com:fivetran/typescript-closure-tools.git
cd typescript-closure-tools
git submodule update --init
npm install
node definition-generator/src/main.js test/class.js class.d.ts # Run a single example
sudo npm install -g
closure2ts
[--provides symbols.tsv]
[--globals output/dir/global-declarations.d.ts]
[--input_root input/dir]
[--output_root output/dir]
[--include_private boolean]
input/dir/input-file.js output/dir/output-file.d.ts
input/dir/another-input-file.js output/dir/another-output-file.d.ts
...
--provides symbols.tsv
A tab-separated file where each row has the form file-name.js providedSymbol
If this option isn't present, we will simply look for global symbols in the input files.--globals output/dir/global-declarations.d.ts
A TypeScript declaration that will be referenced at the top of every output file.--input_root input/dir
Root of inputs, considered when computing relative paths for ///<reference path="..." />
tags.--output_root output/dir
Root of outputs, considered when computing relative paths for ///<reference path="..." />
tags.--include_private boolean
Whether to include items marked as private (@private), defaults to false.index/
Typescript definition files for the Closure librarysrc/
Typescript sourcesscripts/
Shell scripts used for converting the Closure librarytest/
Jasmine specs and example JS files used in testinglib/
Dependencies./scripts/test.sh
There are several important differences between the Closure type system and TypeScript.
In typescript, static properties of classes are inherited:
declare class SuperClass { }
declare module SuperClass {
var myStaticProperty: number;
}
declare class SubClass extends SuperClass { }
SubClass.myStaticProperty; // : number
In Closure they are not:
/** @constructor */
SuperClass = function() { };
/** @type {string} */
SuperClass.myStaticProperty;
/** @type {string} */
SuperClass.prototype.myInstanceProperty;
/** @extends {SuperClass} */
SubClass = function() { };
goog.inherits(SubClass, SuperClass);
SubClass.myStaticProperty; // undefined
To fix this, we create a common superclass without the static properties:
declare class SuperClass extends SuperClass__Class { }
declare module SuperClass {
class __Class { // fake common superclass without myStaticProperty
myInstanceProperty: string;
}
myStaticProperty: string; // static property that won't be inherited by 'extends SuperClass__Class'
}
declare class SubClass extends SubClass__Class { }
declare module SubClass {
class __Class extends SuperClass__Class { }
}
These fake superclasses don't actually exist and are present solely to simulate the behavior of
goog.inherits(SubClass, SuperClass)
in TypeScript definition files.
Calling new SuperClass__Class
or extends SuperClass__Class
in non-declaration TypeScript code
will generate a runtime error.
You should use new SuperClass
and extends SuperClass
in non-declaration TypeScript code.
In Closure, the @private
annotation is not enforced, and private type definitions, interfaces, classes and enums are
often used in public function declarations. Therefore, these private types are included in the TypeScript definition.
In typescript, the current module shadows the global namespace:
declare class MyClass;
declare module myModule {
class MyClass;
var myValue : MyClass; // Refers to myModule.MyClass, not global MyClass
This can create problems when referring to global variables that are shadowed by locals:
goog.Error = function() { }
/**
* @param {Error} e
*/
goog.f = function(e) { };
becomes:
declare module goog {
class Error { }
function f(e: Error) { }
}
Basic RequireJS support is present:
define(function(require, exports, module) {
/** @param {number} x */
function functionDeclaration(x) { }
});
becomes:
module MODULE {
functionDeclaration(x: number): void;
}
MODULE
define(function(require, exports, module) {
/** @type {number} */
var documentedSymbol = 1;
var ignoredSymbol = 2;
exports.documentedSymbol = documentedSymbol;
exports.ignoredSymbol = 2;
});
becomes:
module MODULE {
var documentedSymbol: number;
}
exports
and module
are ignoredSymbols are exported using their local name:
define(function(require, exports, module) {
/** @type {number} */
var localName = 1;
exports.exportedName = localName;
});
becomes:
module MODULE {
var localName: number;
}