Index: lib/declaration.ts |
diff --git a/lib/declaration.ts b/lib/declaration.ts |
index 2301b102a60d8e86bfb4fa91732663205e3f2cb6..68bef4461317208b5688f5a1e4bddff8c07e549a 100644 |
--- a/lib/declaration.ts |
+++ b/lib/declaration.ts |
@@ -1,110 +1,596 @@ |
import * as ts from 'typescript'; |
+ |
import * as base from './base'; |
-import {Transpiler} from './main'; |
import {FacadeConverter} from './facade_converter'; |
+import {Transpiler} from './main'; |
+import {MergedParameter, MergedType} from './merge'; |
+ |
+export function isFunctionLikeProperty( |
+ decl: ts.PropertyDeclaration|ts.ParameterDeclaration, tc: ts.TypeChecker): boolean { |
+ if (!decl.type) return false; |
+ let name = base.ident(decl.name); |
+ if (name.match(/^on[A-Z]/)) return false; |
+ return base.isFunctionType(decl.type, tc); |
+} |
export default class DeclarationTranspiler extends base.TranspilerBase { |
+ private tc: ts.TypeChecker; |
+ |
+ private moduleStack: string[] = []; |
+ private extendsClass: boolean = false; |
+ |
+ static NUM_FAKE_REST_PARAMETERS = 5; |
+ |
+ setTypeChecker(tc: ts.TypeChecker) { this.tc = tc; } |
+ setFacadeConverter(fc: FacadeConverter) { this.fc = fc; } |
+ |
+ getJsPath(node: ts.Node): string { |
+ let path = [].concat(this.moduleStack); |
+ let classDecl = base.getEnclosingClass(node); |
+ if (classDecl) { |
+ path.push(classDecl.name.text); |
+ } |
+ |
+ switch (node.kind) { |
+ case ts.SyntaxKind.ModuleDeclaration: |
+ break; |
+ case ts.SyntaxKind.ClassDeclaration: |
+ case ts.SyntaxKind.InterfaceDeclaration: |
+ path.push((<base.ClassLike>node).name.text); |
+ break; |
+ case ts.SyntaxKind.EnumDeclaration: |
+ path.push((<ts.EnumDeclaration>node).name.text); |
+ break; |
+ case ts.SyntaxKind.PropertyDeclaration: |
+ case ts.SyntaxKind.VariableDeclaration: |
+ case ts.SyntaxKind.MethodDeclaration: |
+ case ts.SyntaxKind.FunctionDeclaration: |
+ case ts.SyntaxKind.GetAccessor: |
+ case ts.SyntaxKind.SetAccessor: |
+ case ts.SyntaxKind.PropertySignature: |
+ let memberName = base.ident((<base.NamedDeclaration>node).name); |
+ if (!base.isStatic(node) && classDecl != null) return memberName; |
+ path.push(memberName); |
+ break; |
+ default: |
+ throw 'Internal error. Unexpected node kind:' + node.kind; |
+ } |
+ if (path.length === 1) { |
+ // No need to specify the path if is simply the node name. |
+ return ''; |
+ } |
+ return path.join('.'); |
+ } |
+ |
+ private isAnonymousInterface(node: ts.Node): boolean { |
+ if (node.kind !== ts.SyntaxKind.InterfaceDeclaration) return false; |
+ // This is a bit of a hack but for the purposes of Dart codegen, |
+ // interfaces with static members or constructors have a known class name |
+ // at least for the purposes of resolving static members. |
+ // Example case that triggers this case: |
+ // interface Foo { |
+ // bar(); |
+ // } |
+ // declare let Foo: { |
+ // new(): Foo, |
+ // SOME_STATIC : number; |
+ // } |
+ return (<ts.InterfaceDeclaration>node).members.every((m: ts.Declaration) => { |
+ return m.kind !== ts.SyntaxKind.Constructor && !base.isStatic(m); |
+ }); |
+ } |
+ |
+ maybeEmitJsAnnotation(node: ts.Node) { |
+ // No need to emit the annotations as an entity outside the code comment |
+ // will already have the same annotation. |
+ if (this.insideCodeComment) return; |
+ |
+ if (this.isAnonymousInterface(node)) { |
+ this.emit('@anonymous'); |
+ this.emit('@JS()'); |
+ return; |
+ } |
+ let name: String = this.getJsPath(node); |
+ this.emit('@JS('); |
+ if (name.length > 0) { |
+ this.emit('"' + name + '"'); |
+ } |
+ this.emit(')'); |
+ } |
+ |
+ /** |
+ * Emit fake constructors to placate the Dart Analyzer for JS Interop classes. |
+ */ |
+ maybeEmitFakeConstructors(decl: base.ClassLike) { |
+ if (decl.kind === ts.SyntaxKind.ClassDeclaration) { |
+ // Required to avoid spurious dart errors involving base classes without |
+ // default constructors. |
+ this.emit('// @Ignore\n'); |
+ this.fc.visitTypeName(decl.name); |
+ this.emit('.fakeConstructor$()'); |
+ if (this.extendsClass) { |
+ // Required to keep the Dart Analyzer happy when a class has subclasses. |
+ this.emit(': super.fakeConstructor$()'); |
+ } |
+ this.emit(';\n'); |
+ } |
+ } |
+ |
+ private visitName(name: ts.Node) { |
+ if (base.getEnclosingClass(name) != null) { |
+ this.visit(name); |
+ return; |
+ } |
+ // Have to rewrite names in this case as we could have conflicts |
+ // due to needing to support multiple JS modules in a single JS module |
+ if (name.kind !== ts.SyntaxKind.Identifier) { |
+ throw 'Internal error: unexpected function name kind:' + name.kind; |
+ } |
+ let entry = this.fc.lookupCustomDartTypeName(<ts.Identifier>name, this.insideCodeComment); |
+ if (entry) { |
+ this.emit(entry.name); |
+ return; |
+ } |
+ |
+ this.visit(name); |
+ } |
+ |
+ private notSimpleBagOfProperties(type: ts.Type): boolean { |
+ if (this.tc.getSignaturesOfType(type, ts.SignatureKind.Call).length > 0) return true; |
+ if (this.tc.getSignaturesOfType(type, ts.SignatureKind.Construct).length > 0) return true; |
+ if (type.symbol) { |
+ let declaration = <ts.InterfaceDeclaration>type.symbol.declarations[0]; |
+ // We have to check the actual declaration as |
+ if (declaration && declaration.members) { |
+ let members = declaration.members; |
+ for (let i = 0; i < members.length; ++i) { |
+ let node = members[i]; |
+ if (base.isStatic(node)) return true; |
+ switch (node.kind) { |
+ case ts.SyntaxKind.PropertyDeclaration: |
+ case ts.SyntaxKind.PropertySignature: |
+ case ts.SyntaxKind.VariableDeclaration: |
+ break; |
+ default: |
+ return true; |
+ } |
+ } |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ /** |
+ * Returns whether all members of the class and all base classes |
+ */ |
+ hasOnlyProperties(decl: ts.InterfaceDeclaration, outProperties: ts.PropertyDeclaration[]): |
+ boolean { |
+ let type = <ts.InterfaceType>this.tc.getTypeAtLocation(decl); |
+ |
+ let properties = this.tc.getPropertiesOfType(type); |
+ let baseTypes = this.tc.getBaseTypes(type); |
+ if (this.notSimpleBagOfProperties(type)) return false; |
+ for (let i = 0; i < baseTypes.length; ++i) { |
+ let baseType = baseTypes[i]; |
+ if (this.notSimpleBagOfProperties(baseType)) return false; |
+ } |
+ |
+ for (let i = 0; i < properties.length; ++i) { |
+ let symbol = properties[i]; |
+ let node = symbol.valueDeclaration; |
+ switch (node.kind) { |
+ case ts.SyntaxKind.PropertyDeclaration: |
+ case ts.SyntaxKind.PropertySignature: |
+ case ts.SyntaxKind.VariableDeclaration: |
+ let prop = <ts.PropertyDeclaration>node; |
+ if (this.promoteFunctionLikeMembers && isFunctionLikeProperty(prop, this.tc)) { |
+ return false; |
+ } |
+ outProperties.push(prop); |
+ break; |
+ default: |
+ return false; |
+ } |
+ } |
+ return outProperties.length > 0; |
+ } |
+ |
+ visitClassBody(decl: base.ClassLike) { |
+ let properties: ts.PropertyDeclaration[] = []; |
+ let isPropertyBag = decl.kind === ts.SyntaxKind.InterfaceDeclaration && |
+ this.hasOnlyProperties(<ts.InterfaceDeclaration>decl, properties); |
+ this.visitMergingOverloads(decl.members); |
+ |
+ if (isPropertyBag) { |
+ this.emit('external factory'); |
+ this.fc.visitTypeName(decl.name); |
+ this.emitNoSpace('({'); |
+ for (let i = 0; i < properties.length; i++) { |
+ if (i > 0) this.emitNoSpace(','); |
+ let p = properties[i]; |
+ this.visit(p.type); |
+ this.visit(p.name); |
+ } |
+ this.emitNoSpace('});'); |
+ } |
+ } |
+ |
+ visitMergingOverloads(members: Array<ts.Node>) { |
+ // TODO(jacobr): merge method overloads. |
+ let groups: {[name: string]: Array<ts.Node>} = {}; |
+ let orderedGroups: Array<Array<ts.Node>> = []; |
+ members.forEach((node) => { |
+ let name = ''; |
+ switch (node.kind) { |
+ case ts.SyntaxKind.Block: |
+ // For JS interop we always skip the contents of a block. |
+ break; |
+ case ts.SyntaxKind.PropertyDeclaration: |
+ case ts.SyntaxKind.PropertySignature: |
+ case ts.SyntaxKind.VariableDeclaration: { |
+ let propertyDecl = <ts.PropertyDeclaration|ts.VariableDeclaration>node; |
+ // We need to emit these as properties not fields. |
+ if (!this.promoteFunctionLikeMembers || !isFunctionLikeProperty(propertyDecl, this.tc)) { |
+ orderedGroups.push([node]); |
+ return; |
+ } |
+ // Convert to a Method. |
+ let type = propertyDecl.type; |
+ let funcDecl = <ts.FunctionLikeDeclaration>ts.createNode(ts.SyntaxKind.MethodDeclaration); |
+ funcDecl.parent = node.parent; |
+ funcDecl.name = propertyDecl.name as ts.Identifier; |
+ switch (type.kind) { |
+ case ts.SyntaxKind.FunctionType: |
+ let callSignature = <ts.SignatureDeclaration>(<ts.Node>type); |
+ funcDecl.parameters = <ts.NodeArray<ts.ParameterDeclaration>>callSignature.parameters; |
+ funcDecl.type = callSignature.type; |
+ // Fall through to the function case using this node |
+ node = funcDecl; |
+ break; |
+ case ts.SyntaxKind.UnionType: |
+ case ts.SyntaxKind.TypeLiteral: |
+ throw 'Not supported yet'; |
+ default: |
+ throw 'Unexpected case'; |
+ } |
+ name = base.ident((<ts.FunctionLikeDeclaration>node).name); |
+ } break; |
+ case ts.SyntaxKind.FunctionDeclaration: |
+ case ts.SyntaxKind.MethodDeclaration: |
+ case ts.SyntaxKind.MethodSignature: |
+ case ts.SyntaxKind.FunctionExpression: |
+ name = base.ident((<ts.FunctionLikeDeclaration>node).name); |
+ break; |
+ case ts.SyntaxKind.CallSignature: |
+ name = 'call'; |
+ break; |
+ case ts.SyntaxKind.Constructor: |
+ break; |
+ case ts.SyntaxKind.ConstructSignature: |
+ break; |
+ case ts.SyntaxKind.IndexSignature: |
+ name = '[]'; |
+ break; |
+ case ts.SyntaxKind.ClassDeclaration: |
+ case ts.SyntaxKind.InterfaceDeclaration: |
+ case ts.SyntaxKind.VariableStatement: |
+ orderedGroups.push([node]); |
+ return; |
+ case ts.SyntaxKind.GetAccessor: |
+ case ts.SyntaxKind.SetAccessor: |
+ case ts.SyntaxKind.SemicolonClassElement: |
+ case ts.SyntaxKind.ModuleDeclaration: |
+ case ts.SyntaxKind.TypeAliasDeclaration: |
+ case ts.SyntaxKind.ExportAssignment: |
+ orderedGroups.push([node]); |
+ return; |
+ default: |
+ console.log('Warning: unexpected type... overloads: ' + node.kind + ' ' + node.getText()); |
+ orderedGroups.push([node]); |
+ return; |
+ } |
+ let group: Array<ts.Node>; |
+ if (Object.prototype.hasOwnProperty.call(groups, name)) { |
+ group = groups[name]; |
+ } else { |
+ group = []; |
+ groups[name] = group; |
+ orderedGroups.push(group); |
+ } |
+ group.push(node); |
+ }); |
+ |
+ orderedGroups.forEach((group: Array<ts.Node>) => { |
+ if (group.length === 1) { |
+ this.visit(group[0]); |
+ return; |
+ } |
+ group.forEach((fn: ts.Node) => { |
+ // Emit overrides in a comment that the Dart analyzer can at some point |
+ // use to improve autocomplete. |
+ this.maybeLineBreak(); |
+ this.enterCodeComment(); |
+ this.visit(fn); |
+ this.exitCodeComment(); |
+ this.maybeLineBreak(); |
+ }); |
+ // TODO: actually merge. |
+ let first = <ts.SignatureDeclaration>group[0]; |
+ let kind = first.kind; |
+ let merged = <ts.SignatureDeclaration>ts.createNode(kind); |
+ merged.parent = first.parent; |
+ base.copyLocation(first, merged); |
+ switch (kind) { |
+ case ts.SyntaxKind.FunctionDeclaration: |
+ case ts.SyntaxKind.MethodDeclaration: |
+ case ts.SyntaxKind.MethodSignature: |
+ case ts.SyntaxKind.FunctionExpression: |
+ let fn = <ts.FunctionLikeDeclaration>first; |
+ merged.name = fn.name; |
+ break; |
+ case ts.SyntaxKind.CallSignature: |
+ break; |
+ case ts.SyntaxKind.Constructor: |
+ break; |
+ case ts.SyntaxKind.ConstructSignature: |
+ break; |
+ case ts.SyntaxKind.IndexSignature: |
+ break; |
+ default: |
+ throw 'Unexpected kind:' + kind; |
+ } |
+ let mergedParams = first.parameters.map( |
+ (param: ts.ParameterDeclaration) => new MergedParameter(param, this.fc)); |
+ let mergedType = new MergedType(this.fc); |
+ mergedType.merge(first.type); |
+ |
+ for (let i = 1; i < group.length; ++i) { |
+ let signature = <ts.SignatureDeclaration>group[i]; |
+ mergedType.merge(signature.type); |
+ let overlap = Math.min(signature.parameters.length, mergedParams.length); |
+ for (let j = 0; j < overlap; ++j) { |
+ mergedParams[j].merge(signature.parameters[j]); |
+ } |
+ for (let j = overlap; j < mergedParams.length; ++j) { |
+ mergedParams[j].setOptional(); |
+ } |
+ for (let j = mergedParams.length; j < signature.parameters.length; ++j) { |
+ let param = new MergedParameter(signature.parameters[j], this.fc); |
+ param.setOptional(); |
+ mergedParams.push(param); |
+ } |
+ } |
+ merged.parameters = <ts.NodeArray<ts.ParameterDeclaration>>mergedParams.map( |
+ (p) => p.toParameterDeclaration()); |
+ merged.type = mergedType.toTypeNode(); |
+ |
+ this.fc.visit(merged); |
+ }); |
+ } |
+ |
+ |
constructor( |
- tr: Transpiler, private fc: FacadeConverter, private enforceUnderscoreConventions: boolean) { |
+ tr: Transpiler, private fc: FacadeConverter, private enforceUnderscoreConventions: boolean, |
+ private promoteFunctionLikeMembers: boolean) { |
super(tr); |
} |
visitNode(node: ts.Node): boolean { |
switch (node.kind) { |
- case ts.SyntaxKind.VariableDeclarationList: |
- // Note: VariableDeclarationList can only occur as part of a for loop. |
- let varDeclList = <ts.VariableDeclarationList>node; |
- this.visitList(varDeclList.declarations); |
+ case ts.SyntaxKind.ModuleDeclaration: |
+ let moduleDecl = <ts.ModuleDeclaration>node; |
+ this.emit('\n// Module ' + moduleDecl.name.text + '\n'); |
+ this.moduleStack.push(moduleDecl.name.text); |
+ |
+ this.visit(moduleDecl.body); |
+ this.emit('\n// End module ' + moduleDecl.name.text + '\n'); |
+ this.moduleStack.pop(); |
break; |
- case ts.SyntaxKind.VariableDeclaration: |
+ case ts.SyntaxKind.ExportKeyword: |
+ // TODO(jacobr): perhaps add a specific Dart annotation to indicate |
+ // exported members or provide a flag to only generate code for exported |
+ // members. |
+ break; |
+ case ts.SyntaxKind.EnumDeclaration: { |
+ let decl = <ts.EnumDeclaration>node; |
+ // The only legal modifier for an enum decl is const. |
+ let isConst = decl.modifiers && (decl.modifiers.flags & ts.NodeFlags.Const); |
+ if (isConst) { |
+ this.reportError(node, 'const enums are not supported'); |
+ } |
+ // In JS interop mode we have to treat enums as JavaScript classes |
+ // with static members for each enum constant instead of as first |
+ // class enums. |
+ this.maybeEmitJsAnnotation(decl); |
+ this.emit('class'); |
+ this.emit(decl.name.text); |
+ this.emit('{'); |
+ let nodes = decl.members; |
+ for (let i = 0; i < nodes.length; i++) { |
+ this.emit('external static num get'); |
+ this.visit(nodes[i]); |
+ this.emitNoSpace(';'); |
+ } |
+ this.emit('}'); |
+ } break; |
+ case ts.SyntaxKind.Parameter: { |
+ let paramDecl = <ts.ParameterDeclaration>node; |
+ if (paramDecl.type && paramDecl.type.kind === ts.SyntaxKind.FunctionType) { |
+ // Dart uses "returnType paramName ( parameters )" syntax. |
+ let fnType = <ts.FunctionOrConstructorTypeNode>paramDecl.type; |
+ let hasRestParameter = fnType.parameters.some(p => !!p.dotDotDotToken); |
+ if (!hasRestParameter) { |
+ // Dart does not support rest parameters/varargs, degenerate to just "Function". |
+ // TODO(jacobr): also consider faking 0 - NUM_FAKE_REST_PARAMETERS |
+ // instead. |
+ this.visit(fnType.type); |
+ this.visit(paramDecl.name); |
+ this.visitParameters(fnType.parameters); |
+ break; |
+ } |
+ } |
+ |
+ if (paramDecl.dotDotDotToken) { |
+ // Weak support of varargs that works ok if you have 5 of fewer args. |
+ let paramType: ts.TypeNode; |
+ let type = paramDecl.type; |
+ if (type) { |
+ if (type.kind === ts.SyntaxKind.ArrayType) { |
+ let arrayType = <ts.ArrayTypeNode>type; |
+ paramType = arrayType.elementType; |
+ } else if (type.kind !== ts.SyntaxKind.AnyKeyword) { |
+ throw 'Unexpected type for varargs: ' + type.kind; |
+ } |
+ } |
+ |
+ for (let i = 1; i <= DeclarationTranspiler.NUM_FAKE_REST_PARAMETERS; ++i) { |
+ if (i > 1) { |
+ this.emitNoSpace(','); |
+ } |
+ this.visit(paramType); |
+ this.emit(base.ident(paramDecl.name) + i); |
+ } |
+ break; |
+ } |
+ // TODO(jacobr): should we support |
+ if (paramDecl.name.kind === ts.SyntaxKind.ObjectBindingPattern) { |
+ this.emit('Object'); |
+ let pattern = paramDecl.name as ts.BindingPattern; |
+ let elements = pattern.elements; |
+ let name = elements.map((e) => base.ident(e.name)).join('_'); |
+ // Warning: this name is unlikely to but could possible overlap with |
+ // other parameter names. |
+ this.emit(name); |
+ this.enterCodeComment(); |
+ this.emit(pattern.getText()); |
+ this.exitCodeComment(); |
+ break; |
+ } |
+ |
+ if (paramDecl.name.kind !== ts.SyntaxKind.Identifier) { |
+ throw 'Unsupported parameter name kind: ' + paramDecl.name.kind; |
+ } |
+ this.visit(paramDecl.type); |
+ this.visit(paramDecl.name); |
+ } break; |
+ case ts.SyntaxKind.EnumMember: { |
+ let member = <ts.EnumMember>node; |
+ this.visit(member.name); |
+ } break; |
+ case ts.SyntaxKind.ModuleBlock: { |
+ let block = <ts.ModuleBlock>node; |
+ this.visitMergingOverloads(block.statements); |
+ } break; |
+ case ts.SyntaxKind.VariableDeclarationList: { |
+ // We have to handle variable declaration lists differently in the case |
+ // of JS interop because Dart does not support external variables. |
+ let varDeclList = <ts.VariableDeclarationList>node; |
+ this.visitList(varDeclList.declarations, ';'); |
+ } break; |
+ case ts.SyntaxKind.VariableDeclaration: { |
+ // We have to handle variable declarations differently in the case of JS |
+ // interop because Dart does not support external variables. |
let varDecl = <ts.VariableDeclaration>node; |
- this.visitVariableDeclarationType(varDecl); |
- this.visit(varDecl.name); |
- if (varDecl.initializer) { |
- this.emit('='); |
- this.visit(varDecl.initializer); |
+ this.maybeEmitJsAnnotation(varDecl); |
+ this.emit('external'); |
+ this.visit(varDecl.type); |
+ this.emit('get'); |
+ this.visitName(varDecl.name); |
+ if (!this.hasFlag(varDecl.parent, ts.NodeFlags.Const)) { |
+ this.emitNoSpace(';'); |
+ this.maybeEmitJsAnnotation(varDecl); |
+ this.emit('external'); |
+ this.emit('set'); |
+ this.visitName(varDecl.name); |
+ this.emitNoSpace('('); |
+ this.visit(varDecl.type); |
+ this.emit('v)'); |
} |
+ } break; |
+ case ts.SyntaxKind.StringLiteral: { |
+ this.emit('String'); |
+ this.enterCodeComment(); |
+ let sLit = <ts.LiteralExpression>node; |
+ let text = JSON.stringify(sLit.text); |
+ this.emit(text); |
+ this.exitCodeComment(); |
+ } break; |
+ case ts.SyntaxKind.CallSignature: { |
+ let fn = <ts.SignatureDeclaration>node; |
+ this.emit('external'); |
+ this.visit(fn.type); |
+ this.emit('call'); |
+ this.visitParameters(fn.parameters); |
+ this.emitNoSpace(';'); |
+ } break; |
+ case ts.SyntaxKind.IndexSignature: |
+ this.emit('/* Index signature is not yet supported by JavaScript interop. */\n'); |
+ break; |
+ case ts.SyntaxKind.ExportAssignment: |
+ // let exportAssignment = <ts.ExportAssignment>node; |
+ this.emit('/* WARNING: export assignment not yet supported. */\n'); |
+ break; |
+ case ts.SyntaxKind.TypeAliasDeclaration: |
+ // Dart does not provide special syntax for definning type alais |
+ // declarations so we do not emit anything here and resolve alaises |
+ // to their original types at each usage site. |
break; |
- |
case ts.SyntaxKind.ClassDeclaration: |
- let classDecl = <ts.ClassDeclaration>node; |
- if (classDecl.modifiers && (classDecl.modifiers.flags & ts.NodeFlags.Abstract)) { |
+ case ts.SyntaxKind.InterfaceDeclaration: { |
+ this.extendsClass = false; |
+ let classDecl = <ts.ClassDeclaration|ts.InterfaceDeclaration>node; |
+ let isInterface = node.kind === ts.SyntaxKind.InterfaceDeclaration; |
+ if (isInterface && |
+ base.isFunctionTypedefLikeInterface(classDecl as ts.InterfaceDeclaration)) { |
+ let member = <ts.CallSignatureDeclaration>classDecl.members[0]; |
+ this.visitFunctionTypedefInterface(classDecl.name.text, member, classDecl.typeParameters); |
+ break; |
+ } |
+ |
+ let customName = this.fc.lookupCustomDartTypeName(classDecl.name, this.insideCodeComment); |
+ if (customName && !customName.keep) { |
+ this.emit('\n/* Skipping class ' + base.ident(classDecl.name) + '*/\n'); |
+ break; |
+ } |
+ this.maybeEmitJsAnnotation(node); |
+ |
+ if (isInterface || |
+ (classDecl.modifiers && (classDecl.modifiers.flags & ts.NodeFlags.Abstract))) { |
this.visitClassLike('abstract class', classDecl); |
} else { |
this.visitClassLike('class', classDecl); |
} |
- break; |
- case ts.SyntaxKind.InterfaceDeclaration: |
- let ifDecl = <ts.InterfaceDeclaration>node; |
- // Function type interface in an interface with a single declaration |
- // of a call signature (http://goo.gl/ROC5jN). |
- if (ifDecl.members.length === 1 && ifDecl.members[0].kind === ts.SyntaxKind.CallSignature) { |
- let member = <ts.CallSignatureDeclaration>ifDecl.members[0]; |
- this.visitFunctionTypedefInterface(ifDecl.name.text, member, ifDecl.typeParameters); |
- } else { |
- this.visitClassLike('abstract class', ifDecl); |
- } |
- break; |
- case ts.SyntaxKind.HeritageClause: |
+ } break; |
+ case ts.SyntaxKind.HeritageClause: { |
let heritageClause = <ts.HeritageClause>node; |
- if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword && |
- heritageClause.parent.kind !== ts.SyntaxKind.InterfaceDeclaration) { |
+ if (base.isExtendsClause(<ts.HeritageClause>heritageClause)) { |
+ this.extendsClass = true; |
+ } |
+ |
+ if (base.isExtendsClause(heritageClause)) { |
this.emit('extends'); |
} else { |
this.emit('implements'); |
} |
// Can only have one member for extends clauses. |
this.visitList(heritageClause.types); |
- break; |
- case ts.SyntaxKind.ExpressionWithTypeArguments: |
+ } break; |
+ case ts.SyntaxKind.ExpressionWithTypeArguments: { |
let exprWithTypeArgs = <ts.ExpressionWithTypeArguments>node; |
this.visit(exprWithTypeArgs.expression); |
this.maybeVisitTypeArguments(exprWithTypeArgs); |
- break; |
- case ts.SyntaxKind.EnumDeclaration: |
- let decl = <ts.EnumDeclaration>node; |
- // The only legal modifier for an enum decl is const. |
- let isConst = decl.modifiers && (decl.modifiers.flags & ts.NodeFlags.Const); |
- if (isConst) { |
- this.reportError(node, 'const enums are not supported'); |
- } |
- this.emit('enum'); |
- this.fc.visitTypeName(decl.name); |
- this.emit('{'); |
- // Enums can be empty in TS ... |
- if (decl.members.length === 0) { |
- // ... but not in Dart. |
- this.reportError(node, 'empty enums are not supported'); |
- } |
- this.visitList(decl.members); |
- this.emit('}'); |
- break; |
- case ts.SyntaxKind.EnumMember: |
- let member = <ts.EnumMember>node; |
- this.visit(member.name); |
- if (member.initializer) { |
- this.reportError(node, 'enum initializers are not supported'); |
- } |
- break; |
+ } break; |
case ts.SyntaxKind.Constructor: |
+ case ts.SyntaxKind.ConstructSignature: { |
let ctorDecl = <ts.ConstructorDeclaration>node; |
// Find containing class name. |
- let className: ts.Identifier; |
- for (let parent = ctorDecl.parent; parent; parent = parent.parent) { |
- if (parent.kind === ts.SyntaxKind.ClassDeclaration) { |
- className = (<ts.ClassDeclaration>parent).name; |
- break; |
- } |
- } |
- if (!className) this.reportError(ctorDecl, 'cannot find outer class node'); |
+ let classDecl = base.getEnclosingClass(ctorDecl); |
+ if (!classDecl) this.reportError(ctorDecl, 'cannot find outer class node'); |
this.visitDeclarationMetadata(ctorDecl); |
- if (this.isConst(<base.ClassLike>ctorDecl.parent)) { |
- this.emit('const'); |
- } |
- this.visit(className); |
+ this.fc.visitTypeName(classDecl.name); |
this.visitParameters(ctorDecl.parameters); |
- this.visit(ctorDecl.body); |
- break; |
+ this.emitNoSpace(';'); |
+ } break; |
case ts.SyntaxKind.PropertyDeclaration: |
this.visitProperty(<ts.PropertyDeclaration>node); |
break; |
@@ -125,22 +611,9 @@ export default class DeclarationTranspiler extends base.TranspilerBase { |
break; |
case ts.SyntaxKind.FunctionDeclaration: |
let funcDecl = <ts.FunctionDeclaration>node; |
- this.visitDecorators(funcDecl.decorators); |
+ this.visitDeclarationMetadata(funcDecl); |
this.visitFunctionLike(funcDecl); |
break; |
- case ts.SyntaxKind.ArrowFunction: |
- let arrowFunc = <ts.FunctionExpression>node; |
- // Dart only allows expressions following the fat arrow operator. |
- // If the body is a block, we have to drop the fat arrow and emit an |
- // anonymous function instead. |
- if (arrowFunc.body.kind === ts.SyntaxKind.Block) { |
- this.visitFunctionLike(arrowFunc); |
- } else { |
- this.visitParameters(arrowFunc.parameters); |
- this.emit('=>'); |
- this.visit(arrowFunc.body); |
- } |
- break; |
case ts.SyntaxKind.FunctionExpression: |
let funcExpr = <ts.FunctionExpression>node; |
this.visitFunctionLike(funcExpr); |
@@ -151,56 +624,11 @@ export default class DeclarationTranspiler extends base.TranspilerBase { |
break; |
case ts.SyntaxKind.MethodSignature: |
let methodSignatureDecl = <ts.FunctionLikeDeclaration>node; |
- this.visitEachIfPresent(methodSignatureDecl.modifiers); |
+ this.visitDeclarationMetadata(methodSignatureDecl); |
this.visitFunctionLike(methodSignatureDecl); |
break; |
- case ts.SyntaxKind.Parameter: |
- let paramDecl = <ts.ParameterDeclaration>node; |
- // Property parameters will have an explicit property declaration, so we just |
- // need the dart assignment shorthand to reference the property. |
- if (this.hasFlag(paramDecl.modifiers, ts.NodeFlags.Public) || |
- this.hasFlag(paramDecl.modifiers, ts.NodeFlags.Private) || |
- this.hasFlag(paramDecl.modifiers, ts.NodeFlags.Protected)) { |
- this.visitDeclarationMetadata(paramDecl); |
- this.emit('this .'); |
- this.visit(paramDecl.name); |
- if (paramDecl.initializer) { |
- this.emit('='); |
- this.visit(paramDecl.initializer); |
- } |
- break; |
- } |
- if (paramDecl.dotDotDotToken) this.reportError(node, 'rest parameters are unsupported'); |
- if (paramDecl.name.kind === ts.SyntaxKind.ObjectBindingPattern) { |
- this.visitNamedParameter(paramDecl); |
- break; |
- } |
- this.visitDecorators(paramDecl.decorators); |
- |
- if (paramDecl.type && paramDecl.type.kind === ts.SyntaxKind.FunctionType) { |
- // Dart uses "returnType paramName ( parameters )" syntax. |
- let fnType = <ts.FunctionOrConstructorTypeNode>paramDecl.type; |
- let hasRestParameter = fnType.parameters.some(p => !!p.dotDotDotToken); |
- if (hasRestParameter) { |
- // Dart does not support rest parameters/varargs, degenerate to just "Function". |
- this.emit('Function'); |
- this.visit(paramDecl.name); |
- } else { |
- this.visit(fnType.type); |
- this.visit(paramDecl.name); |
- this.visitParameters(fnType.parameters); |
- } |
- } else { |
- if (paramDecl.type) this.visit(paramDecl.type); |
- this.visit(paramDecl.name); |
- } |
- if (paramDecl.initializer) { |
- this.emit('='); |
- this.visit(paramDecl.initializer); |
- } |
- break; |
case ts.SyntaxKind.StaticKeyword: |
- this.emit('static'); |
+ // n-op, handled in `visitFunctionLike` and `visitProperty` below. |
break; |
case ts.SyntaxKind.AbstractKeyword: |
// Abstract methods in Dart simply lack implementation, |
@@ -216,77 +644,55 @@ export default class DeclarationTranspiler extends base.TranspilerBase { |
case ts.SyntaxKind.ProtectedKeyword: |
// Handled in `visitDeclarationMetadata` below. |
break; |
- |
+ case ts.SyntaxKind.VariableStatement: |
+ let variableStmt = <ts.VariableStatement>node; |
+ this.visit(variableStmt.declarationList); |
+ this.emitNoSpace(';'); |
+ break; |
+ case ts.SyntaxKind.SwitchStatement: |
+ case ts.SyntaxKind.ArrayLiteralExpression: |
+ case ts.SyntaxKind.ExpressionStatement: |
+ case ts.SyntaxKind.EmptyStatement: |
+ // No need to emit anything for these cases. |
+ break; |
default: |
return false; |
} |
return true; |
} |
- private visitVariableDeclarationType(varDecl: ts.VariableDeclaration) { |
- /* Note: VariableDeclarationList can only occur as part of a for loop. This helper method |
- * is meant for processing for-loop variable declaration types only. |
- * |
- * In Dart, all variables in a variable declaration list must have the same type. Since |
- * we are doing syntax directed translation, we cannot reliably determine if distinct |
- * variables are declared with the same type or not. Hence we support the following cases: |
- * |
- * - A variable declaration list with a single variable can be explicitly typed. |
- * - When more than one variable is in the list, all must be implicitly typed. |
- */ |
- let firstDecl = varDecl.parent.declarations[0]; |
- let msg = 'Variables in a declaration list of more than one variable cannot by typed'; |
- let isFinal = this.hasFlag(varDecl.parent, ts.NodeFlags.Const); |
- let isConst = false; |
- if (isFinal && varDecl.initializer) { |
- // "const" in TypeScript/ES6 corresponds to "final" in Dart, i.e. reference constness. |
- // If a "const" variable is immediately initialized to a CONST_EXPR(), special case it to be |
- // a deeply const constant, and generate "const ...". |
- isConst = varDecl.initializer.kind === ts.SyntaxKind.StringLiteral || |
- varDecl.initializer.kind === ts.SyntaxKind.NumericLiteral || |
- this.fc.isConstCall(varDecl.initializer); |
- } |
- if (firstDecl === varDecl) { |
- if (isConst) { |
- this.emit('const'); |
- } else if (isFinal) { |
- this.emit('final'); |
- } |
- if (!varDecl.type) { |
- if (!isFinal) this.emit('var'); |
- } else if (varDecl.parent.declarations.length > 1) { |
- this.reportError(varDecl, msg); |
- } else { |
- this.visit(varDecl.type); |
- } |
- } else if (varDecl.type) { |
- this.reportError(varDecl, msg); |
- } |
- } |
- |
private visitFunctionLike(fn: ts.FunctionLikeDeclaration, accessor?: string) { |
this.fc.pushTypeParameterNames(fn); |
+ if (base.isStatic(fn)) { |
+ this.emit('static'); |
+ } |
+ |
try { |
- if (fn.type) { |
- if (fn.kind === ts.SyntaxKind.ArrowFunction || |
- fn.kind === ts.SyntaxKind.FunctionExpression) { |
- // The return type is silently dropped for function expressions (including arrow |
- // functions), it is not supported in Dart. |
- this.emit('/*'); |
- this.visit(fn.type); |
- this.emit('*/'); |
- } else { |
- this.visit(fn.type); |
+ this.visit(fn.type); |
+ if (accessor) this.emit(accessor); |
+ let name = fn.name; |
+ if (name) { |
+ if (name.kind !== ts.SyntaxKind.Identifier) { |
+ this.reportError(name, 'Unexpected name kind:' + name.kind); |
} |
+ this.fc.visitTypeName(<ts.Identifier>name); |
} |
- if (accessor) this.emit(accessor); |
- if (fn.name) this.visit(fn.name); |
+ |
if (fn.typeParameters) { |
- this.emit('/*<'); |
+ let insideComment = this.insideCodeComment; |
+ if (!insideComment) { |
+ this.enterCodeComment(); |
+ } |
+ this.emitNoSpace('<'); |
// Emit the names literally instead of visiting, otherwise they will be replaced with the |
// comment hack themselves. |
- this.emit(fn.typeParameters.map(p => base.ident(p.name)).join(', ')); |
- this.emit('>*/'); |
+ // TODO(jacobr): we can use the regular type parameter visiting pattern |
+ // now that we properly track whether we are inside a comment. |
+ this.emitNoSpace(fn.typeParameters.map(p => base.ident(p.name)).join(', ')); |
+ this.emitNoSpace('>'); |
+ if (!insideComment) { |
+ this.exitCodeComment(); |
+ } |
} |
// Dart does not even allow the parens of an empty param list on getter |
if (accessor !== 'get') { |
@@ -296,77 +702,42 @@ export default class DeclarationTranspiler extends base.TranspilerBase { |
this.reportError(fn, 'getter should not accept parameters'); |
} |
} |
- if (fn.body) { |
- this.visit(fn.body); |
- } else { |
- this.emit(';'); |
- } |
+ this.emitNoSpace(';'); |
} finally { |
this.fc.popTypeParameterNames(fn); |
} |
} |
- private visitParameters(parameters: ts.ParameterDeclaration[]) { |
- this.emit('('); |
- let firstInitParamIdx = 0; |
- for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) { |
- // ObjectBindingPatterns are handled within the parameter visit. |
- let isOpt = |
- parameters[firstInitParamIdx].initializer || parameters[firstInitParamIdx].questionToken; |
- 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.emit(','); |
- let positionalOptional = parameters.slice(firstInitParamIdx, parameters.length); |
- this.emit('['); |
- this.visitList(positionalOptional); |
- this.emit(']'); |
- } |
- |
- this.emit(')'); |
- } |
- |
/** |
* Visit a property declaration. |
+ * In the special case of property parameters in a constructor, we also allow |
+ * a parameter to be emitted as a property. |
+ * We have to emit properties as getter setter pairs as Dart does not support |
+ * external fields. |
* In the special case of property parameters in a constructor, we also allow a parameter to be |
* emitted as a property. |
*/ |
private visitProperty(decl: ts.PropertyDeclaration|ts.ParameterDeclaration, isParameter = false) { |
- if (!isParameter) this.visitDeclarationMetadata(decl); |
- let containingClass = <base.ClassLike>(isParameter ? decl.parent.parent : decl.parent); |
- let isConstField = this.hasAnnotation(decl.decorators, 'CONST'); |
- let hasConstCtor = this.isConst(containingClass); |
- if (isConstField) { |
- // const implies final |
- this.emit('const'); |
- } else { |
- if (hasConstCtor) { |
- this.emit('final'); |
- } |
- } |
- if (decl.type) { |
- this.visit(decl.type); |
- } else if (!isConstField && !hasConstCtor) { |
- this.emit('var'); |
- } |
- this.visit(decl.name); |
- if (decl.initializer && !isParameter) { |
- this.emit('='); |
- this.visit(decl.initializer); |
- } |
- this.emit(';'); |
+ let isStatic = base.isStatic(decl); |
+ this.emit('external'); |
+ if (isStatic) this.emit('static'); |
+ this.visit(decl.type); |
+ this.emit('get'); |
+ this.visitName(decl.name); |
+ this.emitNoSpace(';'); |
+ |
+ this.emit('external'); |
+ if (isStatic) this.emit('static'); |
+ this.emit('set'); |
+ this.visitName(decl.name); |
+ this.emitNoSpace('('); |
+ this.visit(decl.type); |
+ this.emit('v'); |
+ this.emitNoSpace(')'); |
+ this.emitNoSpace(';'); |
} |
private visitClassLike(keyword: string, decl: base.ClassLike) { |
- this.visitDecorators(decl.decorators); |
this.emit(keyword); |
this.fc.visitTypeName(decl.name); |
if (decl.typeParameters) { |
@@ -374,9 +745,12 @@ export default class DeclarationTranspiler extends base.TranspilerBase { |
this.visitList(decl.typeParameters); |
this.emit('>'); |
} |
+ |
this.visitEachIfPresent(decl.heritageClauses); |
this.emit('{'); |
+ this.maybeEmitFakeConstructors(decl); |
+ |
// Synthesize explicit properties for ctor with 'property parameters' |
let synthesizePropertyParam = (param: ts.ParameterDeclaration) => { |
if (this.hasFlag(param.modifiers, ts.NodeFlags.Public) || |
@@ -386,108 +760,40 @@ export default class DeclarationTranspiler extends base.TranspilerBase { |
this.visitProperty(param, true); |
} |
}; |
- (<ts.NodeArray<ts.Declaration>>decl.members) |
- .filter((m) => m.kind === ts.SyntaxKind.Constructor) |
+ (decl.members as ts.NodeArray<ts.Declaration>) |
+ .filter(base.isConstructor) |
.forEach( |
(ctor) => |
(<ts.ConstructorDeclaration>ctor).parameters.forEach(synthesizePropertyParam)); |
- this.visitEachIfPresent(decl.members); |
- // Generate a constructor to host the const modifier, if needed |
- if (this.isConst(decl) && |
- !(<ts.NodeArray<ts.Declaration>>decl.members) |
- .some((m) => m.kind === ts.SyntaxKind.Constructor)) { |
- this.emit('const'); |
- this.fc.visitTypeName(decl.name); |
- this.emit('();'); |
- } |
+ this.visitClassBody(decl); |
this.emit('}'); |
} |
- private visitDecorators(decorators: ts.NodeArray<ts.Decorator>) { |
- if (!decorators) return; |
- |
- decorators.forEach((d) => { |
- // Special case @CONST |
- let name = base.ident(d.expression); |
- if (!name && d.expression.kind === ts.SyntaxKind.CallExpression) { |
- // Unwrap @CONST() |
- let callExpr = (<ts.CallExpression>d.expression); |
- name = base.ident(callExpr.expression); |
- } |
- // Make sure these match IGNORED_ANNOTATIONS below. |
- if (name === 'CONST') { |
- // Ignore @CONST - it is handled above in visitClassLike. |
- return; |
- } |
- this.emit('@'); |
- this.visit(d.expression); |
- }); |
- } |
- |
private visitDeclarationMetadata(decl: ts.Declaration) { |
- this.visitDecorators(decl.decorators); |
this.visitEachIfPresent(decl.modifiers); |
- if (this.hasFlag(decl.modifiers, ts.NodeFlags.Protected)) { |
- this.reportError(decl, 'protected declarations are unsupported'); |
- return; |
- } |
- if (!this.enforceUnderscoreConventions) return; |
- // Early return in case this is a decl with no name, such as a constructor |
- if (!decl.name) return; |
- let name = base.ident(decl.name); |
- if (!name) return; |
- let isPrivate = this.hasFlag(decl.modifiers, ts.NodeFlags.Private); |
- let matchesPrivate = !!name.match(/^_/); |
- if (isPrivate && !matchesPrivate) { |
- this.reportError(decl, 'private members must be prefixed with "_"'); |
- } |
- if (!isPrivate && matchesPrivate) { |
- this.reportError(decl, 'public members must not be prefixed with "_"'); |
- } |
- } |
- |
- private visitNamedParameter(paramDecl: ts.ParameterDeclaration) { |
- this.visitDecorators(paramDecl.decorators); |
- let bp = <ts.BindingPattern>paramDecl.name; |
- let propertyTypes = this.fc.resolvePropertyTypes(paramDecl.type); |
- let initMap = this.getInitializers(paramDecl); |
- this.emit('{'); |
- for (let i = 0; i < bp.elements.length; i++) { |
- let elem = bp.elements[i]; |
- let propDecl = propertyTypes[base.ident(elem.name)]; |
- if (propDecl && propDecl.type) this.visit(propDecl.type); |
- this.visit(elem.name); |
- if (elem.initializer && initMap[base.ident(elem.name)]) { |
- this.reportError(elem, 'cannot have both an inner and outer initializer'); |
- } |
- let init = elem.initializer || initMap[base.ident(elem.name)]; |
- if (init) { |
- this.emit(':'); |
- this.visit(init); |
- } |
- if (i + 1 < bp.elements.length) this.emit(','); |
- } |
- this.emit('}'); |
- } |
- |
- private getInitializers(paramDecl: ts.ParameterDeclaration) { |
- let res: ts.Map<ts.Expression> = {}; |
- if (!paramDecl.initializer) return res; |
- if (paramDecl.initializer.kind !== ts.SyntaxKind.ObjectLiteralExpression) { |
- this.reportError(paramDecl, 'initializers for named parameters must be object literals'); |
- return res; |
- } |
- for (let i of (<ts.ObjectLiteralExpression>paramDecl.initializer).properties) { |
- if (i.kind !== ts.SyntaxKind.PropertyAssignment) { |
- this.reportError(i, 'named parameter initializers must be properties, got ' + i.kind); |
- continue; |
- } |
- let ole = <ts.PropertyAssignment>i; |
- res[base.ident(ole.name)] = ole.initializer; |
+ switch (decl.kind) { |
+ case ts.SyntaxKind.Constructor: |
+ case ts.SyntaxKind.ConstructSignature: |
+ this.emit('external factory'); |
+ break; |
+ case ts.SyntaxKind.ArrowFunction: |
+ case ts.SyntaxKind.CallSignature: |
+ case ts.SyntaxKind.MethodDeclaration: |
+ case ts.SyntaxKind.SetAccessor: |
+ case ts.SyntaxKind.GetAccessor: |
+ case ts.SyntaxKind.MethodSignature: |
+ case ts.SyntaxKind.PropertySignature: |
+ case ts.SyntaxKind.FunctionDeclaration: |
+ if (!base.getEnclosingClass(decl)) { |
+ this.maybeEmitJsAnnotation(decl); |
+ } |
+ this.emit('external'); |
+ break; |
+ default: |
+ throw 'Unexpected declaration kind:' + decl.kind; |
} |
- return res; |
} |
/** |
@@ -503,11 +809,11 @@ export default class DeclarationTranspiler extends base.TranspilerBase { |
} |
this.emit(name); |
if (typeParameters) { |
- this.emit('<'); |
+ this.emitNoSpace('<'); |
this.visitList(typeParameters); |
- this.emit('>'); |
+ this.emitNoSpace('>'); |
} |
this.visitParameters(signature.parameters); |
- this.emit(';'); |
+ this.emitNoSpace(';'); |
} |
} |