| Index: lib/base.ts
|
| diff --git a/lib/base.ts b/lib/base.ts
|
| index 709b28a71c87512052b34ed04d3e120a32cec133..dcc5d5f047617f6ba11a1d8025a6341a881aa1fe 100644
|
| --- a/lib/base.ts
|
| +++ b/lib/base.ts
|
| @@ -1,7 +1,15 @@
|
| +import * as dartStyle from 'dart-style';
|
| import * as ts from 'typescript';
|
| -import {Transpiler} from './main';
|
| +
|
| +import {OutputContext, Transpiler} from './main';
|
|
|
| export type ClassLike = ts.ClassDeclaration | ts.InterfaceDeclaration;
|
| +export type NamedDeclaration = ClassLike | ts.PropertyDeclaration | ts.VariableDeclaration |
|
| + ts.MethodDeclaration | ts.ModuleDeclaration | ts.FunctionDeclaration;
|
| +
|
| +export type Set = {
|
| + [s: string]: boolean
|
| +};
|
|
|
| export function ident(n: ts.Node): string {
|
| if (n.kind === ts.SyntaxKind.Identifier) return (<ts.Identifier>n).text;
|
| @@ -13,13 +21,198 @@ export function ident(n: ts.Node): string {
|
| return null;
|
| }
|
|
|
| +export function isFunctionTypedefLikeInterface(ifDecl: ts.InterfaceDeclaration): boolean {
|
| + return ifDecl.members && ifDecl.members.length === 1 &&
|
| + ifDecl.members[0].kind === ts.SyntaxKind.CallSignature;
|
| +}
|
| +
|
| +export function getDeclaration(type: ts.Type): ts.Declaration {
|
| + let symbol = type.getSymbol();
|
| + if (!symbol) return null;
|
| + if (symbol.valueDeclaration) return symbol.valueDeclaration;
|
| + return symbol.declarations && symbol.declarations.length > 0 ? symbol.declarations[0] : null;
|
| +}
|
| +
|
| +export function isExtendsClause(heritageClause: ts.HeritageClause) {
|
| + return heritageClause.token === ts.SyntaxKind.ExtendsKeyword &&
|
| + heritageClause.parent.kind !== ts.SyntaxKind.InterfaceDeclaration;
|
| +}
|
| +export function isConstructor(n: ts.Node): boolean {
|
| + return n.kind === ts.SyntaxKind.Constructor || n.kind === ts.SyntaxKind.ConstructSignature;
|
| +}
|
| +
|
| +export function isStatic(n: ts.Node): boolean {
|
| + let hasStatic = false;
|
| + ts.forEachChild(n, (child) => {
|
| + if (child.kind === ts.SyntaxKind.StaticKeyword) {
|
| + hasStatic = true;
|
| + }
|
| + });
|
| + return hasStatic;
|
| +}
|
| +
|
| +export function isCallableType(type: ts.TypeNode, tc: ts.TypeChecker): boolean {
|
| + if (isFunctionType(type, tc)) return true;
|
| + if (type.kind === ts.SyntaxKind.TypeReference) {
|
| + if (tc.getSignaturesOfType(tc.getTypeAtLocation(type), ts.SignatureKind.Call).length > 0)
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean {
|
| + let kind = type.kind;
|
| + if (kind === ts.SyntaxKind.FunctionType) return true;
|
| + if (kind === ts.SyntaxKind.TypeReference) {
|
| + let t = tc.getTypeAtLocation(type);
|
| + if (t.symbol && t.symbol.flags & ts.SymbolFlags.Function) return true;
|
| + }
|
| +
|
| + if (kind === ts.SyntaxKind.UnionType) {
|
| + let types = (<ts.UnionTypeNode>type).types;
|
| + for (let i = 0; i < types.length; ++i) {
|
| + if (!isFunctionType(types[i], tc)) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| + // Warning: if the kind is a reference type and the reference is to an
|
| + // interface that only has a call member we will not return that it is a
|
| + // function type.
|
| + if (kind === ts.SyntaxKind.TypeLiteral) {
|
| + let members = (<ts.TypeLiteralNode>type).members;
|
| + for (let i = 0; i < members.length; ++i) {
|
| + if (members[i].kind !== ts.SyntaxKind.CallSignature) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +export function isTypeNode(node: ts.Node): boolean {
|
| + switch (node.kind) {
|
| + case ts.SyntaxKind.UnionType:
|
| + case ts.SyntaxKind.TypeReference:
|
| + case ts.SyntaxKind.TypeLiteral:
|
| + case ts.SyntaxKind.LastTypeNode:
|
| + case ts.SyntaxKind.ArrayType:
|
| + case ts.SyntaxKind.TypePredicate:
|
| + case ts.SyntaxKind.TypeQuery:
|
| + case ts.SyntaxKind.TupleType:
|
| + case ts.SyntaxKind.NumberKeyword:
|
| + case ts.SyntaxKind.StringKeyword:
|
| + case ts.SyntaxKind.VoidKeyword:
|
| + case ts.SyntaxKind.BooleanKeyword:
|
| + case ts.SyntaxKind.AnyKeyword:
|
| + case ts.SyntaxKind.FunctionType:
|
| + return true;
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +export function isCallable(decl: ClassLike): boolean {
|
| + let members = decl.members as Array<ts.ClassElement>;
|
| + return members.some((member) => { return member.kind === ts.SyntaxKind.CallSignature; });
|
| +}
|
| +
|
| +export function copyLocation(src: ts.TextRange, dest: ts.TextRange) {
|
| + dest.pos = src.pos;
|
| + dest.end = src.end;
|
| +}
|
| +
|
| +// Polyfill for ES6 Array.find.
|
| +export function arrayFindPolyfill<T>(
|
| + nodeArray: ts.NodeArray<T>, predicate: (node: T) => boolean): T {
|
| + for (let i = 0; i < nodeArray.length; ++i) {
|
| + if (predicate(nodeArray[i])) return nodeArray[i];
|
| + }
|
| + return null;
|
| +}
|
| +
|
| +export function getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node {
|
| + for (let parent = n; parent; parent = parent.parent) {
|
| + if (parent.kind === kind) return parent;
|
| + }
|
| + return null;
|
| +}
|
| +
|
| +export function getEnclosingClass(n: ts.Node): ClassLike {
|
| + for (let parent = n.parent; parent; parent = parent.parent) {
|
| + if (parent.kind === ts.SyntaxKind.ClassDeclaration ||
|
| + parent.kind === ts.SyntaxKind.InterfaceDeclaration) {
|
| + return <ClassLike>parent;
|
| + }
|
| + }
|
| + return null;
|
| +}
|
| +
|
| +export function isConstCall(node: ts.CallExpression): boolean {
|
| + return node && ident(node.expression) === 'CONST_EXPR';
|
| +}
|
| +
|
| +export function isInsideConstExpr(node: ts.Node): boolean {
|
| + return isConstCall(<ts.CallExpression>getAncestor(node, ts.SyntaxKind.CallExpression));
|
| +}
|
| +
|
| +export function formatType(s: string, comment: string, insideCodeComment: boolean): string {
|
| + if (!comment) {
|
| + return s;
|
| + } else if (insideCodeComment) {
|
| + // When inside a comment we only need to emit the comment version which
|
| + // is the syntax we would like to use if Dart supported all language
|
| + // features we would like to use for interop.
|
| + return comment;
|
| + } else {
|
| + let sb = s + '/*';
|
| + // Check if the comment is a valid type name in which case it is safe to use the Dart code
|
| + // written in comments syntax.
|
| + const stubToMakeTypeValidStatement = ' DUMMY_VARIABLE_NAME;';
|
| + comment = comment.trim();
|
| + let statement = comment + stubToMakeTypeValidStatement;
|
| + let result = dartStyle.formatCode(statement);
|
| +
|
| + if (!result.error) {
|
| + result.code = result.code.trim();
|
| + let expectedStubIndex = result.code.length - stubToMakeTypeValidStatement.length;
|
| + if (result.code.lastIndexOf(stubToMakeTypeValidStatement) === expectedStubIndex) {
|
| + comment = result.code.substring(0, expectedStubIndex).trim();
|
| + sb += '=';
|
| + }
|
| + }
|
| + sb += comment;
|
| + sb += '*/';
|
| + return sb;
|
| + }
|
| +}
|
| +
|
| export class TranspilerBase {
|
| private idCounter: number = 0;
|
| - constructor(private transpiler: Transpiler) {}
|
| + constructor(protected transpiler: Transpiler) {}
|
|
|
| visit(n: ts.Node) { this.transpiler.visit(n); }
|
| + pushContext(context: OutputContext) { this.transpiler.pushContext(context); }
|
| + popContext() { this.transpiler.popContext(); }
|
| emit(s: string) { this.transpiler.emit(s); }
|
| emitNoSpace(s: string) { this.transpiler.emitNoSpace(s); }
|
| + emitType(s: string, comment: string) { this.transpiler.emitType(s, comment); }
|
| + maybeLineBreak() { return this.transpiler.maybeLineBreak(); }
|
| + enterCodeComment() { return this.transpiler.enterCodeComment(); }
|
| + exitCodeComment() { return this.transpiler.exitCodeComment(); }
|
| + get insideCodeComment() { return this.transpiler.insideCodeComment; }
|
| +
|
| + emitImport(toEmit: string) {
|
| + if (!this.transpiler.importsEmitted[toEmit]) {
|
| + this.pushContext(OutputContext.Import);
|
| + this.emit(`import "${toEmit}";`);
|
| + this.transpiler.importsEmitted[toEmit] = true;
|
| + this.popContext();
|
| + }
|
| + }
|
| +
|
| reportError(n: ts.Node, message: string) { this.transpiler.reportError(n, message); }
|
|
|
| visitNode(n: ts.Node): boolean { throw new Error('not implemented'); }
|
| @@ -33,13 +226,13 @@ export class TranspilerBase {
|
| visitList(nodes: ts.Node[], separator = ',') {
|
| for (let i = 0; i < nodes.length; i++) {
|
| this.visit(nodes[i]);
|
| - if (i < nodes.length - 1) this.emit(separator);
|
| + if (i < nodes.length - 1) this.emitNoSpace(separator);
|
| }
|
| }
|
|
|
| uniqueId(name: string): string {
|
| const id = this.idCounter++;
|
| - return `_${name}\$\$ts2dart\$${id}`;
|
| + return `_${name}\$\$js_facade_gen\$${id}`;
|
| }
|
|
|
| assert(c: ts.Node, condition: boolean, reason: string): void {
|
| @@ -56,7 +249,7 @@ export class TranspilerBase {
|
| return null;
|
| }
|
|
|
| - hasAncestor(n: ts.Node, kind: ts.SyntaxKind): boolean { return !!this.getAncestor(n, kind); }
|
| + hasAncestor(n: ts.Node, kind: ts.SyntaxKind): boolean { return !!getAncestor(n, kind); }
|
|
|
| hasAnnotation(decorators: ts.NodeArray<ts.Decorator>, name: string): boolean {
|
| if (!decorators) return false;
|
| @@ -74,27 +267,6 @@ export class TranspilerBase {
|
| return n && (n.flags & flag) !== 0 || false;
|
| }
|
|
|
| - isConst(decl: ClassLike) {
|
| - return this.hasAnnotation(decl.decorators, 'CONST') ||
|
| - (<ts.NodeArray<ts.Declaration>>decl.members).some((m) => {
|
| - if (m.kind !== ts.SyntaxKind.Constructor) return false;
|
| - return this.hasAnnotation(m.decorators, 'CONST');
|
| - });
|
| - }
|
| -
|
| - maybeDestructureIndexType(node: ts.TypeLiteralNode): [ts.TypeNode, ts.TypeNode] {
|
| - let members = node.members;
|
| - if (members.length !== 1 || members[0].kind !== ts.SyntaxKind.IndexSignature) {
|
| - return null;
|
| - }
|
| - let indexSig = <ts.IndexSignatureDeclaration>(members[0]);
|
| - if (indexSig.parameters.length > 1) {
|
| - this.reportError(indexSig, 'Expected an index signature to have a single parameter');
|
| - }
|
| - return [indexSig.parameters[0].type, indexSig.type];
|
| - }
|
| -
|
| -
|
| getRelativeFileName(fileName: string): string {
|
| return this.transpiler.getRelativeFileName(fileName);
|
| }
|
| @@ -109,7 +281,36 @@ export class TranspilerBase {
|
| }
|
| this.emitNoSpace('<');
|
| this.visitList(n.typeArguments);
|
| - this.emit('>');
|
| + this.emitNoSpace('>');
|
| }
|
| }
|
| +
|
| + visitParameters(parameters: ts.ParameterDeclaration[]) {
|
| + this.emitNoSpace('(');
|
| + let firstInitParamIdx = 0;
|
| + for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) {
|
| + // ObjectBindingPatterns are handled within the parameter visit.
|
| + let isOpt = parameters[firstInitParamIdx].initializer ||
|
| + parameters[firstInitParamIdx].questionToken ||
|
| + parameters[firstInitParamIdx].dotDotDotToken;
|
| + if (isOpt && parameters[firstInitParamIdx].name.kind !== ts.SyntaxKind.ObjectBindingPattern) {
|
| + break;
|
| + }
|
| + }
|
| +
|
| + if (firstInitParamIdx !== 0) {
|
| + let requiredParams = parameters.slice(0, firstInitParamIdx);
|
| + this.visitList(requiredParams);
|
| + }
|
| +
|
| + if (firstInitParamIdx !== parameters.length) {
|
| + if (firstInitParamIdx !== 0) this.emitNoSpace(',');
|
| + let positionalOptional = parameters.slice(firstInitParamIdx, parameters.length);
|
| + this.emit('[');
|
| + this.visitList(positionalOptional);
|
| + this.emitNoSpace(']');
|
| + }
|
| +
|
| + this.emitNoSpace(')');
|
| + }
|
| }
|
|
|