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

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: Fix types Created 4 years, 3 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
« no previous file with comments | « lib/expression.ts ('k') | lib/literal.ts » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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[], 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 }
OLDNEW
« no previous file with comments | « lib/expression.ts ('k') | lib/literal.ts » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698