Skip to content

Commit

Permalink
initial ts2.1 support
Browse files Browse the repository at this point in the history
TypeScript 2.1 changed some APIs (which was easy enough to handle)
but it appears the representation of types is pretty different, so
I'm leaving that for a later change.

This change is enough to get tsickle to *compile* with TypeScript 2.1.
A bunch of type-related code is commented out (marked with "TODO(ts2.1)")
and many of the tests fail.

Initial work on #295.
  • Loading branch information
evmar committed Jan 6, 2017
1 parent 6b573b7 commit d25b3b8
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 52 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"merge2": "^1.0.2",
"temp": "^0.8.1",
"tslint": "^3.15.1",
"typescript": "^2.0.0"
"typescript": "^2.1.0"
},
"scripts": {
"prepublish": "gulp compile",
Expand Down
40 changes: 22 additions & 18 deletions src/tsickle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ export function formatDiagnostics(diags: ts.Diagnostic[]): string {
.join('\n');
}

/** @return true if node has the specified modifier flag set. */
function hasModifierFlag(node: ts.Node, flag: ts.ModifierFlags): boolean {
return (ts.getCombinedModifierFlags(node) & flag) !== 0;
}

/**
* TypeScript allows you to write identifiers quoted, like:
* interface Foo {
Expand Down Expand Up @@ -123,12 +128,14 @@ function getParameterName(param: ts.ParameterDeclaration, index: number): string
// ignored anyway.
return `__${index}`;
default:
// The above list of kinds should be exhaustive.
throw new Error(`unhandled function parameter kind: ${ts.SyntaxKind[param.name.kind]}`);
// The above list of kinds is exhaustive. param.name is 'never' at this point.
let paramName = param.name as ts.Node;
throw new Error(`unhandled function parameter kind: ${ts.SyntaxKind[paramName.kind]}`);
}
}

const VISIBILITY_FLAGS = ts.NodeFlags.Private | ts.NodeFlags.Protected | ts.NodeFlags.Public;
const VISIBILITY_FLAGS: ts.ModifierFlags =
ts.ModifierFlags.Private | ts.ModifierFlags.Protected | ts.ModifierFlags.Public;

/**
* A Rewriter subclass that adds Tsickle-specific (Closure translation) functionality.
Expand Down Expand Up @@ -183,7 +190,7 @@ class ClosureRewriter extends Rewriter {
}

// Add @abstract on "abstract" declarations.
if (fnDecl.flags & ts.NodeFlags.Abstract) {
if (hasModifierFlag(fnDecl, ts.ModifierFlags.Abstract)) {
newDoc.push({tagName: 'abstract'});
}

Expand Down Expand Up @@ -399,7 +406,7 @@ class Annotator extends ClosureRewriter {
* emit it as is and visit its children.
*/
maybeProcess(node: ts.Node): boolean {
if ((node.flags & ts.NodeFlags.Ambient) || isDtsFileName(this.file.fileName)) {
if ((hasModifierFlag(node, ts.ModifierFlags.Ambient)) || isDtsFileName(this.file.fileName)) {
this.externsWriter.visit(node);
// An ambient declaration declares types for TypeScript's benefit, so we want to skip Tsickle
// conversion of its contents.
Expand Down Expand Up @@ -485,7 +492,7 @@ class Annotator extends ClosureRewriter {
let fnDecl = <ts.FunctionLikeDeclaration>node;

if (!fnDecl.body) {
if ((fnDecl.flags & ts.NodeFlags.Abstract) !== 0) {
if (hasModifierFlag(fnDecl, ts.ModifierFlags.Abstract)) {
this.emitFunctionType([fnDecl]);
// Abstract functions look like
// abstract foo();
Expand Down Expand Up @@ -712,7 +719,7 @@ class Annotator extends ClosureRewriter {

private visitClassDeclaration(classDecl: ts.ClassDeclaration) {
let jsDoc = this.getJSDoc(classDecl) || [];
if ((classDecl.flags & ts.NodeFlags.Abstract) !== 0) {
if (hasModifierFlag(classDecl, ts.ModifierFlags.Abstract)) {
jsDoc.push({tagName: 'abstract'});
}
this.emit('\n');
Expand Down Expand Up @@ -742,7 +749,7 @@ class Annotator extends ClosureRewriter {
if (sym.flags & ts.SymbolFlags.Value) return;

this.emit(`\n/** @record */\n`);
if (iface.flags & ts.NodeFlags.Export) this.emit('export ');
if (hasModifierFlag(iface, ts.ModifierFlags.Export)) this.emit('export ');
let name = getIdentifierText(iface.name);
this.emit(`function ${name}() {}\n`);
if (iface.typeParameters) {
Expand Down Expand Up @@ -775,7 +782,7 @@ class Annotator extends ClosureRewriter {
ctors.push(member as ts.ConstructorDeclaration);
} else if (member.kind === ts.SyntaxKind.PropertyDeclaration) {
let prop = member as ts.PropertyDeclaration;
let isStatic = (prop.flags & ts.NodeFlags.Static) !== 0;
let isStatic = hasModifierFlag(prop, ts.ModifierFlags.Static);
if (isStatic) {
staticProps.push(prop);
} else {
Expand All @@ -786,7 +793,7 @@ class Annotator extends ClosureRewriter {

if (ctors.length > 0) {
let ctor = ctors[0];
paramProps = ctor.parameters.filter((p) => !!(p.flags & VISIBILITY_FLAGS));
paramProps = ctor.parameters.filter(p => hasModifierFlag(p, VISIBILITY_FLAGS));
}

if (nonStaticProps.length === 0 && paramProps.length === 0 && staticProps.length === 0) {
Expand Down Expand Up @@ -851,7 +858,7 @@ class Annotator extends ClosureRewriter {
// requires us to not assign to typedef exports). Instead, emit the
// "exports.foo;" line directly in that case.
this.emit(`\n/** @typedef {${this.typeToClosure(node)}} */\n`);
if (node.flags & ts.NodeFlags.Export) {
if (hasModifierFlag(node, ts.ModifierFlags.Export)) {
this.emit('exports.');
} else {
this.emit('var ');
Expand All @@ -861,7 +868,7 @@ class Annotator extends ClosureRewriter {

/** Processes an EnumDeclaration or returns false for ordinary processing. */
private maybeProcessEnum(node: ts.EnumDeclaration): boolean {
if (node.flags & ts.NodeFlags.Const) {
if (hasModifierFlag(node, ts.ModifierFlags.Const)) {
// const enums disappear after TS compilation and consequently need no
// help from tsickle.
return false;
Expand Down Expand Up @@ -915,13 +922,10 @@ class Annotator extends ClosureRewriter {
// both a typedef and an indexable object if we export it.
this.emit('\n');
let name = node.name.getText();
if (node.flags & ts.NodeFlags.Export) {
this.emit('export ');
}
const isExported = hasModifierFlag(node, ts.ModifierFlags.Export);
if (isExported) this.emit('export ');
this.emit(`type ${name} = number;\n`);
if (node.flags & ts.NodeFlags.Export) {
this.emit('export ');
}
if (isExported) this.emit('export ');
this.emit(`let ${name}: any = {};\n`);

// Emit foo.BAR = 0; lines.
Expand Down
43 changes: 13 additions & 30 deletions src/type-translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,31 +37,13 @@ export function typeToDebugString(type: ts.Type): string {
let debugString = `flags:0x${type.flags.toString(16)}`;

const basicTypes: ts.TypeFlags[] = [
ts.TypeFlags.Any,
ts.TypeFlags.String,
ts.TypeFlags.Number,
ts.TypeFlags.Boolean,
ts.TypeFlags.Enum,
ts.TypeFlags.StringLiteral,
ts.TypeFlags.NumberLiteral,
ts.TypeFlags.BooleanLiteral,
ts.TypeFlags.EnumLiteral,
ts.TypeFlags.ESSymbol,
ts.TypeFlags.Void,
ts.TypeFlags.Undefined,
ts.TypeFlags.Null,
ts.TypeFlags.Never,
ts.TypeFlags.TypeParameter,
ts.TypeFlags.Class,
ts.TypeFlags.Interface,
ts.TypeFlags.Reference,
ts.TypeFlags.Tuple,
ts.TypeFlags.Union,
ts.TypeFlags.Intersection,
ts.TypeFlags.Anonymous,
ts.TypeFlags.Instantiated,
ts.TypeFlags.ThisType,
ts.TypeFlags.ObjectLiteralPatternWithComputedProperties,
ts.TypeFlags.Any, ts.TypeFlags.String, ts.TypeFlags.Number,
ts.TypeFlags.Boolean, ts.TypeFlags.Enum, ts.TypeFlags.StringLiteral,
ts.TypeFlags.NumberLiteral, ts.TypeFlags.BooleanLiteral, ts.TypeFlags.EnumLiteral,
ts.TypeFlags.ESSymbol, ts.TypeFlags.Void, ts.TypeFlags.Undefined,
ts.TypeFlags.Null, ts.TypeFlags.Never, ts.TypeFlags.TypeParameter,
ts.TypeFlags.Object, ts.TypeFlags.Union, ts.TypeFlags.Intersection,
ts.TypeFlags.Index, ts.TypeFlags.IndexedAccess,
];
for (let flag of basicTypes) {
if ((type.flags & flag) !== 0) {
Expand Down Expand Up @@ -220,13 +202,13 @@ export class TypeTranslator {

if (type.symbol && this.isBlackListed(type.symbol)) return '?';

if (type.flags & ts.TypeFlags.Class) {
if (/* TODO(ts2.1): type.flags & ts.TypeFlags.Class */ 1 === 1) {
if (!type.symbol) {
this.warn('class has no symbol');
return '?';
}
return '!' + this.symbolToString(type.symbol);
} else if (type.flags & ts.TypeFlags.Interface) {
} else if (/* TODO(ts2.1): type.flags & ts.TypeFlags.Interface */ 1 === 1) {
// Note: ts.InterfaceType has a typeParameters field, but that
// specifies the parameters that the interface type *expects*
// when it's used, and should not be transformed to the output.
Expand All @@ -248,13 +230,14 @@ export class TypeTranslator {
}
}
return '!' + this.symbolToString(type.symbol);
} else if (type.flags & ts.TypeFlags.Reference) {
} else if (/* TODO(ts2.1): type.flags & ts.TypeFlags.Reference */ 1 === 1) {
// A reference to another type, e.g. Array<number> refers to Array.
// Emit the referenced type and any type arguments.
let referenceType = type as ts.TypeReference;

let typeStr = '';
let isTuple = (referenceType.flags & ts.TypeFlags.Tuple) > 0;
// TODO(ts2.1): let isTuple = (referenceType.flags & ts.TypeFlags.Tuple) > 0;
let isTuple = true;
// For unknown reasons, tuple types can be reference types containing a
// reference loop. see Destructuring3 in functions.ts.
// TODO(rado): handle tuples in their own branch.
Expand All @@ -274,7 +257,7 @@ export class TypeTranslator {
typeStr += isTuple ? `!Array` : `<${params.join(', ')}>`;
}
return typeStr;
} else if (type.flags & ts.TypeFlags.Anonymous) {
} else if (/* TODO(ts2.1): type.flags & ts.TypeFlags.Anonymous */ 1 === 1) {
if (!type.symbol) {
// This comes up when generating code for an arrow function as passed
// to a generic function. The passed-in type is tagged as anonymous
Expand Down
2 changes: 1 addition & 1 deletion test/decorator-annotator_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe(
let goodSourceFile = program.getSourceFile(testCaseFileName);
expect(() => convertDecorators(program.getTypeChecker(), goodSourceFile)).to.not.throw();
let badSourceFile =
ts.createSourceFile(testCaseFileName, sourceText, ts.ScriptTarget.ES6, true);
ts.createSourceFile(testCaseFileName, sourceText, ts.ScriptTarget.ES2015, true);
expect(() => convertDecorators(program.getTypeChecker(), badSourceFile)).to.throw();
});

Expand Down
4 changes: 2 additions & 2 deletions test/test_support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {toArray} from '../src/util';

/** The TypeScript compiler options used by the test suite. */
export const compilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ES6,
target: ts.ScriptTarget.ES2015,
skipDefaultLibCheck: true,
experimentalDecorators: true,
emitDecoratorMetadata: true,
Expand All @@ -34,7 +34,7 @@ const {cachedLibPath, cachedLib} = (function() {
let host = ts.createCompilerHost(compilerOptions);
let fn = host.getDefaultLibFileName(compilerOptions);
let p = ts.getDefaultLibFilePath(compilerOptions);
return {cachedLibPath: p, cachedLib: host.getSourceFile(fn, ts.ScriptTarget.ES6)};
return {cachedLibPath: p, cachedLib: host.getSourceFile(fn, ts.ScriptTarget.ES2015)};
})();

/** Creates a ts.Program from a set of input files. */
Expand Down

0 comments on commit d25b3b8

Please sign in to comment.