OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dev_compiler.src.codegen.js_codegen; | 5 library dev_compiler.src.codegen.js_codegen; |
6 | 6 |
7 import 'dart:collection' show HashSet, HashMap; | 7 import 'dart:collection' show HashSet, HashMap; |
8 import 'dart:io' show Directory, File; | |
9 | 8 |
10 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
11 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; | 10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; |
12 import 'package:analyzer/src/generated/constant.dart'; | 11 import 'package:analyzer/src/generated/constant.dart'; |
13 import 'package:analyzer/src/generated/element.dart'; | 12 import 'package:analyzer/src/generated/element.dart'; |
14 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; | 13 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
15 import 'package:analyzer/src/generated/scanner.dart' | 14 import 'package:analyzer/src/generated/scanner.dart' |
16 show StringToken, Token, TokenType; | 15 show StringToken, Token, TokenType; |
17 import 'package:source_maps/source_maps.dart' as srcmaps show Printer; | |
18 import 'package:source_maps/source_maps.dart' show SourceMapSpan; | |
19 import 'package:source_span/source_span.dart' show SourceLocation; | |
20 import 'package:path/path.dart' as path; | 16 import 'package:path/path.dart' as path; |
21 | 17 |
22 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder; | 18 import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder; |
23 | 19 |
24 // TODO(jmesserly): import from its own package | 20 // TODO(jmesserly): import from its own package |
25 import 'package:dev_compiler/src/js/js_ast.dart' as JS; | 21 import 'package:dev_compiler/src/js/js_ast.dart' as JS; |
26 import 'package:dev_compiler/src/js/js_ast.dart' show js; | 22 import 'package:dev_compiler/src/js/js_ast.dart' show js; |
27 | 23 |
28 import 'package:dev_compiler/src/checker/rules.dart'; | 24 import 'package:dev_compiler/src/checker/rules.dart'; |
29 import 'package:dev_compiler/src/info.dart'; | 25 import 'package:dev_compiler/src/info.dart'; |
30 import 'package:dev_compiler/src/options.dart'; | 26 import 'package:dev_compiler/src/options.dart'; |
31 import 'package:dev_compiler/src/utils.dart'; | 27 import 'package:dev_compiler/src/utils.dart'; |
32 | 28 |
33 import 'code_generator.dart'; | 29 import 'code_generator.dart'; |
34 import 'js_names.dart'; | 30 import 'js_names.dart' show JSTemporary, invalidJSStaticMethodName; |
35 import 'js_metalet.dart'; | 31 import 'js_metalet.dart'; |
| 32 import 'js_printer.dart' show writeJsLibrary; |
| 33 import 'side_effect_analysis.dart'; |
36 | 34 |
37 // Various dynamic helpers we call. | 35 // Various dynamic helpers we call. |
38 // If renaming these, make sure to check other places like the | 36 // If renaming these, make sure to check other places like the |
39 // dart_runtime.js file and comments. | 37 // dart_runtime.js file and comments. |
40 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can | 38 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can |
41 // import and generate calls to, rather than dart_runtime.js | 39 // import and generate calls to, rather than dart_runtime.js |
42 const DPUT = 'dput'; | 40 const DPUT = 'dput'; |
43 const DLOAD = 'dload'; | 41 const DLOAD = 'dload'; |
44 const DINDEX = 'dindex'; | 42 const DINDEX = 'dindex'; |
45 const DSETINDEX = 'dsetindex'; | 43 const DSETINDEX = 'dsetindex'; |
(...skipping 1597 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1643 /// | 1641 /// |
1644 /// If the expression does not end up using `x` more than once, or if those | 1642 /// If the expression does not end up using `x` more than once, or if those |
1645 /// expressions can be treated as stateless (e.g. they are non-mutated | 1643 /// expressions can be treated as stateless (e.g. they are non-mutated |
1646 /// variables), then the resulting code will be simplified automatically. | 1644 /// variables), then the resulting code will be simplified automatically. |
1647 /// | 1645 /// |
1648 /// [scope] will be mutated to contain the new temporary's initialization. | 1646 /// [scope] will be mutated to contain the new temporary's initialization. |
1649 Expression _bindValue( | 1647 Expression _bindValue( |
1650 Map<String, JS.Expression> scope, String name, Expression expr, | 1648 Map<String, JS.Expression> scope, String name, Expression expr, |
1651 {Expression context}) { | 1649 {Expression context}) { |
1652 // No need to do anything for stateless expressions. | 1650 // No need to do anything for stateless expressions. |
1653 if (_isStateless(expr, context)) return expr; | 1651 if (isStateless(expr, context)) return expr; |
1654 | 1652 |
1655 var t = _createTemporary('#$name', expr.staticType); | 1653 var t = _createTemporary('#$name', expr.staticType); |
1656 scope[name] = _visit(expr); | 1654 scope[name] = _visit(expr); |
1657 return t; | 1655 return t; |
1658 } | 1656 } |
1659 | 1657 |
1660 /// Desugars postfix increment. | 1658 /// Desugars postfix increment. |
1661 /// | 1659 /// |
1662 /// In the general case [expr] can be one of [IndexExpression], | 1660 /// In the general case [expr] can be one of [IndexExpression], |
1663 /// [PrefixExpression] or [PropertyAccess] and we need to | 1661 /// [PrefixExpression] or [PropertyAccess] and we need to |
(...skipping 654 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2318 ..addAll(e.accessors.map((m) => m.name)); | 2316 ..addAll(e.accessors.map((m) => m.name)); |
2319 for (var name in names) { | 2317 for (var name in names) { |
2320 _extensionMethods.putIfAbsent(name, () => []).add(type); | 2318 _extensionMethods.putIfAbsent(name, () => []).add(type); |
2321 } | 2319 } |
2322 } | 2320 } |
2323 } | 2321 } |
2324 | 2322 |
2325 TypeProvider get types => rules.provider; | 2323 TypeProvider get types => rules.provider; |
2326 | 2324 |
2327 String generateLibrary(LibraryUnit unit, LibraryInfo info) { | 2325 String generateLibrary(LibraryUnit unit, LibraryInfo info) { |
2328 var jsTree = | 2326 var codegen = new JSCodegenVisitor(info, rules, _extensionMethods); |
2329 new JSCodegenVisitor(info, rules, _extensionMethods).emitLibrary(unit); | 2327 var module = codegen.emitLibrary(unit); |
2330 | 2328 var dir = path.join(outDir, jsOutputPath(info, root)); |
2331 var outputPath = path.join(outDir, jsOutputPath(info, root)); | 2329 return writeJsLibrary(module, dir, emitSourceMaps: options.emitSourceMaps); |
2332 new Directory(path.dirname(outputPath)).createSync(recursive: true); | |
2333 | |
2334 if (options.emitSourceMaps) { | |
2335 var outFilename = path.basename(outputPath); | |
2336 var printer = new srcmaps.Printer(outFilename); | |
2337 _writeNode( | |
2338 new SourceMapPrintingContext(printer, path.dirname(outputPath)), | |
2339 jsTree); | |
2340 printer.add('//# sourceMappingURL=$outFilename.map'); | |
2341 // Write output file and source map | |
2342 var text = printer.text; | |
2343 new File(outputPath).writeAsStringSync(text); | |
2344 new File('$outputPath.map').writeAsStringSync(printer.map); | |
2345 return computeHash(text); | |
2346 } else { | |
2347 var text = jsNodeToString(jsTree); | |
2348 new File(outputPath).writeAsStringSync(text); | |
2349 return computeHash(text); | |
2350 } | |
2351 } | 2330 } |
2352 } | 2331 } |
2353 | 2332 |
2354 void _writeNode(JS.JavaScriptPrintingContext context, JS.Node node) { | |
2355 var opts = new JS.JavaScriptPrintingOptions(allowKeywordsInProperties: true); | |
2356 node.accept(new JS.Printer(opts, context, localNamer: new JSNamer(node))); | |
2357 } | |
2358 | |
2359 String jsNodeToString(JS.Node node) { | |
2360 var context = new JS.SimpleJavaScriptPrintingContext(); | |
2361 _writeNode(context, node); | |
2362 return context.getText(); | |
2363 } | |
2364 | |
2365 /// Choose a canonical name from the library element. | 2333 /// Choose a canonical name from the library element. |
2366 /// This never uses the library's name (the identifier in the `library` | 2334 /// This never uses the library's name (the identifier in the `library` |
2367 /// declaration) as it doesn't have any meaningful rules enforced. | 2335 /// declaration) as it doesn't have any meaningful rules enforced. |
2368 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); | 2336 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); |
2369 | 2337 |
2370 /// Shorthand for identifier-like property names. | 2338 /// Shorthand for identifier-like property names. |
2371 /// For now, we emit them as strings and the printer restores them to | 2339 /// For now, we emit them as strings and the printer restores them to |
2372 /// identifiers if it can. | 2340 /// identifiers if it can. |
2373 // TODO(jmesserly): avoid the round tripping through quoted form. | 2341 // TODO(jmesserly): avoid the round tripping through quoted form. |
2374 JS.LiteralString _propertyName(String name) => js.string(name, "'"); | 2342 JS.LiteralString _propertyName(String name) => js.string(name, "'"); |
2375 | 2343 |
2376 /// Path to file that will be generated for [info]. In case it's url is a | 2344 /// Path to file that will be generated for [info]. In case it's url is a |
2377 /// `file:` url, we use [root] to determine the relative path from the entry | 2345 /// `file:` url, we use [root] to determine the relative path from the entry |
2378 /// point file. | 2346 /// point file. |
2379 String jsOutputPath(LibraryInfo info, Uri root) { | 2347 String jsOutputPath(LibraryInfo info, Uri root) { |
2380 var uri = info.library.source.uri; | 2348 var uri = info.library.source.uri; |
2381 var filepath = '${path.withoutExtension(uri.path)}.js'; | 2349 var filepath = '${path.withoutExtension(uri.path)}.js'; |
2382 if (uri.scheme == 'dart') { | 2350 if (uri.scheme == 'dart') { |
2383 filepath = 'dart/$filepath'; | 2351 filepath = 'dart/$filepath'; |
2384 } else if (uri.scheme == 'file') { | 2352 } else if (uri.scheme == 'file') { |
2385 filepath = path.relative(filepath, from: path.dirname(root.path)); | 2353 filepath = path.relative(filepath, from: path.dirname(root.path)); |
2386 } else { | 2354 } else { |
2387 assert(uri.scheme == 'package'); | 2355 assert(uri.scheme == 'package'); |
2388 // filepath is good here, we want the output to start with a directory | 2356 // filepath is good here, we want the output to start with a directory |
2389 // matching the package name. | 2357 // matching the package name. |
2390 } | 2358 } |
2391 return filepath; | 2359 return filepath; |
2392 } | 2360 } |
2393 | 2361 |
2394 class SourceMapPrintingContext extends JS.JavaScriptPrintingContext { | |
2395 final srcmaps.Printer printer; | |
2396 final String outputDir; | |
2397 | |
2398 CompilationUnit unit; | |
2399 Uri uri; | |
2400 | |
2401 SourceMapPrintingContext(this.printer, this.outputDir); | |
2402 | |
2403 void emit(String string) { | |
2404 printer.add(string); | |
2405 } | |
2406 | |
2407 void enterNode(JS.Node jsNode) { | |
2408 AstNode node = jsNode.sourceInformation; | |
2409 if (node is CompilationUnit) { | |
2410 unit = node; | |
2411 uri = _makeRelativeUri(unit.element.source.uri); | |
2412 return; | |
2413 } | |
2414 if (unit == null || node == null || node.offset == -1) return; | |
2415 | |
2416 var loc = _location(node.offset); | |
2417 var name = _getIdentifier(node); | |
2418 if (name != null) { | |
2419 // TODO(jmesserly): mark only uses the beginning of the span, but | |
2420 // we're required to pass this as a valid span. | |
2421 var end = _location(node.end); | |
2422 printer.mark(new SourceMapSpan(loc, end, name, isIdentifier: true)); | |
2423 } else { | |
2424 printer.mark(loc); | |
2425 } | |
2426 } | |
2427 | |
2428 SourceLocation _location(int offset) => locationForOffset(unit, uri, offset); | |
2429 | |
2430 Uri _makeRelativeUri(Uri src) { | |
2431 return new Uri(path: path.relative(src.path, from: outputDir)); | |
2432 } | |
2433 | |
2434 void exitNode(JS.Node jsNode) { | |
2435 AstNode node = jsNode.sourceInformation; | |
2436 if (node is CompilationUnit) { | |
2437 unit = null; | |
2438 uri = null; | |
2439 return; | |
2440 } | |
2441 if (unit == null || node == null || node.offset == -1) return; | |
2442 | |
2443 // TODO(jmesserly): in many cases marking the end will be unncessary. | |
2444 printer.mark(_location(node.end)); | |
2445 } | |
2446 | |
2447 String _getIdentifier(AstNode node) { | |
2448 if (node is SimpleIdentifier) return node.name; | |
2449 return null; | |
2450 } | |
2451 } | |
2452 | |
2453 /// True is the expression can be evaluated multiple times without causing | |
2454 /// code execution. This is true for final fields. This can be true for local | |
2455 /// variables, if: | |
2456 /// * they are not assigned within the [context]. | |
2457 /// * they are not assigned in a function closure anywhere. | |
2458 /// True is the expression can be evaluated multiple times without causing | |
2459 /// code execution. This is true for final fields. This can be true for local | |
2460 /// variables, if: | |
2461 /// | |
2462 /// * they are not assigned within the [context] scope. | |
2463 /// * they are not assigned in a function closure anywhere. | |
2464 /// | |
2465 /// This method is used to avoid creating temporaries in cases where we know | |
2466 /// we can safely re-evaluate [node] multiple times in [context]. This lets | |
2467 /// us generate prettier code. | |
2468 /// | |
2469 /// This method is conservative: it should never return `true` unless it is | |
2470 /// certain the [node] is stateless, because generated code may rely on the | |
2471 /// correctness of a `true` value. However it may return `false` for things | |
2472 /// that are in fact, stateless. | |
2473 bool _isStateless(Expression node, [AstNode context]) { | |
2474 if (node is SimpleIdentifier) { | |
2475 var e = node.staticElement; | |
2476 if (e is PropertyAccessorElement) e = e.variable; | |
2477 if (e is VariableElement && !e.isSynthetic) { | |
2478 if (e.isFinal) return true; | |
2479 if (e is LocalVariableElement || e is ParameterElement) { | |
2480 // make sure the local isn't mutated in the context. | |
2481 return !_isPotentiallyMutated(e, context); | |
2482 } | |
2483 } | |
2484 } | |
2485 return false; | |
2486 } | |
2487 | |
2488 /// Returns true if the local variable is potentially mutated within [context]. | |
2489 /// This accounts for closures that may have been created outside of [context]. | |
2490 bool _isPotentiallyMutated(VariableElement e, [AstNode context]) { | |
2491 if (e.isPotentiallyMutatedInClosure) return true; | |
2492 if (e.isPotentiallyMutatedInScope) { | |
2493 // Need to visit the context looking for assignment to this local. | |
2494 if (context != null) { | |
2495 var visitor = new _AssignmentFinder(e); | |
2496 context.accept(visitor); | |
2497 return visitor._potentiallyMutated; | |
2498 } | |
2499 return true; | |
2500 } | |
2501 return false; | |
2502 } | |
2503 | |
2504 /// Adapted from VariableResolverVisitor. Finds an assignment to a given | |
2505 /// local variable. | |
2506 class _AssignmentFinder extends RecursiveAstVisitor { | |
2507 final VariableElement _variable; | |
2508 bool _potentiallyMutated = false; | |
2509 | |
2510 _AssignmentFinder(this._variable); | |
2511 | |
2512 @override | |
2513 visitSimpleIdentifier(SimpleIdentifier node) { | |
2514 // Ignore if qualified. | |
2515 AstNode parent = node.parent; | |
2516 if (parent is PrefixedIdentifier && | |
2517 identical(parent.identifier, node)) return; | |
2518 if (parent is PropertyAccess && | |
2519 identical(parent.propertyName, node)) return; | |
2520 if (parent is MethodInvocation && | |
2521 identical(parent.methodName, node)) return; | |
2522 if (parent is ConstructorName) return; | |
2523 if (parent is Label) return; | |
2524 | |
2525 if (node.inSetterContext() && node.staticElement == _variable) { | |
2526 _potentiallyMutated = true; | |
2527 } | |
2528 } | |
2529 } | |
2530 | |
2531 // TODO(jmesserly): validate the library. See issue #135. | 2362 // TODO(jmesserly): validate the library. See issue #135. |
2532 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; | 2363 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; |
2533 | 2364 |
2534 // TODO(jacobr): we would like to do something like the following | 2365 // TODO(jacobr): we would like to do something like the following |
2535 // but we don't have summary support yet. | 2366 // but we don't have summary support yet. |
2536 // bool _supportJsExtensionMethod(AnnotatedNode node) => | 2367 // bool _supportJsExtensionMethod(AnnotatedNode node) => |
2537 // _getAnnotation(node, "SupportJsExtensionMethod") != null; | 2368 // _getAnnotation(node, "SupportJsExtensionMethod") != null; |
OLD | NEW |