Index: lib/facade_converter.ts |
diff --git a/lib/facade_converter.ts b/lib/facade_converter.ts |
index 76346c4e9c4df4e5ee823d0c03bee7a18fc9791f..c43dda92e5216def334104a373d7d09e45d13ab0 100644 |
--- a/lib/facade_converter.ts |
+++ b/lib/facade_converter.ts |
@@ -1,40 +1,197 @@ |
-import * as base from './base'; |
import * as ts from 'typescript'; |
+ |
+import * as base from './base'; |
+import {Set} from './base'; |
+import {DART_LIBRARIES_FOR_BROWSER_TYPES, TS_TO_DART_TYPENAMES} from './dart_libraries_for_browser_types'; |
import {Transpiler} from './main'; |
+import {MergedType} from './merge'; |
type CallHandler = (c: ts.CallExpression, context: ts.Expression) => void; |
type PropertyHandler = (c: ts.PropertyAccessExpression) => void; |
-type Set = { |
- [s: string]: boolean |
-}; |
const FACADE_DEBUG = false; |
- |
const FACADE_NODE_MODULES_PREFIX = /^(\.\.\/)*node_modules\//; |
-function merge(...args: {[key: string]: any}[]): {[key: string]: any} { |
- let returnObject: {[key: string]: any} = {}; |
- for (let arg of args) { |
- for (let key of Object.getOwnPropertyNames(arg)) { |
- returnObject[key] = arg[key]; |
+// These constants must be kept in sync with package:func/func.dart which |
+// provides a cannonical set of typedefs defining commonly used function types |
+// to simplify specifying function types in Dart. |
+const MAX_DART_FUNC_ACTION_PARAMETERS = 4; |
+const MAX_DART_FUNC_ACTION_PARAMETERS_OPTIONAL = 1; |
+ |
+/** |
+ * Prefix to add to a variable name that leaves the JS name referenced |
+ * unchanged. |
+ */ |
+const DART_RESERVED_NAME_PREFIX = 'JS$'; |
+ |
+export function fixupIdentifierName(text: string): string { |
+ return (FacadeConverter.DART_RESERVED_WORDS.indexOf(text) !== -1 || |
+ FacadeConverter.DART_OTHER_KEYWORDS.indexOf(text) !== -1 || text[0] === '_') ? |
+ DART_RESERVED_NAME_PREFIX + text : |
+ text; |
+} |
+ |
+function numOptionalParameters(parameters: ts.NodeArray<ts.ParameterDeclaration>): number { |
+ for (let i = 0; i < parameters.length; ++i) { |
+ if (parameters[i].questionToken) return parameters.length - i; |
+ } |
+ return 0; |
+} |
+ |
+function hasVarArgs(parameters: ts.NodeArray<ts.ParameterDeclaration>): boolean { |
+ for (let i = 0; i < parameters.length; ++i) { |
+ if (parameters[i].dotDotDotToken) return true; |
+ } |
+ return false; |
+} |
+ |
+/** |
+ * Generates the JavaScript expression required to reference the node |
+ * from the global context. InterfaceDeclarations do not technically correspond |
+ * to actual JavaScript objects but we still generate a reference path for them |
+ * so that we have a guaranteed unique name. |
+ * |
+ * Example JS path: |
+ * module m1 { |
+ * module m2 { |
+ * class foo { } |
+ * } |
+ * } |
+ * Path: m1.m2.foo |
+ */ |
+function fullJsPath(node: base.NamedDeclaration): string { |
+ let parts: Array<string> = [base.ident(node.name)]; |
+ let p: ts.Node = node.parent; |
+ while (p != null) { |
+ let kind = p.kind; |
+ if (kind === ts.SyntaxKind.ModuleDeclaration || kind === ts.SyntaxKind.InterfaceDeclaration || |
+ kind === ts.SyntaxKind.ClassDeclaration) { |
+ parts.unshift(base.ident((<base.NamedDeclaration>p).name)); |
} |
+ p = p.parent; |
+ } |
+ return parts.join('.'); |
+} |
+ |
+class DartNameRecord { |
+ name: string; |
+ constructor(private node: ts.Node, name: string, private library: DartLibrary) { |
+ this.name = name; |
} |
- return returnObject; |
} |
+export class DartLibrary { |
+ constructor(private fileName: string) { this.usedNames = {}; } |
+ |
+ /** |
+ * @returns {boolean} whether the name was added. |
+ */ |
+ addName(name: string): boolean { |
+ if (Object.prototype.hasOwnProperty.call(this.usedNames, name)) { |
+ return false; |
+ } |
+ this.usedNames[name] = true; |
+ return true; |
+ } |
+ |
+ private usedNames: Set; |
+} |
+ |
+export class NameRewriter { |
+ private dartTypes: ts.Map<DartNameRecord> = {}; |
+ // TODO(jacobr): use libraries to track what library imports need to be |
+ // emitted. Complex cases such as ts.SyntaxKind.TypeReference |
+ // where emitting the type could require emitting imports to libraries not |
+ // specified in the imports listed in TypeScript. Additionally, d.ts files |
+ // can contain multiple modules and for readability we may want an option |
+ // to emit each of those modules as a separate Dart library. That would also |
+ // require tracking what external libraries are referenced. |
+ private libraries: ts.Map<DartLibrary> = {}; |
+ |
+ private computeName(node: base.NamedDeclaration): DartNameRecord { |
+ let fullPath = fullJsPath(node); |
+ if (Object.prototype.hasOwnProperty.call(this.dartTypes, fullPath)) { |
+ return this.dartTypes[fullPath]; |
+ } |
+ let sourceFile = <ts.SourceFile>base.getAncestor(node, ts.SyntaxKind.SourceFile); |
+ let fileName = sourceFile.fileName; |
+ let library: DartLibrary; |
+ if (Object.prototype.hasOwnProperty.call(this.libraries, fileName)) { |
+ library = this.libraries[fileName]; |
+ } else { |
+ library = new DartLibrary(fileName); |
+ this.libraries[fileName] = library; |
+ } |
+ let parts = fullPath.split('.'); |
+ for (let i = parts.length - 1; i >= 0; i--) { |
+ // Find a unique name by including more of the module hierarchy in the |
+ // name. This is an arbitrary but hopefully unsurprising scheme to |
+ // generate unique names. There may be classes or members with conflicting |
+ // names due to a single d.ts file containing multiple modules. |
+ // TODO(jacobr): we should suppress this behavior outside of JS Interop |
+ // mode and instead generate a compile error if there are conflicting |
+ // names. |
+ let candidateName = fixupIdentifierName(parts.slice(i).join('_')); |
+ if (library.addName(candidateName)) { |
+ // Able to add name to library. |
+ let ret = new DartNameRecord(node, candidateName, library); |
+ this.dartTypes[fullPath] = ret; |
+ return ret; |
+ } |
+ } |
+ |
+ // Usually the module name prefixes should be sufficient to disambiguate |
+ // names but sometimes we need to add a numeric prefix as well to |
+ // disambiguate. We could alternately append the full module prefix as well |
+ // to make the name choice completely unsurprising albeit even uglier. |
+ // This case should be very rarely hit. |
+ let i = 2; |
+ while (true) { |
+ let candidateName = parts[parts.length - 1] + i; |
+ if (library.addName(candidateName)) { |
+ // Able to add name to library. |
+ let ret = new DartNameRecord(node, candidateName, library); |
+ this.dartTypes[fullPath] = ret; |
+ return ret; |
+ } |
+ i++; |
+ } |
+ } |
+ |
+ lookupName(node: base.NamedDeclaration, context: ts.Node) { return this.computeName(node).name; } |
+} |
export class FacadeConverter extends base.TranspilerBase { |
private tc: ts.TypeChecker; |
- private candidateProperties: {[propertyName: string]: boolean} = {}; |
+ // For the Dart keyword list see |
+ // https://www.dartlang.org/docs/dart-up-and-running/ch02.html#keywords |
+ static DART_RESERVED_WORDS = |
+ ('assert break case catch class const continue default do else enum extends false final ' + |
+ 'finally for if in is new null rethrow return super switch this throw true try let void ' + |
+ 'while with') |
+ .split(/ /); |
+ |
+ // These are the built-in and limited keywords. |
+ static DART_OTHER_KEYWORDS = |
+ ('abstract as async await deferred dynamic export external factory get implements import ' + |
+ 'library operator part set static sync typedef yield call') |
+ .split(/ /); |
+ |
private candidateTypes: {[typeName: string]: boolean} = {}; |
private typingsRootRegex: RegExp; |
private genericMethodDeclDepth = 0; |
- constructor(transpiler: Transpiler, typingsRoot = '') { |
+ constructor( |
+ transpiler: Transpiler, typingsRoot = '', private nameRewriter: NameRewriter, |
+ private useHtml: boolean) { |
super(transpiler); |
- this.extractPropertyNames(this.callHandlers, this.candidateProperties); |
- this.extractPropertyNames(this.propertyHandlers, this.candidateProperties); |
- this.extractPropertyNames(this.TS_TO_DART_TYPENAMES, this.candidateTypes); |
+ if (useHtml) { |
+ this.extractPropertyNames(TS_TO_DART_TYPENAMES, this.candidateTypes); |
+ Object.keys(DART_LIBRARIES_FOR_BROWSER_TYPES) |
+ .forEach((propName) => this.candidateTypes[propName] = true); |
+ } else { |
+ this.extractPropertyNames(TS_TO_DART_TYPENAMES, this.candidateTypes); |
+ } |
this.typingsRootRegex = new RegExp('^' + typingsRoot.replace('.', '\\.')); |
} |
@@ -42,6 +199,9 @@ export class FacadeConverter extends base.TranspilerBase { |
private extractPropertyNames(m: ts.Map<ts.Map<any>>, candidates: {[k: string]: boolean}) { |
for (let fileName of Object.keys(m)) { |
const file = m[fileName]; |
+ if (file === undefined) { |
+ return; |
+ } |
Object.keys(file) |
.map((propName) => propName.substring(propName.lastIndexOf('.') + 1)) |
.forEach((propName) => candidates[propName] = true); |
@@ -50,63 +210,6 @@ export class FacadeConverter extends base.TranspilerBase { |
setTypeChecker(tc: ts.TypeChecker) { this.tc = tc; } |
- maybeHandleCall(c: ts.CallExpression): boolean { |
- if (!this.tc) return false; |
- let {context, symbol} = this.getCallInformation(c); |
- if (!symbol) { |
- // getCallInformation returns a symbol if we understand this call. |
- return false; |
- } |
- let handler = this.getHandler(c, symbol, this.callHandlers); |
- return handler && !handler(c, context); |
- } |
- |
- handlePropertyAccess(pa: ts.PropertyAccessExpression): boolean { |
- if (!this.tc) return; |
- let ident = pa.name.text; |
- if (!this.candidateProperties.hasOwnProperty(ident)) return false; |
- let symbol = this.tc.getSymbolAtLocation(pa.name); |
- if (!symbol) { |
- this.reportMissingType(pa, ident); |
- return false; |
- } |
- |
- let handler = this.getHandler(pa, symbol, this.propertyHandlers); |
- return handler && !handler(pa); |
- } |
- |
- /** |
- * Searches for type references that require extra imports and emits the imports as necessary. |
- */ |
- emitExtraImports(sourceFile: ts.SourceFile) { |
- let libraries = <ts.Map<string>>{ |
- 'XMLHttpRequest': 'dart:html', |
- 'KeyboardEvent': 'dart:html', |
- 'Uint8Array': 'dart:typed_arrays', |
- 'ArrayBuffer': 'dart:typed_arrays', |
- 'Promise': 'dart:async', |
- }; |
- let emitted: Set = {}; |
- this.emitImports(sourceFile, libraries, emitted, sourceFile); |
- } |
- |
- private emitImports( |
- n: ts.Node, libraries: ts.Map<string>, emitted: Set, sourceFile: ts.SourceFile): void { |
- if (n.kind === ts.SyntaxKind.TypeReference) { |
- let type = base.ident((<ts.TypeReferenceNode>n).typeName); |
- if (libraries.hasOwnProperty(type)) { |
- let toEmit = libraries[type]; |
- if (!emitted[toEmit]) { |
- this.emit(`import "${toEmit}";`); |
- emitted[toEmit] = true; |
- } |
- } |
- } |
- |
- n.getChildren(sourceFile) |
- .forEach((child: ts.Node) => this.emitImports(child, libraries, emitted, sourceFile)); |
- } |
- |
pushTypeParameterNames(n: ts.FunctionLikeDeclaration) { |
if (!n.typeParameters) return; |
this.genericMethodDeclDepth++; |
@@ -137,11 +240,12 @@ export class FacadeConverter extends base.TranspilerBase { |
} |
/** |
- * The Dart Development Compiler (DDC) has a syntax extension that uses comments to emulate |
- * generic methods in Dart. ts2dart has to hack around this and keep track of which type names |
- * in the current scope are actually DDC type parameters and need to be emitted in comments. |
+ * The Dart analyzer has a syntax extension that uses comments to emulate |
+ * generic methods in Dart. We work around this and keep track of which type |
+ * names in the current scope need to be emitted in comments. |
* |
- * TODO(martinprobst): Remove this once the DDC hack has made it into Dart proper. |
+ * TODO(jacobr): Remove this once all Dart implementations support generic |
+ * methods. |
*/ |
private isGenericMethodTypeParameterName(name: ts.EntityName): boolean { |
// Avoid checking this unless needed. |
@@ -158,6 +262,169 @@ export class FacadeConverter extends base.TranspilerBase { |
return symbol.declarations.some(d => d.parent.kind === ts.SyntaxKind.FunctionDeclaration); |
} |
+ generateTypeList(types: ts.TypeNode[], insideComment: boolean, seperator = ','): string { |
+ let that = this; |
+ return types.map((type) => { return that.generateDartTypeName(type, insideComment); }) |
+ .join(seperator); |
+ } |
+ |
+ maybeGenerateTypeArguments( |
+ n: {typeArguments?: ts.NodeArray<ts.TypeNode>}, insideComment: boolean): string { |
+ if (!n.typeArguments) return ''; |
+ return '<' + this.generateTypeList(n.typeArguments, insideComment) + '>'; |
+ } |
+ |
+ generateDartTypeName(node: ts.TypeNode, insideComment: boolean): string { |
+ let name: string; |
+ let comment: string; |
+ if (!node) { |
+ return 'dynamic'; |
+ } |
+ switch (node.kind) { |
+ case ts.SyntaxKind.TypeQuery: |
+ let query = <ts.TypeQueryNode>node; |
+ name = 'dynamic'; |
+ name += '/* Dart does not support TypeQuery: typeof ' + base.ident(query.exprName) + ' */'; |
+ break; |
+ case ts.SyntaxKind.LastTypeNode: |
+ let type = (node as ts.ParenthesizedTypeNode).type; |
+ if (!type) { |
+ // This case occurs for String literal types |
+ comment = node.getText(); |
+ // TODO(jacobr): find a better way to detect string literal types. |
+ name = comment[0] === '"' ? 'String' : 'dynamic'; |
+ break; |
+ } |
+ return this.generateDartTypeName(type, insideComment); |
+ case ts.SyntaxKind.TypePredicate: |
+ return this.generateDartTypeName((node as ts.TypePredicateNode).type, insideComment); |
+ case ts.SyntaxKind.TupleType: |
+ let tuple = <ts.TupleTypeNode>node; |
+ name = 'List<'; |
+ let mergedType = new MergedType(this); |
+ tuple.elementTypes.forEach((t) => mergedType.merge(t)); |
+ name += this.generateDartTypeName(mergedType.toTypeNode(), insideComment); |
+ name += '>'; |
+ comment = 'Tuple<' + this.generateTypeList(tuple.elementTypes, insideComment) + '>'; |
+ break; |
+ case ts.SyntaxKind.UnionType: |
+ let union = <ts.UnionTypeNode>node; |
+ // TODO(jacobr): this isn't fundamentally JS Interop specific but we |
+ // choose to be more aggressive at finding a useful value for the |
+ // union when in JS Interop mode while otherwise we expect that union |
+ // types will not be used extensively. |
+ let simpleType = this.toSimpleDartType(union.types); |
+ if (simpleType) { |
+ name = this.generateDartTypeName(simpleType, insideComment); |
+ } else { |
+ name = 'dynamic'; |
+ } |
+ let types = union.types; |
+ comment = this.generateTypeList(types, true, '|'); |
+ break; |
+ case ts.SyntaxKind.TypePredicate: |
+ return this.generateDartTypeName((node as ts.TypePredicateNode).type, insideComment); |
+ case ts.SyntaxKind.TypeReference: |
+ let typeRef = <ts.TypeReferenceNode>node; |
+ name = this.generateDartName(typeRef.typeName, insideComment) + |
+ this.maybeGenerateTypeArguments(typeRef, insideComment); |
+ break; |
+ case ts.SyntaxKind.TypeLiteral: |
+ let members = (<ts.TypeLiteralNode>node).members; |
+ if (members.length === 1 && members[0].kind === ts.SyntaxKind.IndexSignature) { |
+ let indexSig = <ts.IndexSignatureDeclaration>(members[0]); |
+ if (indexSig.parameters.length > 1) { |
+ this.reportError(indexSig, 'Expected an index signature to have a single parameter'); |
+ } |
+ // Unfortunately for JS interop, we cannot treat JS Objects as Dart |
+ // Map objects. We could treat them as JSMap<indexSig.type> |
+ // if we define a base JSMap type that is Map like but not actually |
+ // a map. |
+ name = 'dynamic'; |
+ comment = 'JSMap of <' + this.generateDartTypeName(indexSig.parameters[0].type, true) + |
+ ',' + this.generateDartTypeName(indexSig.type, true) + '>'; |
+ } else { |
+ name = 'dynamic'; |
+ comment = node.getText(); |
+ } |
+ break; |
+ case ts.SyntaxKind.FunctionType: |
+ let callSignature = <ts.FunctionOrConstructorTypeNode>node; |
+ let parameters = callSignature.parameters; |
+ |
+ // Use a function signature from package:func where possible. |
+ let numOptional = numOptionalParameters(parameters); |
+ let isVoid = callSignature.type && callSignature.type.kind === ts.SyntaxKind.VoidKeyword; |
+ if (parameters.length <= MAX_DART_FUNC_ACTION_PARAMETERS && |
+ numOptional <= MAX_DART_FUNC_ACTION_PARAMETERS_OPTIONAL && !hasVarArgs(parameters)) { |
+ this.emitImport('package:func/func.dart'); |
+ let typeDefName = (isVoid) ? 'VoidFunc' : 'Func'; |
+ typeDefName += parameters.length; |
+ if (numOptional > 0) { |
+ typeDefName += 'Opt' + numOptional; |
+ } |
+ name = typeDefName; |
+ let numArgs = parameters.length + (isVoid ? 0 : 1); |
+ if (numArgs > 0) { |
+ name += '<'; |
+ } |
+ let isFirst = true; |
+ for (let i = 0; i < parameters.length; ++i) { |
+ if (isFirst) { |
+ isFirst = false; |
+ } else { |
+ name += ', '; |
+ } |
+ name += this.generateDartTypeName(parameters[i].type, insideComment); |
+ } |
+ if (!isVoid) { |
+ if (!isFirst) { |
+ name += ', '; |
+ } |
+ name += this.generateDartTypeName(callSignature.type, insideComment); |
+ } |
+ if (numArgs > 0) { |
+ name += '>'; |
+ } |
+ } else { |
+ name = 'Function'; |
+ if (node.getSourceFile()) { |
+ comment = node.getText(); |
+ } |
+ } |
+ break; |
+ case ts.SyntaxKind.ArrayType: |
+ name = 'List' + |
+ '<' + this.generateDartTypeName((<ts.ArrayTypeNode>node).elementType, insideComment) + |
+ '>'; |
+ break; |
+ case ts.SyntaxKind.NumberKeyword: |
+ name = 'num'; |
+ break; |
+ case ts.SyntaxKind.StringLiteral: |
+ case ts.SyntaxKind.StringKeyword: |
+ name = 'String'; |
+ break; |
+ case ts.SyntaxKind.VoidKeyword: |
+ name = 'void'; |
+ break; |
+ case ts.SyntaxKind.BooleanKeyword: |
+ name = 'bool'; |
+ break; |
+ case ts.SyntaxKind.AnyKeyword: |
+ name = 'dynamic'; |
+ break; |
+ default: |
+ this.reportError(node, 'Unexpected TypeNode kind'); |
+ } |
+ if (name == null) { |
+ name = 'XXX NULLNAME'; |
+ } |
+ |
+ name = name.trim(); |
+ return base.formatType(name, comment, insideComment); |
+ } |
+ |
visitTypeName(typeName: ts.EntityName) { |
if (typeName.kind !== ts.SyntaxKind.Identifier) { |
this.visit(typeName); |
@@ -167,104 +434,292 @@ export class FacadeConverter extends base.TranspilerBase { |
if (this.isGenericMethodTypeParameterName(typeName)) { |
// DDC generic methods hack - all names that are type parameters to generic methods have to be |
// emitted in comments. |
- this.emit('dynamic/*='); |
- this.emit(ident); |
- this.emit('*/'); |
+ this.emitType('dynamic', ident); |
return; |
} |
- if (this.candidateTypes.hasOwnProperty(ident) && this.tc) { |
- let symbol = this.tc.getSymbolAtLocation(typeName); |
- if (!symbol) { |
- this.reportMissingType(typeName, ident); |
- return; |
- } |
- let fileAndName = this.getFileAndName(typeName, symbol); |
- if (fileAndName) { |
- let fileSubs = this.TS_TO_DART_TYPENAMES[fileAndName.fileName]; |
- if (fileSubs && fileSubs.hasOwnProperty(fileAndName.qname)) { |
- this.emit(fileSubs[fileAndName.qname]); |
- return; |
- } |
+ let custom = this.lookupCustomDartTypeName(<ts.Identifier>typeName, this.insideCodeComment); |
+ if (custom) { |
+ if (custom.comment) { |
+ this.emitType(custom.name, custom.comment); |
+ } else { |
+ this.emit(custom.name); |
} |
+ } else { |
+ this.visit(typeName); |
} |
- this.emit(ident); |
} |
- shouldEmitNew(c: ts.CallExpression): boolean { |
- if (!this.tc) return true; |
- |
- let ci = this.getCallInformation(c); |
- let symbol = ci.symbol; |
- // getCallInformation returns a symbol if we understand this call. |
- if (!symbol) return true; |
+ getSymbolDeclaration(symbol: ts.Symbol, n?: ts.Node): ts.Declaration { |
+ if (!symbol) return null; |
+ let decl = symbol.valueDeclaration; |
+ if (!decl) { |
+ // In the case of a pure declaration with no assignment, there is no value declared. |
+ // Just grab the first declaration, hoping it is declared once. |
+ if (!symbol.declarations || symbol.declarations.length === 0) { |
+ this.reportError(n, 'no declarations for symbol ' + symbol.name); |
+ return; |
+ } |
+ decl = symbol.declarations[0]; |
+ } |
+ return decl; |
+ } |
- let loc = this.getFileAndName(c, symbol); |
- if (!loc) return true; |
- let {fileName, qname} = loc; |
- let fileSubs = this.callHandlerReplaceNew[fileName]; |
- if (!fileSubs) return true; |
- return !fileSubs[qname]; |
+ generateDartName(identifier: ts.EntityName, insideComment: boolean): string { |
+ let ret = this.lookupCustomDartTypeName(identifier, insideComment); |
+ if (ret) return base.formatType(ret.name, ret.comment, insideComment); |
+ // TODO(jacobr): handle library import prefixes better. This generally works |
+ // but is somewhat fragile. |
+ return base.ident(identifier); |
} |
- private getCallInformation(c: ts.CallExpression): {context?: ts.Expression, symbol?: ts.Symbol} { |
+ /** |
+ * Returns null if declaration cannot be found or is not valid in Dart. |
+ */ |
+ getDeclaration(identifier: ts.EntityName): ts.Declaration { |
let symbol: ts.Symbol; |
- let context: ts.Expression; |
- let ident: string; |
- let expr = c.expression; |
+ if (!this.tc) return null; |
+ |
+ symbol = this.tc.getSymbolAtLocation(identifier); |
+ let declaration = this.getSymbolDeclaration(symbol, identifier); |
+ if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) { |
+ let kind = declaration.parent.kind; |
+ // Only kinds of TypeParameters supported by Dart. |
+ if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.InterfaceDeclaration) { |
+ return null; |
+ } |
+ } |
+ return declaration; |
+ } |
- if (expr.kind === ts.SyntaxKind.Identifier) { |
- // Function call. |
- ident = base.ident(expr); |
- if (!this.candidateProperties.hasOwnProperty(ident)) return {}; |
- symbol = this.tc.getSymbolAtLocation(expr); |
- if (FACADE_DEBUG) console.error('s:', symbol); |
+ /** |
+ * Returns a custom Dart type name or null if the type isn't a custom Dart |
+ * type. |
+ */ |
+ lookupCustomDartTypeName(identifier: ts.EntityName, insideComment: boolean): |
+ {name?: string, comment?: string, keep?: boolean} { |
+ let ident = base.ident(identifier); |
+ let symbol: ts.Symbol; |
+ if (!this.tc) return null; |
+ |
+ symbol = this.tc.getSymbolAtLocation(identifier); |
+ let declaration = this.getSymbolDeclaration(symbol, identifier); |
+ if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) { |
+ let kind = declaration.parent.kind; |
+ // Only kinds of TypeParameters supported by Dart. |
+ if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.InterfaceDeclaration) { |
+ return {name: 'dynamic', comment: ident}; |
+ } |
+ } |
+ if (this.candidateTypes.hasOwnProperty(ident)) { |
if (!symbol) { |
- this.reportMissingType(c, ident); |
- return {}; |
+ return null; |
} |
- context = null; |
- } else if (expr.kind === ts.SyntaxKind.PropertyAccessExpression) { |
- // Method call. |
- let pa = <ts.PropertyAccessExpression>expr; |
- ident = base.ident(pa.name); |
- if (!this.candidateProperties.hasOwnProperty(ident)) return {}; |
+ let fileAndName = this.getFileAndName(identifier, symbol); |
- symbol = this.tc.getSymbolAtLocation(pa); |
- if (FACADE_DEBUG) console.error('s:', symbol); |
+ if (fileAndName) { |
+ let fileSubs = TS_TO_DART_TYPENAMES[fileAndName.fileName]; |
+ if (fileSubs) { |
+ let dartBrowserType = DART_LIBRARIES_FOR_BROWSER_TYPES.hasOwnProperty(fileAndName.qname); |
+ if (dartBrowserType) { |
+ this.emitImport(DART_LIBRARIES_FOR_BROWSER_TYPES[fileAndName.qname]); |
+ } |
+ if (fileSubs.hasOwnProperty(fileAndName.qname)) { |
+ return {name: fileSubs[fileAndName.qname]}; |
+ } |
+ if (dartBrowserType) { |
+ // Not a rename but has a dart core libraries definition. |
+ return {name: fileAndName.qname}; |
+ } |
+ } |
+ } |
+ } |
+ if (symbol) { |
+ if (symbol.flags & ts.SymbolFlags.Enum) { |
+ // We can't treat JavaScript enums as Dart enums in this case. |
+ return {name: 'num', comment: ident}; |
+ } |
+ // TODO(jacobr): we could choose to only support type alais declarations |
+ // for JS interop but it seems handling type alaises is generally helpful |
+ // without much risk of generating confusing Dart code. |
+ if (declaration.kind === ts.SyntaxKind.TypeAliasDeclaration) { |
+ let alias = <ts.TypeAliasDeclaration>declaration; |
+ if (alias.typeParameters) { |
+ // We can handle this case but currently do not. |
+ this.reportError(declaration, 'Type parameters for type alaises are not supported'); |
+ } |
+ return {name: this.generateDartTypeName(alias.type, insideComment)}; |
+ } |
- // Error will be reported by PropertyAccess handling below. |
- if (!symbol) return {}; |
+ let kind = declaration.kind; |
+ if (kind === ts.SyntaxKind.ClassDeclaration || kind === ts.SyntaxKind.InterfaceDeclaration || |
+ kind === ts.SyntaxKind.VariableDeclaration || |
+ kind === ts.SyntaxKind.PropertyDeclaration || |
+ kind === ts.SyntaxKind.FunctionDeclaration) { |
+ let name = this.nameRewriter.lookupName(<base.NamedDeclaration>declaration, identifier); |
+ if (kind === ts.SyntaxKind.InterfaceDeclaration && |
+ base.isFunctionTypedefLikeInterface(<ts.InterfaceDeclaration>declaration) && |
+ base.getAncestor(identifier, ts.SyntaxKind.HeritageClause)) { |
+ // TODO(jacobr): we need to specify a specific call method for this |
+ // case if we want to get the most from Dart type checking. |
+ return {name: 'Function', comment: name}; |
+ } |
+ return {name: name, keep: true}; |
+ } |
+ } |
+ return null; |
+ } |
- context = pa.expression; |
+ // TODO(jacobr): performance of this method could easily be optimized. |
+ /** |
+ * This method works around the lack of Dart support for union types |
+ * generating a valid Dart type that satisfies all the types passed in. |
+ */ |
+ toSimpleDartType(types: Array<ts.TypeNode>) { |
+ // We use MergeType to ensure that we have already deduped types that are |
+ // equivalent even if they aren't obviously identical. |
+ // MergedType will also follow typed aliases, etc which allows us to avoid |
+ // including that logic here as well. |
+ let mergedType = new MergedType(this); |
+ types.forEach((type) => { mergedType.merge(type); }); |
+ let merged = mergedType.toTypeNode(); |
+ if (merged.kind === ts.SyntaxKind.UnionType) { |
+ // For union types find a Dart type that satisfies all the types. |
+ types = (<ts.UnionTypeNode>merged).types; |
+ /** |
+ * Generate a common base type for an array of types. |
+ * The implemented is currently incomplete often returning null when there |
+ * might really be a valid common base type. |
+ */ |
+ let common: ts.TypeNode = types[0]; |
+ for (let i = 1; i < types.length && common != null; ++i) { |
+ let type = types[i]; |
+ if (common !== type) { |
+ if (base.isCallableType(common, this.tc) && base.isCallableType(type, this.tc)) { |
+ // Fall back to a generic Function type if both types are Function. |
+ let fn = <ts.FunctionOrConstructorTypeNode>ts.createNode(ts.SyntaxKind.FunctionType); |
+ fn.parameters = <ts.NodeArray<ts.ParameterDeclaration>>[]; |
+ let parameter = <ts.ParameterDeclaration>ts.createNode(ts.SyntaxKind.Parameter); |
+ parameter.dotDotDotToken = ts.createNode(ts.SyntaxKind.DotDotDotToken); |
+ let name = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); |
+ name.text = 'args'; |
+ fn.parameters.push(parameter); |
+ common = fn; |
+ } else { |
+ switch (type.kind) { |
+ case ts.SyntaxKind.ArrayType: |
+ if (common.kind !== ts.SyntaxKind.ArrayType) { |
+ return null; |
+ } |
+ let array = <ts.ArrayTypeNode>ts.createNode(ts.SyntaxKind.ArrayType); |
+ array.elementType = this.toSimpleDartType([ |
+ (common as ts.ArrayTypeNode).elementType, (type as ts.ArrayTypeNode).elementType |
+ ]); |
+ common = array; |
+ break; |
+ // case ts.SyntaxKind |
+ case ts.SyntaxKind.TypeReference: |
+ if (common.kind !== ts.SyntaxKind.TypeReference) { |
+ return null; |
+ } |
+ common = this.commonSupertype(common, type); |
+ break; |
+ |
+ default: |
+ return null; |
+ } |
+ } |
+ } |
+ } |
+ return common; |
} |
- return {context, symbol}; |
+ return merged; |
+ } |
+ |
+ toTypeNode(type: ts.Type): ts.TypeNode { |
+ if (!type) return null; |
+ let symbol = type.getSymbol(); |
+ if (!symbol) return null; |
+ |
+ let referenceType = <ts.TypeReferenceNode>ts.createNode(ts.SyntaxKind.TypeReference); |
+ // TODO(jacobr): property need to prefix the name better. |
+ referenceType.typeName = this.createEntityName(symbol); |
+ referenceType.typeName.parent = referenceType; |
+ return referenceType; |
} |
- private getHandler<T>(n: ts.Node, symbol: ts.Symbol, m: ts.Map<ts.Map<T>>): T { |
- let loc = this.getFileAndName(n, symbol); |
- if (!loc) return null; |
- let {fileName, qname} = loc; |
- let fileSubs = m[fileName]; |
- if (!fileSubs) return null; |
- return fileSubs[qname]; |
+ createEntityName(symbol: ts.Symbol): ts.EntityName { |
+ let parts = this.tc.getFullyQualifiedName(symbol).split('.'); |
+ let identifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); |
+ identifier.text = parts[parts.length - 1]; |
+ // TODO(jacobr): do we need to include all parts in the entity name? |
+ return identifier; |
+ } |
+ |
+ safeGetBaseTypes(type: ts.InterfaceType): ts.ObjectType[] { |
+ // For an unknown, calling TypeChecker.getBaseTypes on an interface |
+ // that is a typedef like interface causes the typescript compiler to stack |
+ // overflow. Not sure if this is a bug in the typescript compiler or I am |
+ // missing something obvious. |
+ let declaration = base.getDeclaration(type) as ts.InterfaceDeclaration; |
+ if (base.isFunctionTypedefLikeInterface(declaration)) { |
+ return []; |
+ } |
+ return this.tc.getBaseTypes(type); |
+ } |
+ |
+ // TODO(jacobr): all of these subtype checks are fragile and are likely a |
+ // mistake. We would be better off handling subtype relationships in Dart |
+ // where we could reuse an existing Dart type system. |
+ checkTypeSubtypeOf(source: ts.Type, target: ts.Type) { |
+ if (source === target) return true; |
+ if (!(source.flags & ts.TypeFlags.Interface)) return false; |
+ let baseTypes = this.safeGetBaseTypes(source as ts.InterfaceType); |
+ for (let i = 0; i < baseTypes.length; ++i) { |
+ if (baseTypes[i] === target) return true; |
+ } |
+ return false; |
+ } |
+ |
+ commonSupertype(nodeA: ts.TypeNode, nodeB: ts.TypeNode): ts.TypeNode { |
+ if (nodeA == null || nodeB == null) return null; |
+ return this.toTypeNode(this.getCommonSupertype( |
+ this.tc.getTypeAtLocation(nodeA), this.tc.getTypeAtLocation(nodeB))); |
+ } |
+ |
+ getCommonSupertype(a: ts.Type, b: ts.Type): ts.Type { |
+ if (a === b) return a; |
+ // This logic was probably a mistake. It adds a lot of complexity and we can |
+ // do better performing these calculations in the Dart analyzer based |
+ // directly on the union types specified in comments. |
+ return null; |
+ /* |
+ if (!(a.flags & ts.TypeFlags.Interface) || !(b.flags & ts.TypeFlags.Interface)) { |
+ return null; |
+ } |
+ |
+ let bestCommonSuperType: ts.Type = null; |
+ let candidatesA = this.safeGetBaseTypes(a as ts.InterfaceType); |
+ candidatesA.push(a); |
+ |
+ for (let i = 0; i < candidatesA.length; ++i) { |
+ let type = candidatesA[i]; |
+ if (this.checkTypeSubtypeOf(b, type)) { |
+ if (!bestCommonSuperType || this.checkTypeSubtypeOf(bestCommonSuperType, type)) { |
+ bestCommonSuperType = type; |
+ } |
+ } |
+ } |
+ return bestCommonSuperType; |
+ */ |
} |
private getFileAndName(n: ts.Node, originalSymbol: ts.Symbol): {fileName: string, qname: string} { |
let symbol = originalSymbol; |
while (symbol.flags & ts.SymbolFlags.Alias) symbol = this.tc.getAliasedSymbol(symbol); |
- let decl = symbol.valueDeclaration; |
- if (!decl) { |
- // In the case of a pure declaration with no assignment, there is no value declared. |
- // Just grab the first declaration, hoping it is declared once. |
- if (!symbol.declarations || symbol.declarations.length === 0) { |
- this.reportError(n, 'no declarations for symbol ' + originalSymbol.name); |
- return null; |
- } |
- decl = symbol.declarations[0]; |
- } |
+ let decl = this.getSymbolDeclaration(symbol, n); |
const fileName = decl.getSourceFile().fileName; |
const canonicalFileName = this.getRelativeFileName(fileName) |
@@ -281,446 +736,4 @@ export class FacadeConverter extends base.TranspilerBase { |
if (FACADE_DEBUG) console.error('fn:', fileName, 'cfn:', canonicalFileName, 'qn:', qname); |
return {fileName: canonicalFileName, qname}; |
} |
- |
- private isNamedType(node: ts.Node, fileName: string, qname: string): boolean { |
- let symbol = this.tc.getTypeAtLocation(node).getSymbol(); |
- if (!symbol) return false; |
- let actual = this.getFileAndName(node, symbol); |
- if (fileName === 'lib' && !(actual.fileName === 'lib' || actual.fileName === 'lib.es6')) { |
- return false; |
- } else { |
- if (fileName !== actual.fileName) return false; |
- } |
- return qname === actual.qname; |
- } |
- |
- private reportMissingType(n: ts.Node, ident: string) { |
- this.reportError( |
- n, `Untyped property access to "${ident}" which could be ` + `a special ts2dart builtin. ` + |
- `Please add type declarations to disambiguate.`); |
- } |
- |
- isInsideConstExpr(node: ts.Node): boolean { |
- return this.isConstCall( |
- <ts.CallExpression>this.getAncestor(node, ts.SyntaxKind.CallExpression)); |
- } |
- |
- isConstCall(node: ts.Expression): boolean { |
- return node && node.kind === ts.SyntaxKind.CallExpression && |
- base.ident((<ts.CallExpression>node).expression) === 'CONST_EXPR'; |
- } |
- |
- private emitMethodCall(name: string, args?: ts.Expression[]) { |
- this.emit('.'); |
- this.emitCall(name, args); |
- } |
- |
- private emitCall(name: string, args?: ts.Expression[]) { |
- this.emit(name); |
- this.emit('('); |
- if (args) this.visitList(args); |
- this.emit(')'); |
- } |
- |
- private stdlibTypeReplacements: ts.Map<string> = { |
- 'Date': 'DateTime', |
- 'Array': 'List', |
- 'XMLHttpRequest': 'HttpRequest', |
- 'Uint8Array': 'Uint8List', |
- 'ArrayBuffer': 'ByteBuffer', |
- 'Promise': 'Future', |
- |
- // Dart has two different incompatible DOM APIs |
- // https://github.com/angular/angular/issues/2770 |
- 'Node': 'dynamic', |
- 'Text': 'dynamic', |
- 'Element': 'dynamic', |
- 'Event': 'dynamic', |
- 'HTMLElement': 'dynamic', |
- 'HTMLAnchorElement': 'dynamic', |
- 'HTMLStyleElement': 'dynamic', |
- 'HTMLInputElement': 'dynamic', |
- 'HTMLDocument': 'dynamic', |
- 'History': 'dynamic', |
- 'Location': 'dynamic', |
- }; |
- |
- private TS_TO_DART_TYPENAMES: ts.Map<ts.Map<string>> = { |
- 'lib': this.stdlibTypeReplacements, |
- 'lib.es6': this.stdlibTypeReplacements, |
- 'angular2/src/facade/lang': {'Date': 'DateTime'}, |
- |
- 'rxjs/Observable': {'Observable': 'Stream'}, |
- 'es6-promise/es6-promise': {'Promise': 'Future'}, |
- 'es6-shim/es6-shim': {'Promise': 'Future'}, |
- }; |
- |
- private es6Promises: ts.Map<CallHandler> = { |
- 'Promise.catch': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emit('.catchError('); |
- this.visitList(c.arguments); |
- this.emit(')'); |
- }, |
- 'Promise.then': (c: ts.CallExpression, context: ts.Expression) => { |
- // then() in Dart doesn't support 2 arguments. |
- this.visit(context); |
- this.emit('.then('); |
- this.visit(c.arguments[0]); |
- this.emit(')'); |
- if (c.arguments.length > 1) { |
- this.emit('.catchError('); |
- this.visit(c.arguments[1]); |
- this.emit(')'); |
- } |
- }, |
- 'Promise': (c: ts.CallExpression, context: ts.Expression) => { |
- if (c.kind !== ts.SyntaxKind.NewExpression) return true; |
- this.assert(c, c.arguments.length === 1, 'Promise construction must take 2 arguments.'); |
- this.assert( |
- c, c.arguments[0].kind === ts.SyntaxKind.ArrowFunction || |
- c.arguments[0].kind === ts.SyntaxKind.FunctionExpression, |
- 'Promise argument must be a function expression (or arrow function).'); |
- let callback: ts.FunctionLikeDeclaration; |
- if (c.arguments[0].kind === ts.SyntaxKind.ArrowFunction) { |
- callback = <ts.FunctionLikeDeclaration>(<ts.ArrowFunction>c.arguments[0]); |
- } else if (c.arguments[0].kind === ts.SyntaxKind.FunctionExpression) { |
- callback = <ts.FunctionLikeDeclaration>(<ts.FunctionExpression>c.arguments[0]); |
- } |
- this.assert( |
- c, callback.parameters.length > 0 && callback.parameters.length < 3, |
- 'Promise executor must take 1 or 2 arguments (resolve and reject).'); |
- |
- const completerVarName = this.uniqueId('completer'); |
- this.assert( |
- c, callback.parameters[0].name.kind === ts.SyntaxKind.Identifier, |
- 'First argument of the Promise executor is not a straight parameter.'); |
- let resolveParameterIdent = <ts.Identifier>(callback.parameters[0].name); |
- |
- this.emit('(() {'); // Create a new scope. |
- this.emit(`Completer ${completerVarName} = new Completer();`); |
- this.emit('var'); |
- this.emit(resolveParameterIdent.text); |
- this.emit(`= ${completerVarName}.complete;`); |
- |
- if (callback.parameters.length === 2) { |
- this.assert( |
- c, callback.parameters[1].name.kind === ts.SyntaxKind.Identifier, |
- 'First argument of the Promise executor is not a straight parameter.'); |
- let rejectParameterIdent = <ts.Identifier>(callback.parameters[1].name); |
- this.emit('var'); |
- this.emit(rejectParameterIdent.text); |
- this.emit(`= ${completerVarName}.completeError;`); |
- } |
- this.emit('(()'); |
- this.visit(callback.body); |
- this.emit(')();'); |
- this.emit(`return ${completerVarName}.future;`); |
- this.emit('})()'); |
- }, |
- }; |
- |
- private stdlibHandlers: ts.Map<CallHandler> = merge(this.es6Promises, { |
- 'Array.push': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emitMethodCall('add', c.arguments); |
- }, |
- 'Array.pop': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emitMethodCall('removeLast'); |
- }, |
- 'Array.shift': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emit('. removeAt ( 0 )'); |
- }, |
- 'Array.unshift': (c: ts.CallExpression, context: ts.Expression) => { |
- this.emit('('); |
- this.visit(context); |
- if (c.arguments.length === 1) { |
- this.emit('.. insert ( 0,'); |
- this.visit(c.arguments[0]); |
- this.emit(') ) . length'); |
- } else { |
- this.emit('.. insertAll ( 0, ['); |
- this.visitList(c.arguments); |
- this.emit(']) ) . length'); |
- } |
- }, |
- 'Array.map': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emitMethodCall('map', c.arguments); |
- this.emitMethodCall('toList'); |
- }, |
- 'Array.filter': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emitMethodCall('where', c.arguments); |
- this.emitMethodCall('toList'); |
- }, |
- 'Array.some': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emitMethodCall('any', c.arguments); |
- }, |
- 'Array.slice': (c: ts.CallExpression, context: ts.Expression) => { |
- this.emitCall('ListWrapper.slice', [context, ...c.arguments]); |
- }, |
- 'Array.splice': (c: ts.CallExpression, context: ts.Expression) => { |
- this.emitCall('ListWrapper.splice', [context, ...c.arguments]); |
- }, |
- 'Array.concat': (c: ts.CallExpression, context: ts.Expression) => { |
- this.emit('( new List . from ('); |
- this.visit(context); |
- this.emit(')'); |
- c.arguments.forEach(arg => { |
- if (!this.isNamedType(arg, 'lib', 'Array')) { |
- this.reportError(arg, 'Array.concat only takes Array arguments'); |
- } |
- this.emit('.. addAll ('); |
- this.visit(arg); |
- this.emit(')'); |
- }); |
- this.emit(')'); |
- }, |
- 'Array.join': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- if (c.arguments.length) { |
- this.emitMethodCall('join', c.arguments); |
- } else { |
- this.emit('. join ( "," )'); |
- } |
- }, |
- 'Array.reduce': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- |
- if (c.arguments.length >= 2) { |
- this.emitMethodCall('fold', [c.arguments[1], c.arguments[0]]); |
- } else { |
- this.emit('. fold ( null ,'); |
- this.visit(c.arguments[0]); |
- this.emit(')'); |
- } |
- }, |
- 'ArrayConstructor.isArray': (c: ts.CallExpression, context: ts.Expression) => { |
- this.emit('( ('); |
- this.visitList(c.arguments); // Should only be 1. |
- this.emit(')'); |
- this.emit('is List'); |
- this.emit(')'); |
- }, |
- 'Console.log': (c: ts.CallExpression, context: ts.Expression) => { |
- this.emit('print('); |
- if (c.arguments.length === 1) { |
- this.visit(c.arguments[0]); |
- } else { |
- this.emit('['); |
- this.visitList(c.arguments); |
- this.emit('].join(" ")'); |
- } |
- this.emit(')'); |
- }, |
- 'RegExp.exec': (c: ts.CallExpression, context: ts.Expression) => { |
- if (context.kind !== ts.SyntaxKind.RegularExpressionLiteral) { |
- // Fail if the exec call isn't made directly on a regexp literal. |
- // Multiple exec calls on the same global regexp have side effects |
- // (each return the next match), which we can't reproduce with a simple |
- // Dart RegExp (users should switch to some facade / wrapper instead). |
- this.reportError( |
- c, 'exec is only supported on regexp literals, ' + |
- 'to avoid side-effect of multiple calls on global regexps.'); |
- } |
- if (c.parent.kind === ts.SyntaxKind.ElementAccessExpression) { |
- // The result of the exec call is used for immediate indexed access: |
- // this use-case can be accommodated by RegExp.firstMatch, which returns |
- // a Match instance with operator[] which returns groups (special index |
- // 0 returns the full text of the match). |
- this.visit(context); |
- this.emitMethodCall('firstMatch', c.arguments); |
- } else { |
- // In the general case, we want to return a List. To transform a Match |
- // into a List of its groups, we alias it in a local closure that we |
- // call with the Match value. We are then able to use the group method |
- // to generate a List large enough to hold groupCount groups + the |
- // full text of the match at special group index 0. |
- this.emit('((match) => new List.generate(1 + match.groupCount, match.group))('); |
- this.visit(context); |
- this.emitMethodCall('firstMatch', c.arguments); |
- this.emit(')'); |
- } |
- }, |
- 'RegExp.test': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emitMethodCall('hasMatch', c.arguments); |
- }, |
- 'String.substr': (c: ts.CallExpression, context: ts.Expression) => { |
- this.reportError( |
- c, 'substr is unsupported, use substring (but beware of the different semantics!)'); |
- this.visit(context); |
- this.emitMethodCall('substr', c.arguments); |
- }, |
- }); |
- |
- private es6Collections: ts.Map<CallHandler> = { |
- 'Map.set': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emit('['); |
- this.visit(c.arguments[0]); |
- this.emit(']'); |
- this.emit('='); |
- this.visit(c.arguments[1]); |
- }, |
- 'Map.get': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emit('['); |
- this.visit(c.arguments[0]); |
- this.emit(']'); |
- }, |
- 'Map.has': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emitMethodCall('containsKey', c.arguments); |
- }, |
- 'Map.delete': (c: ts.CallExpression, context: ts.Expression) => { |
- // JS Map.delete(k) returns whether k was present in the map, |
- // convert to: |
- // (Map.containsKey(k) && (Map.remove(k) !== null || true)) |
- // (Map.remove(k) !== null || true) is required to always returns true |
- // when Map.containsKey(k) |
- this.emit('('); |
- this.visit(context); |
- this.emitMethodCall('containsKey', c.arguments); |
- this.emit('&& ('); |
- this.visit(context); |
- this.emitMethodCall('remove', c.arguments); |
- this.emit('!= null || true ) )'); |
- }, |
- 'Map.forEach': (c: ts.CallExpression, context: ts.Expression) => { |
- let cb: any; |
- let params: any; |
- |
- switch (c.arguments[0].kind) { |
- case ts.SyntaxKind.FunctionExpression: |
- cb = <ts.FunctionExpression>(c.arguments[0]); |
- params = cb.parameters; |
- if (params.length !== 2) { |
- this.reportError(c, 'Map.forEach callback requires exactly two arguments'); |
- return; |
- } |
- this.visit(context); |
- this.emit('. forEach ( ('); |
- this.visit(params[1]); |
- this.emit(','); |
- this.visit(params[0]); |
- this.emit(')'); |
- this.visit(cb.body); |
- this.emit(')'); |
- break; |
- |
- case ts.SyntaxKind.ArrowFunction: |
- cb = <ts.ArrowFunction>(c.arguments[0]); |
- params = cb.parameters; |
- if (params.length !== 2) { |
- this.reportError(c, 'Map.forEach callback requires exactly two arguments'); |
- return; |
- } |
- this.visit(context); |
- this.emit('. forEach ( ('); |
- this.visit(params[1]); |
- this.emit(','); |
- this.visit(params[0]); |
- this.emit(')'); |
- if (cb.body.kind !== ts.SyntaxKind.Block) { |
- this.emit('=>'); |
- } |
- this.visit(cb.body); |
- this.emit(')'); |
- break; |
- |
- default: |
- this.visit(context); |
- this.emit('. forEach ( ( k , v ) => ('); |
- this.visit(c.arguments[0]); |
- this.emit(') ( v , k ) )'); |
- break; |
- } |
- }, |
- 'Array.find': (c: ts.CallExpression, context: ts.Expression) => { |
- this.visit(context); |
- this.emit('. firstWhere ('); |
- this.visit(c.arguments[0]); |
- this.emit(', orElse : ( ) => null )'); |
- }, |
- }; |
- |
- private callHandlerReplaceNew: ts.Map<ts.Map<boolean>> = { |
- 'es6-promise/es6-promise': {'Promise': true}, |
- 'es6-shim/es6-shim': {'Promise': true}, |
- }; |
- |
- private callHandlers: ts.Map<ts.Map<CallHandler>> = { |
- 'lib': this.stdlibHandlers, |
- 'lib.es6': this.stdlibHandlers, |
- 'es6-promise/es6-promise': this.es6Promises, |
- 'es6-shim/es6-shim': merge(this.es6Promises, this.es6Collections), |
- 'es6-collections/es6-collections': this.es6Collections, |
- 'angular2/manual_typings/globals': this.es6Collections, |
- 'angular2/src/facade/collection': { |
- 'Map': (c: ts.CallExpression, context: ts.Expression): boolean => { |
- // The actual Map constructor is special cased for const calls. |
- if (!this.isInsideConstExpr(c)) return true; |
- if (c.arguments.length) { |
- this.reportError(c, 'Arguments on a Map constructor in a const are unsupported'); |
- } |
- if (c.typeArguments) { |
- this.emit('<'); |
- this.visitList(c.typeArguments); |
- this.emit('>'); |
- } |
- this.emit('{ }'); |
- return false; |
- }, |
- }, |
- 'angular2/src/core/di/forward_ref': { |
- 'forwardRef': (c: ts.CallExpression, context: ts.Expression) => { |
- // The special function forwardRef translates to an unwrapped value in Dart. |
- const callback = <ts.FunctionExpression>c.arguments[0]; |
- if (callback.kind !== ts.SyntaxKind.ArrowFunction) { |
- this.reportError(c, 'forwardRef takes only arrow functions'); |
- return; |
- } |
- this.visit(callback.body); |
- }, |
- }, |
- 'angular2/src/facade/lang': { |
- 'CONST_EXPR': (c: ts.CallExpression, context: ts.Expression) => { |
- // `const` keyword is emitted in the array literal handling, as it needs to be transitive. |
- this.visitList(c.arguments); |
- }, |
- 'normalizeBlank': (c: ts.CallExpression, context: ts.Expression) => { |
- // normalizeBlank is a noop in Dart, so erase it. |
- this.visitList(c.arguments); |
- }, |
- }, |
- }; |
- |
- private es6CollectionsProp: ts.Map<PropertyHandler> = { |
- 'Map.size': (p: ts.PropertyAccessExpression) => { |
- this.visit(p.expression); |
- this.emit('.'); |
- this.emit('length'); |
- }, |
- }; |
- private es6PromisesProp: ts.Map<PropertyHandler> = { |
- 'resolve': (p: ts.PropertyAccessExpression) => { |
- this.visit(p.expression); |
- this.emit('.value'); |
- }, |
- 'reject': (p: ts.PropertyAccessExpression) => { |
- this.visit(p.expression); |
- this.emit('.error'); |
- }, |
- }; |
- |
- private propertyHandlers: ts.Map<ts.Map<PropertyHandler>> = { |
- 'es6-shim/es6-shim': this.es6CollectionsProp, |
- 'es6-collections/es6-collections': this.es6CollectionsProp, |
- 'es6-promise/es6-promise': this.es6PromisesProp, |
- }; |
} |