Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Side by Side Diff: lib/facade_converter.ts

Issue 2225953002: Strip more unused features. (Closed) Base URL: git@github.com:dart-lang/js_facade_gen.git@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698