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(')'); |
+ } |
} |