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[], insideComment: boolean, seperator = ','
): string { |
| 266 let that = this; |
| 267 return types.map((type) => { return that.generateDartTypeName(type, insideCo
mment); }) |
| 268 .join(seperator); |
| 269 } |
| 270 |
| 271 maybeGenerateTypeArguments( |
| 272 n: {typeArguments?: ts.NodeArray<ts.TypeNode>}, insideComment: boolean): s
tring { |
| 273 if (!n.typeArguments) return ''; |
| 274 return '<' + this.generateTypeList(n.typeArguments, insideComment) + '>'; |
| 275 } |
| 276 |
| 277 generateDartTypeName(node: ts.TypeNode, insideComment: boolean): string { |
| 278 let name: string; |
| 279 let comment: string; |
| 280 if (!node) { |
| 281 return 'dynamic'; |
| 282 } |
| 283 switch (node.kind) { |
| 284 case ts.SyntaxKind.TypeQuery: |
| 285 let query = <ts.TypeQueryNode>node; |
| 286 name = 'dynamic'; |
| 287 name += '/* Dart does not support TypeQuery: typeof ' + base.ident(query
.exprName) + ' */'; |
| 288 break; |
| 289 case ts.SyntaxKind.LastTypeNode: |
| 290 let type = (node as ts.ParenthesizedTypeNode).type; |
| 291 if (!type) { |
| 292 // This case occurs for String literal types |
| 293 comment = node.getText(); |
| 294 // TODO(jacobr): find a better way to detect string literal types. |
| 295 name = comment[0] === '"' ? 'String' : 'dynamic'; |
| 296 break; |
| 297 } |
| 298 return this.generateDartTypeName(type, insideComment); |
| 299 case ts.SyntaxKind.TypePredicate: |
| 300 return this.generateDartTypeName((node as ts.TypePredicateNode).type, in
sideComment); |
| 301 case ts.SyntaxKind.TupleType: |
| 302 let tuple = <ts.TupleTypeNode>node; |
| 303 name = 'List<'; |
| 304 let mergedType = new MergedType(this); |
| 305 tuple.elementTypes.forEach((t) => mergedType.merge(t)); |
| 306 name += this.generateDartTypeName(mergedType.toTypeNode(), insideComment
); |
| 307 name += '>'; |
| 308 comment = 'Tuple<' + this.generateTypeList(tuple.elementTypes, insideCom
ment) + '>'; |
| 309 break; |
| 310 case ts.SyntaxKind.UnionType: |
| 311 let union = <ts.UnionTypeNode>node; |
| 312 // TODO(jacobr): this isn't fundamentally JS Interop specific but we |
| 313 // choose to be more aggressive at finding a useful value for the |
| 314 // union when in JS Interop mode while otherwise we expect that union |
| 315 // types will not be used extensively. |
| 316 let simpleType = this.toSimpleDartType(union.types); |
| 317 if (simpleType) { |
| 318 name = this.generateDartTypeName(simpleType, insideComment); |
| 319 } else { |
| 320 name = 'dynamic'; |
| 321 } |
| 322 let types = union.types; |
| 323 comment = this.generateTypeList(types, true, '|'); |
| 324 break; |
| 325 case ts.SyntaxKind.TypePredicate: |
| 326 return this.generateDartTypeName((node as ts.TypePredicateNode).type, in
sideComment); |
| 327 case ts.SyntaxKind.TypeReference: |
| 328 let typeRef = <ts.TypeReferenceNode>node; |
| 329 name = this.generateDartName(typeRef.typeName, insideComment) + |
| 330 this.maybeGenerateTypeArguments(typeRef, insideComment); |
| 331 break; |
| 332 case ts.SyntaxKind.TypeLiteral: |
| 333 let members = (<ts.TypeLiteralNode>node).members; |
| 334 if (members.length === 1 && members[0].kind === ts.SyntaxKind.IndexSigna
ture) { |
| 335 let indexSig = <ts.IndexSignatureDeclaration>(members[0]); |
| 336 if (indexSig.parameters.length > 1) { |
| 337 this.reportError(indexSig, 'Expected an index signature to have a si
ngle parameter'); |
| 338 } |
| 339 // Unfortunately for JS interop, we cannot treat JS Objects as Dart |
| 340 // Map objects. We could treat them as JSMap<indexSig.type> |
| 341 // if we define a base JSMap type that is Map like but not actually |
| 342 // a map. |
| 343 name = 'dynamic'; |
| 344 comment = 'JSMap of <' + this.generateDartTypeName(indexSig.parameters
[0].type, true) + |
| 345 ',' + this.generateDartTypeName(indexSig.type, true) + '>'; |
| 346 } else { |
| 347 name = 'dynamic'; |
| 348 comment = node.getText(); |
| 349 } |
| 350 break; |
| 351 case ts.SyntaxKind.FunctionType: |
| 352 let callSignature = <ts.FunctionOrConstructorTypeNode>node; |
| 353 let parameters = callSignature.parameters; |
| 354 |
| 355 // Use a function signature from package:func where possible. |
| 356 let numOptional = numOptionalParameters(parameters); |
| 357 let isVoid = callSignature.type && callSignature.type.kind === ts.Syntax
Kind.VoidKeyword; |
| 358 if (parameters.length <= MAX_DART_FUNC_ACTION_PARAMETERS && |
| 359 numOptional <= MAX_DART_FUNC_ACTION_PARAMETERS_OPTIONAL && !hasVarAr
gs(parameters)) { |
| 360 this.emitImport('package:func/func.dart'); |
| 361 let typeDefName = (isVoid) ? 'VoidFunc' : 'Func'; |
| 362 typeDefName += parameters.length; |
| 363 if (numOptional > 0) { |
| 364 typeDefName += 'Opt' + numOptional; |
| 365 } |
| 366 name = typeDefName; |
| 367 let numArgs = parameters.length + (isVoid ? 0 : 1); |
| 368 if (numArgs > 0) { |
| 369 name += '<'; |
| 370 } |
| 371 let isFirst = true; |
| 372 for (let i = 0; i < parameters.length; ++i) { |
| 373 if (isFirst) { |
| 374 isFirst = false; |
| 375 } else { |
| 376 name += ', '; |
| 377 } |
| 378 name += this.generateDartTypeName(parameters[i].type, insideComment)
; |
| 379 } |
| 380 if (!isVoid) { |
| 381 if (!isFirst) { |
| 382 name += ', '; |
| 383 } |
| 384 name += this.generateDartTypeName(callSignature.type, insideComment)
; |
| 385 } |
| 386 if (numArgs > 0) { |
| 387 name += '>'; |
| 388 } |
| 389 } else { |
| 390 name = 'Function'; |
| 391 if (node.getSourceFile()) { |
| 392 comment = node.getText(); |
| 393 } |
| 394 } |
| 395 break; |
| 396 case ts.SyntaxKind.ArrayType: |
| 397 name = 'List' + |
| 398 '<' + this.generateDartTypeName((<ts.ArrayTypeNode>node).elementType
, insideComment) + |
| 399 '>'; |
| 400 break; |
| 401 case ts.SyntaxKind.NumberKeyword: |
| 402 name = 'num'; |
| 403 break; |
| 404 case ts.SyntaxKind.StringLiteral: |
| 405 case ts.SyntaxKind.StringKeyword: |
| 406 name = 'String'; |
| 407 break; |
| 408 case ts.SyntaxKind.VoidKeyword: |
| 409 name = 'void'; |
| 410 break; |
| 411 case ts.SyntaxKind.BooleanKeyword: |
| 412 name = 'bool'; |
| 413 break; |
| 414 case ts.SyntaxKind.AnyKeyword: |
| 415 name = 'dynamic'; |
| 416 break; |
| 417 default: |
| 418 this.reportError(node, 'Unexpected TypeNode kind'); |
| 419 } |
| 420 if (name == null) { |
| 421 name = 'XXX NULLNAME'; |
| 422 } |
| 423 |
| 424 name = name.trim(); |
| 425 return base.formatType(name, comment, insideComment); |
| 426 } |
| 427 |
161 visitTypeName(typeName: ts.EntityName) { | 428 visitTypeName(typeName: ts.EntityName) { |
162 if (typeName.kind !== ts.SyntaxKind.Identifier) { | 429 if (typeName.kind !== ts.SyntaxKind.Identifier) { |
163 this.visit(typeName); | 430 this.visit(typeName); |
164 return; | 431 return; |
165 } | 432 } |
166 let ident = base.ident(typeName); | 433 let ident = base.ident(typeName); |
167 if (this.isGenericMethodTypeParameterName(typeName)) { | 434 if (this.isGenericMethodTypeParameterName(typeName)) { |
168 // DDC generic methods hack - all names that are type parameters to generi
c methods have to be | 435 // DDC generic methods hack - all names that are type parameters to generi
c methods have to be |
169 // emitted in comments. | 436 // emitted in comments. |
170 this.emit('dynamic/*='); | 437 this.emitType('dynamic', ident); |
171 this.emit(ident); | |
172 this.emit('*/'); | |
173 return; | 438 return; |
174 } | 439 } |
175 | 440 |
176 if (this.candidateTypes.hasOwnProperty(ident) && this.tc) { | 441 let custom = this.lookupCustomDartTypeName(<ts.Identifier>typeName, this.ins
ideCodeComment); |
177 let symbol = this.tc.getSymbolAtLocation(typeName); | 442 if (custom) { |
178 if (!symbol) { | 443 if (custom.comment) { |
179 this.reportMissingType(typeName, ident); | 444 this.emitType(custom.name, custom.comment); |
180 return; | 445 } else { |
181 } | 446 this.emit(custom.name); |
182 let fileAndName = this.getFileAndName(typeName, symbol); | 447 } |
183 if (fileAndName) { | 448 } else { |
184 let fileSubs = this.TS_TO_DART_TYPENAMES[fileAndName.fileName]; | 449 this.visit(typeName); |
185 if (fileSubs && fileSubs.hasOwnProperty(fileAndName.qname)) { | 450 } |
186 this.emit(fileSubs[fileAndName.qname]); | 451 } |
187 return; | 452 |
188 } | 453 getSymbolDeclaration(symbol: ts.Symbol, n?: ts.Node): ts.Declaration { |
189 } | 454 if (!symbol) return null; |
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, insideComment: boolean): string { |
| 469 let ret = this.lookupCustomDartTypeName(identifier, insideComment); |
| 470 if (ret) return base.formatType(ret.name, ret.comment, insideComment); |
| 471 // TODO(jacobr): handle library import prefixes better. This generally works |
| 472 // but is somewhat fragile. |
| 473 return base.ident(identifier); |
| 474 } |
| 475 |
| 476 /** |
| 477 * Returns null if declaration cannot be found or is not valid in Dart. |
| 478 */ |
| 479 getDeclaration(identifier: ts.EntityName): ts.Declaration { |
| 480 let symbol: ts.Symbol; |
| 481 if (!this.tc) return null; |
| 482 |
| 483 symbol = this.tc.getSymbolAtLocation(identifier); |
| 484 let declaration = this.getSymbolDeclaration(symbol, identifier); |
| 485 if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) { |
| 486 let kind = declaration.parent.kind; |
| 487 // Only kinds of TypeParameters supported by Dart. |
| 488 if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.Inte
rfaceDeclaration) { |
264 return null; | 489 return null; |
265 } | 490 } |
266 decl = symbol.declarations[0]; | 491 } |
267 } | 492 return declaration; |
| 493 } |
| 494 |
| 495 /** |
| 496 * Returns a custom Dart type name or null if the type isn't a custom Dart |
| 497 * type. |
| 498 */ |
| 499 lookupCustomDartTypeName(identifier: ts.EntityName, insideComment: boolean): |
| 500 {name?: string, comment?: string, keep?: boolean} { |
| 501 let ident = base.ident(identifier); |
| 502 let symbol: ts.Symbol; |
| 503 if (!this.tc) return null; |
| 504 |
| 505 symbol = this.tc.getSymbolAtLocation(identifier); |
| 506 let declaration = this.getSymbolDeclaration(symbol, identifier); |
| 507 if (symbol && symbol.flags & ts.SymbolFlags.TypeParameter) { |
| 508 let kind = declaration.parent.kind; |
| 509 // Only kinds of TypeParameters supported by Dart. |
| 510 if (kind !== ts.SyntaxKind.ClassDeclaration && kind !== ts.SyntaxKind.Inte
rfaceDeclaration) { |
| 511 return {name: 'dynamic', comment: ident}; |
| 512 } |
| 513 } |
| 514 |
| 515 if (this.candidateTypes.hasOwnProperty(ident)) { |
| 516 if (!symbol) { |
| 517 return null; |
| 518 } |
| 519 |
| 520 let fileAndName = this.getFileAndName(identifier, symbol); |
| 521 |
| 522 if (fileAndName) { |
| 523 let fileSubs = TS_TO_DART_TYPENAMES[fileAndName.fileName]; |
| 524 if (fileSubs) { |
| 525 let dartBrowserType = DART_LIBRARIES_FOR_BROWSER_TYPES.hasOwnProperty(
fileAndName.qname); |
| 526 if (dartBrowserType) { |
| 527 this.emitImport(DART_LIBRARIES_FOR_BROWSER_TYPES[fileAndName.qname])
; |
| 528 } |
| 529 if (fileSubs.hasOwnProperty(fileAndName.qname)) { |
| 530 return {name: fileSubs[fileAndName.qname]}; |
| 531 } |
| 532 if (dartBrowserType) { |
| 533 // Not a rename but has a dart core libraries definition. |
| 534 return {name: fileAndName.qname}; |
| 535 } |
| 536 } |
| 537 } |
| 538 } |
| 539 if (symbol) { |
| 540 if (symbol.flags & ts.SymbolFlags.Enum) { |
| 541 // We can't treat JavaScript enums as Dart enums in this case. |
| 542 return {name: 'num', comment: ident}; |
| 543 } |
| 544 // TODO(jacobr): we could choose to only support type alais declarations |
| 545 // for JS interop but it seems handling type alaises is generally helpful |
| 546 // without much risk of generating confusing Dart code. |
| 547 if (declaration.kind === ts.SyntaxKind.TypeAliasDeclaration) { |
| 548 let alias = <ts.TypeAliasDeclaration>declaration; |
| 549 if (alias.typeParameters) { |
| 550 // We can handle this case but currently do not. |
| 551 this.reportError(declaration, 'Type parameters for type alaises are no
t supported'); |
| 552 } |
| 553 return {name: this.generateDartTypeName(alias.type, insideComment)}; |
| 554 } |
| 555 |
| 556 let kind = declaration.kind; |
| 557 if (kind === ts.SyntaxKind.ClassDeclaration || kind === ts.SyntaxKind.Inte
rfaceDeclaration || |
| 558 kind === ts.SyntaxKind.VariableDeclaration || |
| 559 kind === ts.SyntaxKind.PropertyDeclaration || |
| 560 kind === ts.SyntaxKind.FunctionDeclaration) { |
| 561 let name = this.nameRewriter.lookupName(<base.NamedDeclaration>declarati
on, identifier); |
| 562 if (kind === ts.SyntaxKind.InterfaceDeclaration && |
| 563 base.isFunctionTypedefLikeInterface(<ts.InterfaceDeclaration>declara
tion) && |
| 564 base.getAncestor(identifier, ts.SyntaxKind.HeritageClause)) { |
| 565 // TODO(jacobr): we need to specify a specific call method for this |
| 566 // case if we want to get the most from Dart type checking. |
| 567 return {name: 'Function', comment: name}; |
| 568 } |
| 569 return {name: name, keep: true}; |
| 570 } |
| 571 } |
| 572 return null; |
| 573 } |
| 574 |
| 575 // TODO(jacobr): performance of this method could easily be optimized. |
| 576 /** |
| 577 * This method works around the lack of Dart support for union types |
| 578 * generating a valid Dart type that satisfies all the types passed in. |
| 579 */ |
| 580 toSimpleDartType(types: Array<ts.TypeNode>) { |
| 581 // We use MergeType to ensure that we have already deduped types that are |
| 582 // equivalent even if they aren't obviously identical. |
| 583 // MergedType will also follow typed aliases, etc which allows us to avoid |
| 584 // including that logic here as well. |
| 585 let mergedType = new MergedType(this); |
| 586 types.forEach((type) => { mergedType.merge(type); }); |
| 587 let merged = mergedType.toTypeNode(); |
| 588 if (merged.kind === ts.SyntaxKind.UnionType) { |
| 589 // For union types find a Dart type that satisfies all the types. |
| 590 types = (<ts.UnionTypeNode>merged).types; |
| 591 /** |
| 592 * Generate a common base type for an array of types. |
| 593 * The implemented is currently incomplete often returning null when there |
| 594 * might really be a valid common base type. |
| 595 */ |
| 596 let common: ts.TypeNode = types[0]; |
| 597 for (let i = 1; i < types.length && common != null; ++i) { |
| 598 let type = types[i]; |
| 599 if (common !== type) { |
| 600 if (base.isCallableType(common, this.tc) && base.isCallableType(type,
this.tc)) { |
| 601 // Fall back to a generic Function type if both types are Function. |
| 602 let fn = <ts.FunctionOrConstructorTypeNode>ts.createNode(ts.SyntaxKi
nd.FunctionType); |
| 603 fn.parameters = <ts.NodeArray<ts.ParameterDeclaration>>[]; |
| 604 let parameter = <ts.ParameterDeclaration>ts.createNode(ts.SyntaxKind
.Parameter); |
| 605 parameter.dotDotDotToken = ts.createNode(ts.SyntaxKind.DotDotDotToke
n); |
| 606 let name = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); |
| 607 name.text = 'args'; |
| 608 fn.parameters.push(parameter); |
| 609 common = fn; |
| 610 } else { |
| 611 switch (type.kind) { |
| 612 case ts.SyntaxKind.ArrayType: |
| 613 if (common.kind !== ts.SyntaxKind.ArrayType) { |
| 614 return null; |
| 615 } |
| 616 let array = <ts.ArrayTypeNode>ts.createNode(ts.SyntaxKind.ArrayT
ype); |
| 617 array.elementType = this.toSimpleDartType([ |
| 618 (common as ts.ArrayTypeNode).elementType, (type as ts.ArrayTyp
eNode).elementType |
| 619 ]); |
| 620 common = array; |
| 621 break; |
| 622 // case ts.SyntaxKind |
| 623 case ts.SyntaxKind.TypeReference: |
| 624 if (common.kind !== ts.SyntaxKind.TypeReference) { |
| 625 return null; |
| 626 } |
| 627 common = this.commonSupertype(common, type); |
| 628 break; |
| 629 |
| 630 default: |
| 631 return null; |
| 632 } |
| 633 } |
| 634 } |
| 635 } |
| 636 return common; |
| 637 } |
| 638 return merged; |
| 639 } |
| 640 |
| 641 toTypeNode(type: ts.Type): ts.TypeNode { |
| 642 if (!type) return null; |
| 643 let symbol = type.getSymbol(); |
| 644 if (!symbol) return null; |
| 645 |
| 646 let referenceType = <ts.TypeReferenceNode>ts.createNode(ts.SyntaxKind.TypeRe
ference); |
| 647 // TODO(jacobr): property need to prefix the name better. |
| 648 referenceType.typeName = this.createEntityName(symbol); |
| 649 referenceType.typeName.parent = referenceType; |
| 650 return referenceType; |
| 651 } |
| 652 |
| 653 createEntityName(symbol: ts.Symbol): ts.EntityName { |
| 654 let parts = this.tc.getFullyQualifiedName(symbol).split('.'); |
| 655 let identifier = <ts.Identifier>ts.createNode(ts.SyntaxKind.Identifier); |
| 656 identifier.text = parts[parts.length - 1]; |
| 657 // TODO(jacobr): do we need to include all parts in the entity name? |
| 658 return identifier; |
| 659 } |
| 660 |
| 661 safeGetBaseTypes(type: ts.InterfaceType): ts.ObjectType[] { |
| 662 // For an unknown, calling TypeChecker.getBaseTypes on an interface |
| 663 // that is a typedef like interface causes the typescript compiler to stack |
| 664 // overflow. Not sure if this is a bug in the typescript compiler or I am |
| 665 // missing something obvious. |
| 666 let declaration = base.getDeclaration(type) as ts.InterfaceDeclaration; |
| 667 if (base.isFunctionTypedefLikeInterface(declaration)) { |
| 668 return []; |
| 669 } |
| 670 return this.tc.getBaseTypes(type); |
| 671 } |
| 672 |
| 673 // TODO(jacobr): all of these subtype checks are fragile and are likely a |
| 674 // mistake. We would be better off handling subtype relationships in Dart |
| 675 // where we could reuse an existing Dart type system. |
| 676 checkTypeSubtypeOf(source: ts.Type, target: ts.Type) { |
| 677 if (source === target) return true; |
| 678 if (!(source.flags & ts.TypeFlags.Interface)) return false; |
| 679 let baseTypes = this.safeGetBaseTypes(source as ts.InterfaceType); |
| 680 for (let i = 0; i < baseTypes.length; ++i) { |
| 681 if (baseTypes[i] === target) return true; |
| 682 } |
| 683 return false; |
| 684 } |
| 685 |
| 686 commonSupertype(nodeA: ts.TypeNode, nodeB: ts.TypeNode): ts.TypeNode { |
| 687 if (nodeA == null || nodeB == null) return null; |
| 688 return this.toTypeNode(this.getCommonSupertype( |
| 689 this.tc.getTypeAtLocation(nodeA), this.tc.getTypeAtLocation(nodeB))); |
| 690 } |
| 691 |
| 692 getCommonSupertype(a: ts.Type, b: ts.Type): ts.Type { |
| 693 if (a === b) return a; |
| 694 // This logic was probably a mistake. It adds a lot of complexity and we can |
| 695 // do better performing these calculations in the Dart analyzer based |
| 696 // directly on the union types specified in comments. |
| 697 return null; |
| 698 /* |
| 699 if (!(a.flags & ts.TypeFlags.Interface) || !(b.flags & ts.TypeFlags.Inte
rface)) { |
| 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(bestCommonSuperT
ype, type)) { |
| 711 bestCommonSuperType = type; |
| 712 } |
| 713 } |
| 714 } |
| 715 return bestCommonSuperType; |
| 716 */ |
| 717 } |
| 718 |
| 719 private getFileAndName(n: ts.Node, originalSymbol: ts.Symbol): {fileName: stri
ng, qname: string} { |
| 720 let symbol = originalSymbol; |
| 721 while (symbol.flags & ts.SymbolFlags.Alias) symbol = this.tc.getAliasedSymbo
l(symbol); |
| 722 let decl = this.getSymbolDeclaration(symbol, n); |
268 | 723 |
269 const fileName = decl.getSourceFile().fileName; | 724 const fileName = decl.getSourceFile().fileName; |
270 const canonicalFileName = this.getRelativeFileName(fileName) | 725 const canonicalFileName = this.getRelativeFileName(fileName) |
271 .replace(/(\.d)?\.ts$/, '') | 726 .replace(/(\.d)?\.ts$/, '') |
272 .replace(FACADE_NODE_MODULES_PREFIX, '') | 727 .replace(FACADE_NODE_MODULES_PREFIX, '') |
273 .replace(this.typingsRootRegex, ''); | 728 .replace(this.typingsRootRegex, ''); |
274 | 729 |
275 let qname = this.tc.getFullyQualifiedName(symbol); | 730 let qname = this.tc.getFullyQualifiedName(symbol); |
276 // Some Qualified Names include their file name. Might be a bug in TypeScrip
t, | 731 // Some Qualified Names include their file name. Might be a bug in TypeScrip
t, |
277 // for the time being just special case. | 732 // for the time being just special case. |
278 if (symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Function | ts.Symb
olFlags.Variable)) { | 733 if (symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Function | ts.Symb
olFlags.Variable)) { |
279 qname = symbol.getName(); | 734 qname = symbol.getName(); |
280 } | 735 } |
281 if (FACADE_DEBUG) console.error('fn:', fileName, 'cfn:', canonicalFileName,
'qn:', qname); | 736 if (FACADE_DEBUG) console.error('fn:', fileName, 'cfn:', canonicalFileName,
'qn:', qname); |
282 return {fileName: canonicalFileName, qname}; | 737 return {fileName: canonicalFileName, qname}; |
283 } | 738 } |
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 } | 739 } |
OLD | NEW |