| OLD | NEW |
| 1 import * as ts from 'typescript'; |
| 2 |
| 1 import * as base from './base'; | 3 import * as base from './base'; |
| 2 import * as ts from 'typescript'; | 4 import {Set} from './base'; |
| 5 import {DART_LIBRARIES_FOR_BROWSER_TYPES, TS_TO_DART_TYPENAMES} from './dart_lib
raries_for_browser_types'; |
| 3 import {Transpiler} from './main'; | 6 import {Transpiler} from './main'; |
| 7 import {MergedType} from './merge'; |
| 4 | 8 |
| 5 type CallHandler = (c: ts.CallExpression, context: ts.Expression) => void; | 9 type CallHandler = (c: ts.CallExpression, context: ts.Expression) => void; |
| 6 type PropertyHandler = (c: ts.PropertyAccessExpression) => void; | 10 type PropertyHandler = (c: ts.PropertyAccessExpression) => void; |
| 7 type Set = { | |
| 8 [s: string]: boolean | |
| 9 }; | |
| 10 | 11 |
| 11 const FACADE_DEBUG = false; | 12 const FACADE_DEBUG = false; |
| 12 | |
| 13 const FACADE_NODE_MODULES_PREFIX = /^(\.\.\/)*node_modules\//; | 13 const FACADE_NODE_MODULES_PREFIX = /^(\.\.\/)*node_modules\//; |
| 14 | 14 |
| 15 function merge(...args: {[key: string]: any}[]): {[key: string]: any} { | 15 // These constants must be kept in sync with package:func/func.dart which |
| 16 let returnObject: {[key: string]: any} = {}; | 16 // provides a cannonical set of typedefs defining commonly used function types |
| 17 for (let arg of args) { | 17 // to simplify specifying function types in Dart. |
| 18 for (let key of Object.getOwnPropertyNames(arg)) { | 18 const MAX_DART_FUNC_ACTION_PARAMETERS = 4; |
| 19 returnObject[key] = arg[key]; | 19 const MAX_DART_FUNC_ACTION_PARAMETERS_OPTIONAL = 1; |
| 20 |
| 21 /** |
| 22 * Prefix to add to a variable name that leaves the JS name referenced |
| 23 * unchanged. |
| 24 */ |
| 25 const DART_RESERVED_NAME_PREFIX = 'JS$'; |
| 26 |
| 27 export function fixupIdentifierName(text: string): string { |
| 28 return (FacadeConverter.DART_RESERVED_WORDS.indexOf(text) !== -1 || |
| 29 FacadeConverter.DART_OTHER_KEYWORDS.indexOf(text) !== -1 || text[0] ==
= '_') ? |
| 30 DART_RESERVED_NAME_PREFIX + text : |
| 31 text; |
| 32 } |
| 33 |
| 34 function numOptionalParameters(parameters: ts.NodeArray<ts.ParameterDeclaration>
): number { |
| 35 for (let i = 0; i < parameters.length; ++i) { |
| 36 if (parameters[i].questionToken) return parameters.length - i; |
| 37 } |
| 38 return 0; |
| 39 } |
| 40 |
| 41 function hasVarArgs(parameters: ts.NodeArray<ts.ParameterDeclaration>): boolean
{ |
| 42 for (let i = 0; i < parameters.length; ++i) { |
| 43 if (parameters[i].dotDotDotToken) return true; |
| 44 } |
| 45 return false; |
| 46 } |
| 47 |
| 48 /** |
| 49 * Generates the JavaScript expression required to reference the node |
| 50 * from the global context. InterfaceDeclarations do not technically correspond |
| 51 * to actual JavaScript objects but we still generate a reference path for them |
| 52 * so that we have a guaranteed unique name. |
| 53 * |
| 54 * Example JS path: |
| 55 * module m1 { |
| 56 * module m2 { |
| 57 * class foo { } |
| 58 * } |
| 59 * } |
| 60 * Path: m1.m2.foo |
| 61 */ |
| 62 function fullJsPath(node: base.NamedDeclaration): string { |
| 63 let parts: Array<string> = [base.ident(node.name)]; |
| 64 let p: ts.Node = node.parent; |
| 65 while (p != null) { |
| 66 let kind = p.kind; |
| 67 if (kind === ts.SyntaxKind.ModuleDeclaration || kind === ts.SyntaxKind.Inter
faceDeclaration || |
| 68 kind === ts.SyntaxKind.ClassDeclaration) { |
| 69 parts.unshift(base.ident((<base.NamedDeclaration>p).name)); |
| 70 } |
| 71 p = p.parent; |
| 72 } |
| 73 return parts.join('.'); |
| 74 } |
| 75 |
| 76 class DartNameRecord { |
| 77 name: string; |
| 78 constructor(private node: ts.Node, name: string, private library: DartLibrary)
{ |
| 79 this.name = name; |
| 80 } |
| 81 } |
| 82 |
| 83 export class DartLibrary { |
| 84 constructor(private fileName: string) { this.usedNames = {}; } |
| 85 |
| 86 /** |
| 87 * @returns {boolean} whether the name was added. |
| 88 */ |
| 89 addName(name: string): boolean { |
| 90 if (Object.prototype.hasOwnProperty.call(this.usedNames, name)) { |
| 91 return false; |
| 92 } |
| 93 this.usedNames[name] = true; |
| 94 return true; |
| 95 } |
| 96 |
| 97 private usedNames: Set; |
| 98 } |
| 99 |
| 100 export class NameRewriter { |
| 101 private dartTypes: ts.Map<DartNameRecord> = {}; |
| 102 // TODO(jacobr): use libraries to track what library imports need to be |
| 103 // emitted. Complex cases such as ts.SyntaxKind.TypeReference |
| 104 // where emitting the type could require emitting imports to libraries not |
| 105 // specified in the imports listed in TypeScript. Additionally, d.ts files |
| 106 // can contain multiple modules and for readability we may want an option |
| 107 // to emit each of those modules as a separate Dart library. That would also |
| 108 // require tracking what external libraries are referenced. |
| 109 private libraries: ts.Map<DartLibrary> = {}; |
| 110 |
| 111 private computeName(node: base.NamedDeclaration): DartNameRecord { |
| 112 let fullPath = fullJsPath(node); |
| 113 if (Object.prototype.hasOwnProperty.call(this.dartTypes, fullPath)) { |
| 114 return this.dartTypes[fullPath]; |
| 115 } |
| 116 let sourceFile = <ts.SourceFile>base.getAncestor(node, ts.SyntaxKind.SourceF
ile); |
| 117 let fileName = sourceFile.fileName; |
| 118 let library: DartLibrary; |
| 119 if (Object.prototype.hasOwnProperty.call(this.libraries, fileName)) { |
| 120 library = this.libraries[fileName]; |
| 121 } else { |
| 122 library = new DartLibrary(fileName); |
| 123 this.libraries[fileName] = library; |
| 124 } |
| 125 let parts = fullPath.split('.'); |
| 126 for (let i = parts.length - 1; i >= 0; i--) { |
| 127 // Find a unique name by including more of the module hierarchy in the |
| 128 // name. This is an arbitrary but hopefully unsurprising scheme to |
| 129 // generate unique names. There may be classes or members with conflicting |
| 130 // names due to a single d.ts file containing multiple modules. |
| 131 // TODO(jacobr): we should suppress this behavior outside of JS Interop |
| 132 // mode and instead generate a compile error if there are conflicting |
| 133 // names. |
| 134 let candidateName = fixupIdentifierName(parts.slice(i).join('_')); |
| 135 if (library.addName(candidateName)) { |
| 136 // Able to add name to library. |
| 137 let ret = new DartNameRecord(node, candidateName, library); |
| 138 this.dartTypes[fullPath] = ret; |
| 139 return ret; |
| 140 } |
| 141 } |
| 142 |
| 143 // Usually the module name prefixes should be sufficient to disambiguate |
| 144 // names but sometimes we need to add a numeric prefix as well to |
| 145 // disambiguate. We could alternately append the full module prefix as well |
| 146 // to make the name choice completely unsurprising albeit even uglier. |
| 147 // This case should be very rarely hit. |
| 148 let i = 2; |
| 149 while (true) { |
| 150 let candidateName = parts[parts.length - 1] + i; |
| 151 if (library.addName(candidateName)) { |
| 152 // Able to add name to library. |
| 153 let ret = new DartNameRecord(node, candidateName, library); |
| 154 this.dartTypes[fullPath] = ret; |
| 155 return ret; |
| 156 } |
| 157 i++; |
| 20 } | 158 } |
| 21 } | 159 } |
| 22 return returnObject; | 160 |
| 161 lookupName(node: base.NamedDeclaration, context: ts.Node) { return this.comput
eName(node).name; } |
| 23 } | 162 } |
| 24 | 163 |
| 25 | |
| 26 export class FacadeConverter extends base.TranspilerBase { | 164 export class FacadeConverter extends base.TranspilerBase { |
| 27 private tc: ts.TypeChecker; | 165 private tc: ts.TypeChecker; |
| 28 private candidateProperties: {[propertyName: string]: boolean} = {}; | 166 // For the Dart keyword list see |
| 167 // https://www.dartlang.org/docs/dart-up-and-running/ch02.html#keywords |
| 168 static DART_RESERVED_WORDS = |
| 169 ('assert break case catch class const continue default do else enum extend
s false final ' + |
| 170 'finally for if in is new null rethrow return super switch this throw tru
e try let void ' + |
| 171 'while with') |
| 172 .split(/ /); |
| 173 |
| 174 // These are the built-in and limited keywords. |
| 175 static DART_OTHER_KEYWORDS = |
| 176 ('abstract as async await deferred dynamic export external factory get imp
lements import ' + |
| 177 'library operator part set static sync typedef yield call') |
| 178 .split(/ /); |
| 179 |
| 29 private candidateTypes: {[typeName: string]: boolean} = {}; | 180 private candidateTypes: {[typeName: string]: boolean} = {}; |
| 30 private typingsRootRegex: RegExp; | 181 private typingsRootRegex: RegExp; |
| 31 private genericMethodDeclDepth = 0; | 182 private genericMethodDeclDepth = 0; |
| 32 | 183 |
| 33 constructor(transpiler: Transpiler, typingsRoot = '') { | 184 constructor( |
| 185 transpiler: Transpiler, typingsRoot = '', private nameRewriter: NameRewrit
er, |
| 186 private useHtml: boolean) { |
| 34 super(transpiler); | 187 super(transpiler); |
| 35 this.extractPropertyNames(this.callHandlers, this.candidateProperties); | 188 if (useHtml) { |
| 36 this.extractPropertyNames(this.propertyHandlers, this.candidateProperties); | 189 this.extractPropertyNames(TS_TO_DART_TYPENAMES, this.candidateTypes); |
| 37 this.extractPropertyNames(this.TS_TO_DART_TYPENAMES, this.candidateTypes); | 190 Object.keys(DART_LIBRARIES_FOR_BROWSER_TYPES) |
| 191 .forEach((propName) => this.candidateTypes[propName] = true); |
| 192 } else { |
| 193 this.extractPropertyNames(TS_TO_DART_TYPENAMES, this.candidateTypes); |
| 194 } |
| 38 | 195 |
| 39 this.typingsRootRegex = new RegExp('^' + typingsRoot.replace('.', '\\.')); | 196 this.typingsRootRegex = new RegExp('^' + typingsRoot.replace('.', '\\.')); |
| 40 } | 197 } |
| 41 | 198 |
| 42 private extractPropertyNames(m: ts.Map<ts.Map<any>>, candidates: {[k: string]:
boolean}) { | 199 private extractPropertyNames(m: ts.Map<ts.Map<any>>, candidates: {[k: string]:
boolean}) { |
| 43 for (let fileName of Object.keys(m)) { | 200 for (let fileName of Object.keys(m)) { |
| 44 const file = m[fileName]; | 201 const file = m[fileName]; |
| 202 if (file === undefined) { |
| 203 return; |
| 204 } |
| 45 Object.keys(file) | 205 Object.keys(file) |
| 46 .map((propName) => propName.substring(propName.lastIndexOf('.') + 1)) | 206 .map((propName) => propName.substring(propName.lastIndexOf('.') + 1)) |
| 47 .forEach((propName) => candidates[propName] = true); | 207 .forEach((propName) => candidates[propName] = true); |
| 48 } | 208 } |
| 49 } | 209 } |
| 50 | 210 |
| 51 setTypeChecker(tc: ts.TypeChecker) { this.tc = tc; } | 211 setTypeChecker(tc: ts.TypeChecker) { this.tc = tc; } |
| 52 | 212 |
| 53 maybeHandleCall(c: ts.CallExpression): boolean { | |
| 54 if (!this.tc) return false; | |
| 55 let {context, symbol} = this.getCallInformation(c); | |
| 56 if (!symbol) { | |
| 57 // getCallInformation returns a symbol if we understand this call. | |
| 58 return false; | |
| 59 } | |
| 60 let handler = this.getHandler(c, symbol, this.callHandlers); | |
| 61 return handler && !handler(c, context); | |
| 62 } | |
| 63 | |
| 64 handlePropertyAccess(pa: ts.PropertyAccessExpression): boolean { | |
| 65 if (!this.tc) return; | |
| 66 let ident = pa.name.text; | |
| 67 if (!this.candidateProperties.hasOwnProperty(ident)) return false; | |
| 68 let symbol = this.tc.getSymbolAtLocation(pa.name); | |
| 69 if (!symbol) { | |
| 70 this.reportMissingType(pa, ident); | |
| 71 return false; | |
| 72 } | |
| 73 | |
| 74 let handler = this.getHandler(pa, symbol, this.propertyHandlers); | |
| 75 return handler && !handler(pa); | |
| 76 } | |
| 77 | |
| 78 /** | |
| 79 * Searches for type references that require extra imports and emits the impor
ts as necessary. | |
| 80 */ | |
| 81 emitExtraImports(sourceFile: ts.SourceFile) { | |
| 82 let libraries = <ts.Map<string>>{ | |
| 83 'XMLHttpRequest': 'dart:html', | |
| 84 'KeyboardEvent': 'dart:html', | |
| 85 'Uint8Array': 'dart:typed_arrays', | |
| 86 'ArrayBuffer': 'dart:typed_arrays', | |
| 87 'Promise': 'dart:async', | |
| 88 }; | |
| 89 let emitted: Set = {}; | |
| 90 this.emitImports(sourceFile, libraries, emitted, sourceFile); | |
| 91 } | |
| 92 | |
| 93 private emitImports( | |
| 94 n: ts.Node, libraries: ts.Map<string>, emitted: Set, sourceFile: ts.Source
File): void { | |
| 95 if (n.kind === ts.SyntaxKind.TypeReference) { | |
| 96 let type = base.ident((<ts.TypeReferenceNode>n).typeName); | |
| 97 if (libraries.hasOwnProperty(type)) { | |
| 98 let toEmit = libraries[type]; | |
| 99 if (!emitted[toEmit]) { | |
| 100 this.emit(`import "${toEmit}";`); | |
| 101 emitted[toEmit] = true; | |
| 102 } | |
| 103 } | |
| 104 } | |
| 105 | |
| 106 n.getChildren(sourceFile) | |
| 107 .forEach((child: ts.Node) => this.emitImports(child, libraries, emitted,
sourceFile)); | |
| 108 } | |
| 109 | |
| 110 pushTypeParameterNames(n: ts.FunctionLikeDeclaration) { | 213 pushTypeParameterNames(n: ts.FunctionLikeDeclaration) { |
| 111 if (!n.typeParameters) return; | 214 if (!n.typeParameters) return; |
| 112 this.genericMethodDeclDepth++; | 215 this.genericMethodDeclDepth++; |
| 113 } | 216 } |
| 114 | 217 |
| 115 popTypeParameterNames(n: ts.FunctionLikeDeclaration) { | 218 popTypeParameterNames(n: ts.FunctionLikeDeclaration) { |
| 116 if (!n.typeParameters) return; | 219 if (!n.typeParameters) return; |
| 117 this.genericMethodDeclDepth--; | 220 this.genericMethodDeclDepth--; |
| 118 } | 221 } |
| 119 | 222 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 130 ' used for named parameter definition must be a property'; | 233 ' used for named parameter definition must be a property'; |
| 131 this.reportError(decl, msg); | 234 this.reportError(decl, msg); |
| 132 continue; | 235 continue; |
| 133 } | 236 } |
| 134 res[sym.name] = <ts.PropertyDeclaration>decl; | 237 res[sym.name] = <ts.PropertyDeclaration>decl; |
| 135 } | 238 } |
| 136 return res; | 239 return res; |
| 137 } | 240 } |
| 138 | 241 |
| 139 /** | 242 /** |
| 140 * The Dart Development Compiler (DDC) has a syntax extension that uses commen
ts to emulate | 243 * The Dart analyzer has a syntax extension that uses comments to emulate |
| 141 * generic methods in Dart. ts2dart has to hack around this and keep track of
which type names | 244 * generic methods in Dart. We work around this and keep track of which type |
| 142 * in the current scope are actually DDC type parameters and need to be emitte
d in comments. | 245 * names in the current scope need to be emitted in comments. |
| 143 * | 246 * |
| 144 * TODO(martinprobst): Remove this once the DDC hack has made it into Dart pro
per. | 247 * TODO(jacobr): Remove this once all Dart implementations support generic |
| 248 * methods. |
| 145 */ | 249 */ |
| 146 private isGenericMethodTypeParameterName(name: ts.EntityName): boolean { | 250 private isGenericMethodTypeParameterName(name: ts.EntityName): boolean { |
| 147 // Avoid checking this unless needed. | 251 // Avoid checking this unless needed. |
| 148 if (this.genericMethodDeclDepth === 0 || !this.tc) return false; | 252 if (this.genericMethodDeclDepth === 0 || !this.tc) return false; |
| 149 // Check if the type of the name is a TypeParameter. | 253 // Check if the type of the name is a TypeParameter. |
| 150 let t = this.tc.getTypeAtLocation(name); | 254 let t = this.tc.getTypeAtLocation(name); |
| 151 if (!t || (t.flags & ts.TypeFlags.TypeParameter) === 0) return false; | 255 if (!t || (t.flags & ts.TypeFlags.TypeParameter) === 0) return false; |
| 152 | 256 |
| 153 // Check if the symbol we're looking at is the type parameter. | 257 // Check if the symbol we're looking at is the type parameter. |
| 154 let symbol = this.tc.getSymbolAtLocation(name); | 258 let symbol = this.tc.getSymbolAtLocation(name); |
| 155 if (symbol !== t.symbol) return false; | 259 if (symbol !== t.symbol) return false; |
| 156 | 260 |
| 157 // Check that the Type Parameter has been declared by a function declaration
. | 261 // Check that the Type Parameter has been declared by a function declaration
. |
| 158 return symbol.declarations.some(d => d.parent.kind === ts.SyntaxKind.Functio
nDeclaration); | 262 return symbol.declarations.some(d => d.parent.kind === ts.SyntaxKind.Functio
nDeclaration); |
| 159 } | 263 } |
| 160 | 264 |
| 265 generateTypeList(types: ts.TypeNode[], seperator = ','): string { |
| 266 return types.map(this.generateDartTypeName.bind(this)).join(seperator); |
| 267 } |
| 268 |
| 269 maybeGenerateTypeArguments(n: {typeArguments?: ts.NodeArray<ts.TypeNode>}): st
ring { |
| 270 if (!n.typeArguments) return ''; |
| 271 return '<' + this.generateTypeList(n.typeArguments) + '>'; |
| 272 } |
| 273 |
| 274 generateDartTypeName(node: ts.TypeNode): string { |
| 275 let name: string; |
| 276 let comment: string; |
| 277 if (!node) { |
| 278 return 'dynamic'; |
| 279 } |
| 280 switch (node.kind) { |
| 281 case ts.SyntaxKind.TypeQuery: |
| 282 let query = <ts.TypeQueryNode>node; |
| 283 name = 'dynamic'; |
| 284 name += '/* Dart does not support TypeQuery: typeof ' + base.ident(query
.exprName) + ' */'; |
| 285 break; |
| 286 case ts.SyntaxKind.LastTypeNode: |
| 287 let type = (node as ts.ParenthesizedTypeNode).type; |
| 288 if (!type) { |
| 289 // This case occurs for String literal types |
| 290 comment = node.getText(); |
| 291 // TODO(jacobr): find a better way to detect string literal types. |
| 292 name = comment[0] === '"' ? 'String' : 'dynamic'; |
| 293 break; |
| 294 } |
| 295 return this.generateDartTypeName(type); |
| 296 case ts.SyntaxKind.TypePredicate: |
| 297 return this.generateDartTypeName((node as ts.TypePredicateNode).type); |
| 298 case ts.SyntaxKind.TupleType: |
| 299 let tuple = <ts.TupleTypeNode>node; |
| 300 name = 'List<'; |
| 301 let mergedType = new MergedType(this); |
| 302 tuple.elementTypes.forEach((t) => mergedType.merge(t)); |
| 303 name += this.generateDartTypeName(mergedType.toTypeNode()); |
| 304 name += '>'; |
| 305 comment = 'Tuple<' + this.generateTypeList(tuple.elementTypes) + '>'; |
| 306 break; |
| 307 case ts.SyntaxKind.UnionType: |
| 308 let union = <ts.UnionTypeNode>node; |
| 309 // TODO(jacobr): this isn't fundamentally JS Interop specific but we |
| 310 // choose to be more aggressive at finding a useful value for the |
| 311 // union when in JS Interop mode while otherwise we expect that union |
| 312 // types will not be used extensively. |
| 313 let simpleType = this.toSimpleDartType(union.types); |
| 314 if (simpleType) { |
| 315 name = this.generateDartTypeName(simpleType); |
| 316 } else { |
| 317 name = 'dynamic'; |
| 318 } |
| 319 let types = union.types; |
| 320 comment = this.generateTypeList(types, '|'); |
| 321 break; |
| 322 case ts.SyntaxKind.TypePredicate: |
| 323 return this.generateDartTypeName((node as ts.TypePredicateNode).type); |
| 324 case ts.SyntaxKind.TypeReference: |
| 325 let typeRef = <ts.TypeReferenceNode>node; |
| 326 name = this.generateDartName(typeRef.typeName) + this.maybeGenerateTypeA
rguments(typeRef); |
| 327 break; |
| 328 case ts.SyntaxKind.TypeLiteral: |
| 329 let members = (<ts.TypeLiteralNode>node).members; |
| 330 if (members.length === 1 && members[0].kind === ts.SyntaxKind.IndexSigna
ture) { |
| 331 let indexSig = <ts.IndexSignatureDeclaration>(members[0]); |
| 332 if (indexSig.parameters.length > 1) { |
| 333 this.reportError(indexSig, 'Expected an index signature to have a si
ngle parameter'); |
| 334 } |
| 335 // Unfortunately for JS interop, we cannot treat JS Objects as Dart |
| 336 // Map objects. We could treat them as JSMap<indexSig.type> |
| 337 // if we define a base JSMap type that is Map like but not actually |
| 338 // a map. |
| 339 name = 'dynamic'; |
| 340 comment = 'Map<' + this.generateDartTypeName(indexSig.parameters[0].ty
pe) + ',' + |
| 341 this.generateDartTypeName(indexSig.type) + '>'; |
| 342 } else { |
| 343 name = 'dynamic'; |
| 344 comment = node.getText(); |
| 345 } |
| 346 break; |
| 347 case ts.SyntaxKind.FunctionType: |
| 348 let callSignature = <ts.FunctionOrConstructorTypeNode>node; |
| 349 let parameters = callSignature.parameters; |
| 350 |
| 351 // Use a function signature from package:func where possible. |
| 352 let numOptional = numOptionalParameters(parameters); |
| 353 let isVoid = callSignature.type && callSignature.type.kind === ts.Syntax
Kind.VoidKeyword; |
| 354 if (parameters.length <= MAX_DART_FUNC_ACTION_PARAMETERS && |
| 355 numOptional <= MAX_DART_FUNC_ACTION_PARAMETERS_OPTIONAL && !hasVarAr
gs(parameters)) { |
| 356 this.emitImport('package:func/func.dart'); |
| 357 let typeDefName = (isVoid) ? 'VoidFunc' : 'Func'; |
| 358 typeDefName += parameters.length; |
| 359 if (numOptional > 0) { |
| 360 typeDefName += 'Opt' + numOptional; |
| 361 } |
| 362 name = typeDefName; |
| 363 let numArgs = parameters.length + (isVoid ? 0 : 1); |
| 364 if (numArgs > 0) { |
| 365 name += '<'; |
| 366 } |
| 367 let isFirst = true; |
| 368 for (let i = 0; i < parameters.length; ++i) { |
| 369 if (isFirst) { |
| 370 isFirst = false; |
| 371 } else { |
| 372 name += ', '; |
| 373 } |
| 374 name += this.generateDartTypeName(parameters[i].type); |
| 375 } |
| 376 if (!isVoid) { |
| 377 if (!isFirst) { |
| 378 name += ', '; |
| 379 } |
| 380 name += this.generateDartTypeName(callSignature.type); |
| 381 } |
| 382 if (numArgs > 0) { |
| 383 name += '>'; |
| 384 } |
| 385 } else { |
| 386 name = 'Function'; |
| 387 if (node.getSourceFile()) { |
| 388 comment = node.getText(); |
| 389 } |
| 390 } |
| 391 break; |
| 392 case ts.SyntaxKind.ArrayType: |
| 393 name = 'List' + |
| 394 '<' + this.generateDartTypeName((<ts.ArrayTypeNode>node).elementType
) + '>'; |
| 395 break; |
| 396 case ts.SyntaxKind.NumberKeyword: |
| 397 name = 'num'; |
| 398 break; |
| 399 case ts.SyntaxKind.StringLiteral: |
| 400 case ts.SyntaxKind.StringKeyword: |
| 401 name = 'String'; |
| 402 break; |
| 403 case ts.SyntaxKind.VoidKeyword: |
| 404 name = 'void'; |
| 405 break; |
| 406 case ts.SyntaxKind.BooleanKeyword: |
| 407 name = 'bool'; |
| 408 break; |
| 409 case ts.SyntaxKind.AnyKeyword: |
| 410 name = 'dynamic'; |
| 411 break; |
| 412 default: |
| 413 this.reportError(node, 'Unexpected TypeNode kind'); |
| 414 } |
| 415 if (name == null) { |
| 416 name = 'XXX NULLNAME'; |
| 417 } |
| 418 |
| 419 name = name.trim(); |
| 420 |
| 421 if (comment) { |
| 422 name = name + '/*' + comment + '*/'; |
| 423 } |
| 424 return name; |
| 425 } |
| 426 |
| 161 visitTypeName(typeName: ts.EntityName) { | 427 visitTypeName(typeName: ts.EntityName) { |
| 162 if (typeName.kind !== ts.SyntaxKind.Identifier) { | 428 if (typeName.kind !== ts.SyntaxKind.Identifier) { |
| 163 this.visit(typeName); | 429 this.visit(typeName); |
| 164 return; | 430 return; |
| 165 } | 431 } |
| 166 let ident = base.ident(typeName); | 432 let ident = base.ident(typeName); |
| 167 if (this.isGenericMethodTypeParameterName(typeName)) { | 433 if (this.isGenericMethodTypeParameterName(typeName)) { |
| 168 // DDC generic methods hack - all names that are type parameters to generi
c methods have to be | 434 // DDC generic methods hack - all names that are type parameters to generi
c methods have to be |
| 169 // emitted in comments. | 435 // emitted in comments. |
| 170 this.emit('dynamic/*='); | 436 this.emit('dynamic/*='); |
| 171 this.emit(ident); | 437 this.emit(ident); |
| 172 this.emit('*/'); | 438 this.emit('*/'); |
| 173 return; | 439 return; |
| 174 } | 440 } |
| 175 | 441 |
| 176 if (this.candidateTypes.hasOwnProperty(ident) && this.tc) { | 442 let custom = this.lookupCustomDartTypeName(<ts.Identifier>typeName); |
| 177 let symbol = this.tc.getSymbolAtLocation(typeName); | 443 if (custom) { |
| 178 if (!symbol) { | 444 if (custom.comment) { |
| 179 this.reportMissingType(typeName, ident); | 445 this.emit('/*=' + custom.comment + '*/'); |
| 180 return; | 446 } |
| 181 } | 447 this.emit(custom.name); |
| 182 let fileAndName = this.getFileAndName(typeName, symbol); | 448 } else { |
| 183 if (fileAndName) { | 449 this.visit(typeName); |
| 184 let fileSubs = this.TS_TO_DART_TYPENAMES[fileAndName.fileName]; | 450 } |
| 185 if (fileSubs && fileSubs.hasOwnProperty(fileAndName.qname)) { | 451 } |
| 186 this.emit(fileSubs[fileAndName.qname]); | 452 |
| 187 return; | 453 getSymbolDeclaration(symbol: ts.Symbol, n?: ts.Node): ts.Declaration { |
| 188 } | 454 if (!symbol) return null; |
| 189 } | |
| 190 } | |
| 191 this.emit(ident); | |
| 192 } | |
| 193 | |
| 194 shouldEmitNew(c: ts.CallExpression): boolean { | |
| 195 if (!this.tc) return true; | |
| 196 | |
| 197 let ci = this.getCallInformation(c); | |
| 198 let symbol = ci.symbol; | |
| 199 // getCallInformation returns a symbol if we understand this call. | |
| 200 if (!symbol) return true; | |
| 201 | |
| 202 let loc = this.getFileAndName(c, symbol); | |
| 203 if (!loc) return true; | |
| 204 let {fileName, qname} = loc; | |
| 205 let fileSubs = this.callHandlerReplaceNew[fileName]; | |
| 206 if (!fileSubs) return true; | |
| 207 return !fileSubs[qname]; | |
| 208 } | |
| 209 | |
| 210 private getCallInformation(c: ts.CallExpression): {context?: ts.Expression, sy
mbol?: ts.Symbol} { | |
| 211 let symbol: ts.Symbol; | |
| 212 let context: ts.Expression; | |
| 213 let ident: string; | |
| 214 let expr = c.expression; | |
| 215 | |
| 216 if (expr.kind === ts.SyntaxKind.Identifier) { | |
| 217 // Function call. | |
| 218 ident = base.ident(expr); | |
| 219 if (!this.candidateProperties.hasOwnProperty(ident)) return {}; | |
| 220 symbol = this.tc.getSymbolAtLocation(expr); | |
| 221 if (FACADE_DEBUG) console.error('s:', symbol); | |
| 222 | |
| 223 if (!symbol) { | |
| 224 this.reportMissingType(c, ident); | |
| 225 return {}; | |
| 226 } | |
| 227 | |
| 228 context = null; | |
| 229 } else if (expr.kind === ts.SyntaxKind.PropertyAccessExpression) { | |
| 230 // Method call. | |
| 231 let pa = <ts.PropertyAccessExpression>expr; | |
| 232 ident = base.ident(pa.name); | |
| 233 if (!this.candidateProperties.hasOwnProperty(ident)) return {}; | |
| 234 | |
| 235 symbol = this.tc.getSymbolAtLocation(pa); | |
| 236 if (FACADE_DEBUG) console.error('s:', symbol); | |
| 237 | |
| 238 // Error will be reported by PropertyAccess handling below. | |
| 239 if (!symbol) return {}; | |
| 240 | |
| 241 context = pa.expression; | |
| 242 } | |
| 243 return {context, symbol}; | |
| 244 } | |
| 245 | |
| 246 private getHandler<T>(n: ts.Node, symbol: ts.Symbol, m: ts.Map<ts.Map<T>>): T
{ | |
| 247 let loc = this.getFileAndName(n, symbol); | |
| 248 if (!loc) return null; | |
| 249 let {fileName, qname} = loc; | |
| 250 let fileSubs = m[fileName]; | |
| 251 if (!fileSubs) return null; | |
| 252 return fileSubs[qname]; | |
| 253 } | |
| 254 | |
| 255 private getFileAndName(n: ts.Node, originalSymbol: ts.Symbol): {fileName: stri
ng, qname: string} { | |
| 256 let symbol = originalSymbol; | |
| 257 while (symbol.flags & ts.SymbolFlags.Alias) symbol = this.tc.getAliasedSymbo
l(symbol); | |
| 258 let decl = symbol.valueDeclaration; | 455 let decl = symbol.valueDeclaration; |
| 259 if (!decl) { | 456 if (!decl) { |
| 260 // In the case of a pure declaration with no assignment, there is no value
declared. | 457 // In the case of a pure declaration with no assignment, there is no value
declared. |
| 261 // Just grab the first declaration, hoping it is declared once. | 458 // Just grab the first declaration, hoping it is declared once. |
| 262 if (!symbol.declarations || symbol.declarations.length === 0) { | 459 if (!symbol.declarations || symbol.declarations.length === 0) { |
| 263 this.reportError(n, 'no declarations for symbol ' + originalSymbol.name)
; | 460 this.reportError(n, 'no declarations for symbol ' + symbol.name); |
| 461 return; |
| 462 } |
| 463 decl = symbol.declarations[0]; |
| 464 } |
| 465 return decl; |
| 466 } |
| 467 |
| 468 generateDartName(identifier: ts.EntityName): string { |
| 469 let ret = this.lookupCustomDartTypeName(identifier); |
| 470 if (ret) { |
| 471 // TODO(jacobr): reduce the number of times we write this pattern. |
| 472 if (ret.comment) { |
| 473 return ret.name + '/*=' + ret.comment + '*/'; |
| 474 } else { |
| 475 return ret.name; |
| 476 } |
| 477 } |
| 478 // TODO(jacobr): handle library import prefixes better. This generally works |
| 479 // but is somewhat fragile. |
| 480 return base.ident(identifier); |
| 481 } |
| 482 |
| 483 /** |
| 484 * Returns null if declaration cannot be found or is not valid in Dart. |
| 485 */ |
| 486 getDeclaration(identifier: ts.EntityName): ts.Declaration { |
| 487 let symbol: ts.Symbol; |
| 488 if (!this.tc) return null; |
| 489 |
| 490 symbol = this.tc.getSymbolAtLocation(identifier); |
| 491 let declaration = this.getSymbolDeclaration(symbol, identifier); |
| 492 if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) { |
| 493 let kind = declaration.parent.kind; |
| 494 // Only kinds of TypeParameters supported by Dart. |
| 495 if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.Inte
rfaceDeclaration) { |
| 264 return null; | 496 return null; |
| 265 } | 497 } |
| 266 decl = symbol.declarations[0]; | 498 } |
| 267 } | 499 return declaration; |
| 500 } |
| 501 |
| 502 /** |
| 503 * Returns a custom Dart type name or null if the type isn't a custom Dart |
| 504 * type. |
| 505 */ |
| 506 lookupCustomDartTypeName(identifier: ts.EntityName): |
| 507 {name?: string, comment?: string, keep?: boolean} { |
| 508 let ident = base.ident(identifier); |
| 509 let symbol: ts.Symbol; |
| 510 if (!this.tc) return null; |
| 511 |
| 512 symbol = this.tc.getSymbolAtLocation(identifier); |
| 513 let declaration = this.getSymbolDeclaration(symbol, identifier); |
| 514 if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) { |
| 515 let kind = declaration.parent.kind; |
| 516 // Only kinds of TypeParameters supported by Dart. |
| 517 if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.Inte
rfaceDeclaration) { |
| 518 return {name: 'dynamic', comment: ident}; |
| 519 } |
| 520 } |
| 521 |
| 522 if (this.candidateTypes.hasOwnProperty(ident)) { |
| 523 if (!symbol) { |
| 524 return null; |
| 525 } |
| 526 |
| 527 let fileAndName = this.getFileAndName(identifier, symbol); |
| 528 |
| 529 if (fileAndName) { |
| 530 let fileSubs = TS_TO_DART_TYPENAMES[fileAndName.fileName]; |
| 531 if (fileSubs) { |
| 532 let dartBrowserType = DART_LIBRARIES_FOR_BROWSER_TYPES.hasOwnProperty(
fileAndName.qname); |
| 533 if (dartBrowserType) { |
| 534 this.emitImport(DART_LIBRARIES_FOR_BROWSER_TYPES[fileAndName.qname])
; |
| 535 } |
| 536 if (fileSubs.hasOwnProperty(fileAndName.qname)) { |
| 537 return {name: fileSubs[fileAndName.qname]}; |
| 538 } |
| 539 if (dartBrowserType) { |
| 540 // Not a rename but has a dart core libraries definition. |
| 541 return {name: fileAndName.qname}; |
| 542 } |
| 543 } |
| 544 } |
| 545 } |
| 546 if (symbol) { |
| 547 if (symbol.flags & ts.SymbolFlags.Enum) { |
| 548 // We can't treat JavaScript enums as Dart enums in this case. |
| 549 return {name: 'num', comment: ident}; |
| 550 } |
| 551 // TODO(jacobr): we could choose to only support type alais declarations |
| 552 // for JS interop but it seems handling type alaises is generally helpful |
| 553 // without much risk of generating confusing Dart code. |
| 554 if (declaration.kind === ts.SyntaxKind.TypeAliasDeclaration) { |
| 555 let alias = <ts.TypeAliasDeclaration>declaration; |
| 556 if (alias.typeParameters) { |
| 557 // We can handle this case but currently do not. |
| 558 this.reportError(declaration, 'Type parameters for type alaises are no
t supported'); |
| 559 } |
| 560 return {name: this.generateDartTypeName(alias.type)}; |
| 561 } |
| 562 |
| 563 let kind = declaration.kind; |
| 564 if (kind === ts.SyntaxKind.ClassDeclaration || kind === ts.SyntaxKind.Inte
rfaceDeclaration || |
| 565 kind === ts.SyntaxKind.VariableDeclaration || |
| 566 kind === ts.SyntaxKind.PropertyDeclaration || |
| 567 kind === ts.SyntaxKind.FunctionDeclaration) { |
| 568 let name = this.nameRewriter.lookupName(<base.NamedDeclaration>declarati
on, identifier); |
| 569 if (kind === ts.SyntaxKind.InterfaceDeclaration && |
| 570 base.isFunctionTypedefLikeInterface(<ts.InterfaceDeclaration>declara
tion) && |
| 571 base.getAncestor(identifier, ts.SyntaxKind.HeritageClause)) { |
| 572 // TODO(jacobr): we need to specify a specific call method for this |
| 573 // case if we want to get the most from Dart type checking. |
| 574 return {name: 'Function', comment: name}; |
| 575 } |
| 576 return {name: name, keep: true}; |
| 577 } |
| 578 } |
| 579 return null; |
| 580 } |
| 581 |
| 582 // TODO(jacobr): performance of this method could easily be optimized. |
| 583 /** |
| 584 * This method works around the lack of Dart support for union types |
| 585 * generating a valid Dart type that satisfies all the types passed in. |
| 586 */ |
| 587 toSimpleDartType(types: Array<ts.TypeNode>) { |
| 588 // We use MergeType to ensure that we have already deduped types that are |
| 589 // equivalent even if they aren't obviously identical. |
| 590 // MergedType will also follow typed aliases, etc which allows us to avoid |
| 591 // including that logic here as well. |
| 592 let mergedType = new MergedType(this); |
| 593 types.forEach((type) => { mergedType.merge(type); }); |
| 594 let merged = mergedType.toTypeNode(); |
| 595 if (merged.kind === ts.SyntaxKind.UnionType) { |
| 596 // For union types find a Dart type that satisfies all the types. |
| 597 types = (<ts.UnionTypeNode>merged).types; |
| 598 /** |
| 599 * Generate a common base type for an array of types. |
| 600 * The implemented is currently incomplete often returning null when there |
| 601 * might really be a valid common base type. |
| 602 */ |
| 603 let common: ts.TypeNode = types[0]; |
| 604 for (let i = 1; i < types.length && common != null; ++i) { |
| 605 let type = types[i]; |
| 606 if (common !== type) { |
| 607 if (base.isCallableType(common, this.tc) && base.isCallableType(type,
this.tc)) { |
| 608 // Fall back to a generic Function type if both types are Function. |
| 609 let fn = <ts.FunctionOrConstructorTypeNode>ts.createNode(ts.SyntaxKi
nd.FunctionType); |
| 610 fn.parameters = <ts.NodeArray<ts.ParameterDeclaration>>[]; |
| 611 let parameter = <ts.ParameterDeclaration>ts.createNode(ts.SyntaxKind
.Parameter); |
| 612 parameter.dotDotDotToken = ts.createNode(ts.SyntaxKind.DotDotDotToke
n); |
| 613 let name = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); |
| 614 name.text = 'args'; |
| 615 fn.parameters.push(parameter); |
| 616 common = fn; |
| 617 } else { |
| 618 switch (type.kind) { |
| 619 case ts.SyntaxKind.ArrayType: |
| 620 let array = <ts.ArrayTypeNode>ts.createNode(ts.SyntaxKind.ArrayT
ype); |
| 621 array.elementType = this.toSimpleDartType([ |
| 622 (common as ts.ArrayTypeNode).elementType, (type as ts.ArrayTyp
eNode).elementType |
| 623 ]); |
| 624 common = array; |
| 625 break; |
| 626 // case ts.SyntaxKind |
| 627 case ts.SyntaxKind.TypeReference: |
| 628 if (common.kind !== ts.SyntaxKind.TypeReference) { |
| 629 return null; |
| 630 } |
| 631 common = this.commonSupertype(common, type); |
| 632 break; |
| 633 |
| 634 default: |
| 635 return null; |
| 636 } |
| 637 } |
| 638 } |
| 639 } |
| 640 return common; |
| 641 } |
| 642 return merged; |
| 643 } |
| 644 |
| 645 toTypeNode(type: ts.Type): ts.TypeNode { |
| 646 if (!type) return null; |
| 647 let symbol = type.getSymbol(); |
| 648 if (!symbol) return null; |
| 649 |
| 650 let referenceType = <ts.TypeReferenceNode>ts.createNode(ts.SyntaxKind.TypeRe
ference); |
| 651 // TODO(jacobr): property need to prefix the name better. |
| 652 referenceType.typeName = this.createEntityName(symbol); |
| 653 referenceType.typeName.parent = referenceType; |
| 654 return referenceType; |
| 655 } |
| 656 |
| 657 createEntityName(symbol: ts.Symbol): ts.EntityName { |
| 658 let parts = this.tc.getFullyQualifiedName(symbol).split('.'); |
| 659 let identifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); |
| 660 identifier.text = parts[parts.length - 1]; |
| 661 // TODO(jacobr): do we need to include all parts in the entity name? |
| 662 return identifier; |
| 663 } |
| 664 |
| 665 safeGetBaseTypes(type: ts.InterfaceType): ts.ObjectType[] { |
| 666 // For an unknown, calling TypeChecker.getBaseTypes on an interface |
| 667 // that is a typedef like interface causes the typescript compiler to stack |
| 668 // overflow. Not sure if this is a bug in the typescript compiler or I am |
| 669 // missing something obvious. |
| 670 let declaration = base.getDeclaration(type) as ts.InterfaceDeclaration; |
| 671 if (base.isFunctionTypedefLikeInterface(declaration)) { |
| 672 return []; |
| 673 } |
| 674 return this.tc.getBaseTypes(type); |
| 675 } |
| 676 |
| 677 // TODO(jacobr): all of these subtype checks are fragile and are likely a |
| 678 // mistake. We would be better off handling subtype relationships in Dart |
| 679 // where we could reuse an existing Dart type system. |
| 680 checkTypeSubtypeOf(source: ts.Type, target: ts.Type) { |
| 681 if (source === target) return true; |
| 682 if (!(source.flags & ts.TypeFlags.Interface)) return false; |
| 683 let baseTypes = this.safeGetBaseTypes(source as ts.InterfaceType); |
| 684 for (let i = 0; i < baseTypes.length; ++i) { |
| 685 if (baseTypes[i] === target) return true; |
| 686 } |
| 687 return false; |
| 688 } |
| 689 |
| 690 commonSupertype(nodeA: ts.TypeNode, nodeB: ts.TypeNode): ts.TypeNode { |
| 691 if (nodeA == null || nodeB == null) return null; |
| 692 return this.toTypeNode(this.getCommonSupertype( |
| 693 this.tc.getTypeAtLocation(nodeA), this.tc.getTypeAtLocation(nodeB))); |
| 694 } |
| 695 |
| 696 getCommonSupertype(a: ts.Type, b: ts.Type): ts.Type { |
| 697 if (a === b) return a; |
| 698 |
| 699 if (!(a.flags & ts.TypeFlags.Interface) || !(b.flags & ts.TypeFlags.Interfac
e)) { |
| 700 return null; |
| 701 } |
| 702 |
| 703 let bestCommonSuperType: ts.Type = null; |
| 704 let candidatesA = this.safeGetBaseTypes(a as ts.InterfaceType); |
| 705 candidatesA.push(a); |
| 706 |
| 707 for (let i = 0; i < candidatesA.length; ++i) { |
| 708 let type = candidatesA[i]; |
| 709 if (this.checkTypeSubtypeOf(b, type)) { |
| 710 if (!bestCommonSuperType || this.checkTypeSubtypeOf(bestCommonSuperType,
type)) { |
| 711 bestCommonSuperType = type; |
| 712 } |
| 713 } |
| 714 } |
| 715 return bestCommonSuperType; |
| 716 } |
| 717 |
| 718 private getFileAndName(n: ts.Node, originalSymbol: ts.Symbol): {fileName: stri
ng, qname: string} { |
| 719 let symbol = originalSymbol; |
| 720 while (symbol.flags & ts.SymbolFlags.Alias) symbol = this.tc.getAliasedSymbo
l(symbol); |
| 721 let decl = this.getSymbolDeclaration(symbol, n); |
| 268 | 722 |
| 269 const fileName = decl.getSourceFile().fileName; | 723 const fileName = decl.getSourceFile().fileName; |
| 270 const canonicalFileName = this.getRelativeFileName(fileName) | 724 const canonicalFileName = this.getRelativeFileName(fileName) |
| 271 .replace(/(\.d)?\.ts$/, '') | 725 .replace(/(\.d)?\.ts$/, '') |
| 272 .replace(FACADE_NODE_MODULES_PREFIX, '') | 726 .replace(FACADE_NODE_MODULES_PREFIX, '') |
| 273 .replace(this.typingsRootRegex, ''); | 727 .replace(this.typingsRootRegex, ''); |
| 274 | 728 |
| 275 let qname = this.tc.getFullyQualifiedName(symbol); | 729 let qname = this.tc.getFullyQualifiedName(symbol); |
| 276 // Some Qualified Names include their file name. Might be a bug in TypeScrip
t, | 730 // Some Qualified Names include their file name. Might be a bug in TypeScrip
t, |
| 277 // for the time being just special case. | 731 // for the time being just special case. |
| 278 if (symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Function | ts.Symb
olFlags.Variable)) { | 732 if (symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Function | ts.Symb
olFlags.Variable)) { |
| 279 qname = symbol.getName(); | 733 qname = symbol.getName(); |
| 280 } | 734 } |
| 281 if (FACADE_DEBUG) console.error('fn:', fileName, 'cfn:', canonicalFileName,
'qn:', qname); | 735 if (FACADE_DEBUG) console.error('fn:', fileName, 'cfn:', canonicalFileName,
'qn:', qname); |
| 282 return {fileName: canonicalFileName, qname}; | 736 return {fileName: canonicalFileName, qname}; |
| 283 } | 737 } |
| 284 | |
| 285 private isNamedType(node: ts.Node, fileName: string, qname: string): boolean { | |
| 286 let symbol = this.tc.getTypeAtLocation(node).getSymbol(); | |
| 287 if (!symbol) return false; | |
| 288 let actual = this.getFileAndName(node, symbol); | |
| 289 if (fileName === 'lib' && !(actual.fileName === 'lib' || actual.fileName ===
'lib.es6')) { | |
| 290 return false; | |
| 291 } else { | |
| 292 if (fileName !== actual.fileName) return false; | |
| 293 } | |
| 294 return qname === actual.qname; | |
| 295 } | |
| 296 | |
| 297 private reportMissingType(n: ts.Node, ident: string) { | |
| 298 this.reportError( | |
| 299 n, `Untyped property access to "${ident}" which could be ` + `a special
ts2dart builtin. ` + | |
| 300 `Please add type declarations to disambiguate.`); | |
| 301 } | |
| 302 | |
| 303 isInsideConstExpr(node: ts.Node): boolean { | |
| 304 return this.isConstCall( | |
| 305 <ts.CallExpression>this.getAncestor(node, ts.SyntaxKind.CallExpression))
; | |
| 306 } | |
| 307 | |
| 308 isConstCall(node: ts.Expression): boolean { | |
| 309 return node && node.kind === ts.SyntaxKind.CallExpression && | |
| 310 base.ident((<ts.CallExpression>node).expression) === 'CONST_EXPR'; | |
| 311 } | |
| 312 | |
| 313 private emitMethodCall(name: string, args?: ts.Expression[]) { | |
| 314 this.emit('.'); | |
| 315 this.emitCall(name, args); | |
| 316 } | |
| 317 | |
| 318 private emitCall(name: string, args?: ts.Expression[]) { | |
| 319 this.emit(name); | |
| 320 this.emit('('); | |
| 321 if (args) this.visitList(args); | |
| 322 this.emit(')'); | |
| 323 } | |
| 324 | |
| 325 private stdlibTypeReplacements: ts.Map<string> = { | |
| 326 'Date': 'DateTime', | |
| 327 'Array': 'List', | |
| 328 'XMLHttpRequest': 'HttpRequest', | |
| 329 'Uint8Array': 'Uint8List', | |
| 330 'ArrayBuffer': 'ByteBuffer', | |
| 331 'Promise': 'Future', | |
| 332 | |
| 333 // Dart has two different incompatible DOM APIs | |
| 334 // https://github.com/angular/angular/issues/2770 | |
| 335 'Node': 'dynamic', | |
| 336 'Text': 'dynamic', | |
| 337 'Element': 'dynamic', | |
| 338 'Event': 'dynamic', | |
| 339 'HTMLElement': 'dynamic', | |
| 340 'HTMLAnchorElement': 'dynamic', | |
| 341 'HTMLStyleElement': 'dynamic', | |
| 342 'HTMLInputElement': 'dynamic', | |
| 343 'HTMLDocument': 'dynamic', | |
| 344 'History': 'dynamic', | |
| 345 'Location': 'dynamic', | |
| 346 }; | |
| 347 | |
| 348 private TS_TO_DART_TYPENAMES: ts.Map<ts.Map<string>> = { | |
| 349 'lib': this.stdlibTypeReplacements, | |
| 350 'lib.es6': this.stdlibTypeReplacements, | |
| 351 'angular2/src/facade/lang': {'Date': 'DateTime'}, | |
| 352 | |
| 353 'rxjs/Observable': {'Observable': 'Stream'}, | |
| 354 'es6-promise/es6-promise': {'Promise': 'Future'}, | |
| 355 'es6-shim/es6-shim': {'Promise': 'Future'}, | |
| 356 }; | |
| 357 | |
| 358 private es6Promises: ts.Map<CallHandler> = { | |
| 359 'Promise.catch': (c: ts.CallExpression, context: ts.Expression) => { | |
| 360 this.visit(context); | |
| 361 this.emit('.catchError('); | |
| 362 this.visitList(c.arguments); | |
| 363 this.emit(')'); | |
| 364 }, | |
| 365 'Promise.then': (c: ts.CallExpression, context: ts.Expression) => { | |
| 366 // then() in Dart doesn't support 2 arguments. | |
| 367 this.visit(context); | |
| 368 this.emit('.then('); | |
| 369 this.visit(c.arguments[0]); | |
| 370 this.emit(')'); | |
| 371 if (c.arguments.length > 1) { | |
| 372 this.emit('.catchError('); | |
| 373 this.visit(c.arguments[1]); | |
| 374 this.emit(')'); | |
| 375 } | |
| 376 }, | |
| 377 'Promise': (c: ts.CallExpression, context: ts.Expression) => { | |
| 378 if (c.kind !== ts.SyntaxKind.NewExpression) return true; | |
| 379 this.assert(c, c.arguments.length === 1, 'Promise construction must take 2
arguments.'); | |
| 380 this.assert( | |
| 381 c, c.arguments[0].kind === ts.SyntaxKind.ArrowFunction || | |
| 382 c.arguments[0].kind === ts.SyntaxKind.FunctionExpression, | |
| 383 'Promise argument must be a function expression (or arrow function).')
; | |
| 384 let callback: ts.FunctionLikeDeclaration; | |
| 385 if (c.arguments[0].kind === ts.SyntaxKind.ArrowFunction) { | |
| 386 callback = <ts.FunctionLikeDeclaration>(<ts.ArrowFunction>c.arguments[0]
); | |
| 387 } else if (c.arguments[0].kind === ts.SyntaxKind.FunctionExpression) { | |
| 388 callback = <ts.FunctionLikeDeclaration>(<ts.FunctionExpression>c.argumen
ts[0]); | |
| 389 } | |
| 390 this.assert( | |
| 391 c, callback.parameters.length > 0 && callback.parameters.length < 3, | |
| 392 'Promise executor must take 1 or 2 arguments (resolve and reject).'); | |
| 393 | |
| 394 const completerVarName = this.uniqueId('completer'); | |
| 395 this.assert( | |
| 396 c, callback.parameters[0].name.kind === ts.SyntaxKind.Identifier, | |
| 397 'First argument of the Promise executor is not a straight parameter.')
; | |
| 398 let resolveParameterIdent = <ts.Identifier>(callback.parameters[0].name); | |
| 399 | |
| 400 this.emit('(() {'); // Create a new scope. | |
| 401 this.emit(`Completer ${completerVarName} = new Completer();`); | |
| 402 this.emit('var'); | |
| 403 this.emit(resolveParameterIdent.text); | |
| 404 this.emit(`= ${completerVarName}.complete;`); | |
| 405 | |
| 406 if (callback.parameters.length === 2) { | |
| 407 this.assert( | |
| 408 c, callback.parameters[1].name.kind === ts.SyntaxKind.Identifier, | |
| 409 'First argument of the Promise executor is not a straight parameter.
'); | |
| 410 let rejectParameterIdent = <ts.Identifier>(callback.parameters[1].name); | |
| 411 this.emit('var'); | |
| 412 this.emit(rejectParameterIdent.text); | |
| 413 this.emit(`= ${completerVarName}.completeError;`); | |
| 414 } | |
| 415 this.emit('(()'); | |
| 416 this.visit(callback.body); | |
| 417 this.emit(')();'); | |
| 418 this.emit(`return ${completerVarName}.future;`); | |
| 419 this.emit('})()'); | |
| 420 }, | |
| 421 }; | |
| 422 | |
| 423 private stdlibHandlers: ts.Map<CallHandler> = merge(this.es6Promises, { | |
| 424 'Array.push': (c: ts.CallExpression, context: ts.Expression) => { | |
| 425 this.visit(context); | |
| 426 this.emitMethodCall('add', c.arguments); | |
| 427 }, | |
| 428 'Array.pop': (c: ts.CallExpression, context: ts.Expression) => { | |
| 429 this.visit(context); | |
| 430 this.emitMethodCall('removeLast'); | |
| 431 }, | |
| 432 'Array.shift': (c: ts.CallExpression, context: ts.Expression) => { | |
| 433 this.visit(context); | |
| 434 this.emit('. removeAt ( 0 )'); | |
| 435 }, | |
| 436 'Array.unshift': (c: ts.CallExpression, context: ts.Expression) => { | |
| 437 this.emit('('); | |
| 438 this.visit(context); | |
| 439 if (c.arguments.length === 1) { | |
| 440 this.emit('.. insert ( 0,'); | |
| 441 this.visit(c.arguments[0]); | |
| 442 this.emit(') ) . length'); | |
| 443 } else { | |
| 444 this.emit('.. insertAll ( 0, ['); | |
| 445 this.visitList(c.arguments); | |
| 446 this.emit(']) ) . length'); | |
| 447 } | |
| 448 }, | |
| 449 'Array.map': (c: ts.CallExpression, context: ts.Expression) => { | |
| 450 this.visit(context); | |
| 451 this.emitMethodCall('map', c.arguments); | |
| 452 this.emitMethodCall('toList'); | |
| 453 }, | |
| 454 'Array.filter': (c: ts.CallExpression, context: ts.Expression) => { | |
| 455 this.visit(context); | |
| 456 this.emitMethodCall('where', c.arguments); | |
| 457 this.emitMethodCall('toList'); | |
| 458 }, | |
| 459 'Array.some': (c: ts.CallExpression, context: ts.Expression) => { | |
| 460 this.visit(context); | |
| 461 this.emitMethodCall('any', c.arguments); | |
| 462 }, | |
| 463 'Array.slice': (c: ts.CallExpression, context: ts.Expression) => { | |
| 464 this.emitCall('ListWrapper.slice', [context, ...c.arguments]); | |
| 465 }, | |
| 466 'Array.splice': (c: ts.CallExpression, context: ts.Expression) => { | |
| 467 this.emitCall('ListWrapper.splice', [context, ...c.arguments]); | |
| 468 }, | |
| 469 'Array.concat': (c: ts.CallExpression, context: ts.Expression) => { | |
| 470 this.emit('( new List . from ('); | |
| 471 this.visit(context); | |
| 472 this.emit(')'); | |
| 473 c.arguments.forEach(arg => { | |
| 474 if (!this.isNamedType(arg, 'lib', 'Array')) { | |
| 475 this.reportError(arg, 'Array.concat only takes Array arguments'); | |
| 476 } | |
| 477 this.emit('.. addAll ('); | |
| 478 this.visit(arg); | |
| 479 this.emit(')'); | |
| 480 }); | |
| 481 this.emit(')'); | |
| 482 }, | |
| 483 'Array.join': (c: ts.CallExpression, context: ts.Expression) => { | |
| 484 this.visit(context); | |
| 485 if (c.arguments.length) { | |
| 486 this.emitMethodCall('join', c.arguments); | |
| 487 } else { | |
| 488 this.emit('. join ( "," )'); | |
| 489 } | |
| 490 }, | |
| 491 'Array.reduce': (c: ts.CallExpression, context: ts.Expression) => { | |
| 492 this.visit(context); | |
| 493 | |
| 494 if (c.arguments.length >= 2) { | |
| 495 this.emitMethodCall('fold', [c.arguments[1], c.arguments[0]]); | |
| 496 } else { | |
| 497 this.emit('. fold ( null ,'); | |
| 498 this.visit(c.arguments[0]); | |
| 499 this.emit(')'); | |
| 500 } | |
| 501 }, | |
| 502 'ArrayConstructor.isArray': (c: ts.CallExpression, context: ts.Expression) =
> { | |
| 503 this.emit('( ('); | |
| 504 this.visitList(c.arguments); // Should only be 1. | |
| 505 this.emit(')'); | |
| 506 this.emit('is List'); | |
| 507 this.emit(')'); | |
| 508 }, | |
| 509 'Console.log': (c: ts.CallExpression, context: ts.Expression) => { | |
| 510 this.emit('print('); | |
| 511 if (c.arguments.length === 1) { | |
| 512 this.visit(c.arguments[0]); | |
| 513 } else { | |
| 514 this.emit('['); | |
| 515 this.visitList(c.arguments); | |
| 516 this.emit('].join(" ")'); | |
| 517 } | |
| 518 this.emit(')'); | |
| 519 }, | |
| 520 'RegExp.exec': (c: ts.CallExpression, context: ts.Expression) => { | |
| 521 if (context.kind !== ts.SyntaxKind.RegularExpressionLiteral) { | |
| 522 // Fail if the exec call isn't made directly on a regexp literal. | |
| 523 // Multiple exec calls on the same global regexp have side effects | |
| 524 // (each return the next match), which we can't reproduce with a simple | |
| 525 // Dart RegExp (users should switch to some facade / wrapper instead). | |
| 526 this.reportError( | |
| 527 c, 'exec is only supported on regexp literals, ' + | |
| 528 'to avoid side-effect of multiple calls on global regexps.'); | |
| 529 } | |
| 530 if (c.parent.kind === ts.SyntaxKind.ElementAccessExpression) { | |
| 531 // The result of the exec call is used for immediate indexed access: | |
| 532 // this use-case can be accommodated by RegExp.firstMatch, which returns | |
| 533 // a Match instance with operator[] which returns groups (special index | |
| 534 // 0 returns the full text of the match). | |
| 535 this.visit(context); | |
| 536 this.emitMethodCall('firstMatch', c.arguments); | |
| 537 } else { | |
| 538 // In the general case, we want to return a List. To transform a Match | |
| 539 // into a List of its groups, we alias it in a local closure that we | |
| 540 // call with the Match value. We are then able to use the group method | |
| 541 // to generate a List large enough to hold groupCount groups + the | |
| 542 // full text of the match at special group index 0. | |
| 543 this.emit('((match) => new List.generate(1 + match.groupCount, match.gro
up))('); | |
| 544 this.visit(context); | |
| 545 this.emitMethodCall('firstMatch', c.arguments); | |
| 546 this.emit(')'); | |
| 547 } | |
| 548 }, | |
| 549 'RegExp.test': (c: ts.CallExpression, context: ts.Expression) => { | |
| 550 this.visit(context); | |
| 551 this.emitMethodCall('hasMatch', c.arguments); | |
| 552 }, | |
| 553 'String.substr': (c: ts.CallExpression, context: ts.Expression) => { | |
| 554 this.reportError( | |
| 555 c, 'substr is unsupported, use substring (but beware of the different
semantics!)'); | |
| 556 this.visit(context); | |
| 557 this.emitMethodCall('substr', c.arguments); | |
| 558 }, | |
| 559 }); | |
| 560 | |
| 561 private es6Collections: ts.Map<CallHandler> = { | |
| 562 'Map.set': (c: ts.CallExpression, context: ts.Expression) => { | |
| 563 this.visit(context); | |
| 564 this.emit('['); | |
| 565 this.visit(c.arguments[0]); | |
| 566 this.emit(']'); | |
| 567 this.emit('='); | |
| 568 this.visit(c.arguments[1]); | |
| 569 }, | |
| 570 'Map.get': (c: ts.CallExpression, context: ts.Expression) => { | |
| 571 this.visit(context); | |
| 572 this.emit('['); | |
| 573 this.visit(c.arguments[0]); | |
| 574 this.emit(']'); | |
| 575 }, | |
| 576 'Map.has': (c: ts.CallExpression, context: ts.Expression) => { | |
| 577 this.visit(context); | |
| 578 this.emitMethodCall('containsKey', c.arguments); | |
| 579 }, | |
| 580 'Map.delete': (c: ts.CallExpression, context: ts.Expression) => { | |
| 581 // JS Map.delete(k) returns whether k was present in the map, | |
| 582 // convert to: | |
| 583 // (Map.containsKey(k) && (Map.remove(k) !== null || true)) | |
| 584 // (Map.remove(k) !== null || true) is required to always returns true | |
| 585 // when Map.containsKey(k) | |
| 586 this.emit('('); | |
| 587 this.visit(context); | |
| 588 this.emitMethodCall('containsKey', c.arguments); | |
| 589 this.emit('&& ('); | |
| 590 this.visit(context); | |
| 591 this.emitMethodCall('remove', c.arguments); | |
| 592 this.emit('!= null || true ) )'); | |
| 593 }, | |
| 594 'Map.forEach': (c: ts.CallExpression, context: ts.Expression) => { | |
| 595 let cb: any; | |
| 596 let params: any; | |
| 597 | |
| 598 switch (c.arguments[0].kind) { | |
| 599 case ts.SyntaxKind.FunctionExpression: | |
| 600 cb = <ts.FunctionExpression>(c.arguments[0]); | |
| 601 params = cb.parameters; | |
| 602 if (params.length !== 2) { | |
| 603 this.reportError(c, 'Map.forEach callback requires exactly two argum
ents'); | |
| 604 return; | |
| 605 } | |
| 606 this.visit(context); | |
| 607 this.emit('. forEach ( ('); | |
| 608 this.visit(params[1]); | |
| 609 this.emit(','); | |
| 610 this.visit(params[0]); | |
| 611 this.emit(')'); | |
| 612 this.visit(cb.body); | |
| 613 this.emit(')'); | |
| 614 break; | |
| 615 | |
| 616 case ts.SyntaxKind.ArrowFunction: | |
| 617 cb = <ts.ArrowFunction>(c.arguments[0]); | |
| 618 params = cb.parameters; | |
| 619 if (params.length !== 2) { | |
| 620 this.reportError(c, 'Map.forEach callback requires exactly two argum
ents'); | |
| 621 return; | |
| 622 } | |
| 623 this.visit(context); | |
| 624 this.emit('. forEach ( ('); | |
| 625 this.visit(params[1]); | |
| 626 this.emit(','); | |
| 627 this.visit(params[0]); | |
| 628 this.emit(')'); | |
| 629 if (cb.body.kind !== ts.SyntaxKind.Block) { | |
| 630 this.emit('=>'); | |
| 631 } | |
| 632 this.visit(cb.body); | |
| 633 this.emit(')'); | |
| 634 break; | |
| 635 | |
| 636 default: | |
| 637 this.visit(context); | |
| 638 this.emit('. forEach ( ( k , v ) => ('); | |
| 639 this.visit(c.arguments[0]); | |
| 640 this.emit(') ( v , k ) )'); | |
| 641 break; | |
| 642 } | |
| 643 }, | |
| 644 'Array.find': (c: ts.CallExpression, context: ts.Expression) => { | |
| 645 this.visit(context); | |
| 646 this.emit('. firstWhere ('); | |
| 647 this.visit(c.arguments[0]); | |
| 648 this.emit(', orElse : ( ) => null )'); | |
| 649 }, | |
| 650 }; | |
| 651 | |
| 652 private callHandlerReplaceNew: ts.Map<ts.Map<boolean>> = { | |
| 653 'es6-promise/es6-promise': {'Promise': true}, | |
| 654 'es6-shim/es6-shim': {'Promise': true}, | |
| 655 }; | |
| 656 | |
| 657 private callHandlers: ts.Map<ts.Map<CallHandler>> = { | |
| 658 'lib': this.stdlibHandlers, | |
| 659 'lib.es6': this.stdlibHandlers, | |
| 660 'es6-promise/es6-promise': this.es6Promises, | |
| 661 'es6-shim/es6-shim': merge(this.es6Promises, this.es6Collections), | |
| 662 'es6-collections/es6-collections': this.es6Collections, | |
| 663 'angular2/manual_typings/globals': this.es6Collections, | |
| 664 'angular2/src/facade/collection': { | |
| 665 'Map': (c: ts.CallExpression, context: ts.Expression): boolean => { | |
| 666 // The actual Map constructor is special cased for const calls. | |
| 667 if (!this.isInsideConstExpr(c)) return true; | |
| 668 if (c.arguments.length) { | |
| 669 this.reportError(c, 'Arguments on a Map constructor in a const are uns
upported'); | |
| 670 } | |
| 671 if (c.typeArguments) { | |
| 672 this.emit('<'); | |
| 673 this.visitList(c.typeArguments); | |
| 674 this.emit('>'); | |
| 675 } | |
| 676 this.emit('{ }'); | |
| 677 return false; | |
| 678 }, | |
| 679 }, | |
| 680 'angular2/src/core/di/forward_ref': { | |
| 681 'forwardRef': (c: ts.CallExpression, context: ts.Expression) => { | |
| 682 // The special function forwardRef translates to an unwrapped value in D
art. | |
| 683 const callback = <ts.FunctionExpression>c.arguments[0]; | |
| 684 if (callback.kind !== ts.SyntaxKind.ArrowFunction) { | |
| 685 this.reportError(c, 'forwardRef takes only arrow functions'); | |
| 686 return; | |
| 687 } | |
| 688 this.visit(callback.body); | |
| 689 }, | |
| 690 }, | |
| 691 'angular2/src/facade/lang': { | |
| 692 'CONST_EXPR': (c: ts.CallExpression, context: ts.Expression) => { | |
| 693 // `const` keyword is emitted in the array literal handling, as it needs
to be transitive. | |
| 694 this.visitList(c.arguments); | |
| 695 }, | |
| 696 'normalizeBlank': (c: ts.CallExpression, context: ts.Expression) => { | |
| 697 // normalizeBlank is a noop in Dart, so erase it. | |
| 698 this.visitList(c.arguments); | |
| 699 }, | |
| 700 }, | |
| 701 }; | |
| 702 | |
| 703 private es6CollectionsProp: ts.Map<PropertyHandler> = { | |
| 704 'Map.size': (p: ts.PropertyAccessExpression) => { | |
| 705 this.visit(p.expression); | |
| 706 this.emit('.'); | |
| 707 this.emit('length'); | |
| 708 }, | |
| 709 }; | |
| 710 private es6PromisesProp: ts.Map<PropertyHandler> = { | |
| 711 'resolve': (p: ts.PropertyAccessExpression) => { | |
| 712 this.visit(p.expression); | |
| 713 this.emit('.value'); | |
| 714 }, | |
| 715 'reject': (p: ts.PropertyAccessExpression) => { | |
| 716 this.visit(p.expression); | |
| 717 this.emit('.error'); | |
| 718 }, | |
| 719 }; | |
| 720 | |
| 721 private propertyHandlers: ts.Map<ts.Map<PropertyHandler>> = { | |
| 722 'es6-shim/es6-shim': this.es6CollectionsProp, | |
| 723 'es6-collections/es6-collections': this.es6CollectionsProp, | |
| 724 'es6-promise/es6-promise': this.es6PromisesProp, | |
| 725 }; | |
| 726 } | 738 } |
| OLD | NEW |