| 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 | 8 |
| 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
| 10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; | 10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 import 'package:dev_compiler/src/js/js_ast.dart' show js; | 24 import 'package:dev_compiler/src/js/js_ast.dart' show js; |
| 25 | 25 |
| 26 import 'package:dev_compiler/src/checker/rules.dart'; | 26 import 'package:dev_compiler/src/checker/rules.dart'; |
| 27 import 'package:dev_compiler/src/dependency_graph.dart'; | 27 import 'package:dev_compiler/src/dependency_graph.dart'; |
| 28 import 'package:dev_compiler/src/info.dart'; | 28 import 'package:dev_compiler/src/info.dart'; |
| 29 import 'package:dev_compiler/src/options.dart'; | 29 import 'package:dev_compiler/src/options.dart'; |
| 30 import 'package:dev_compiler/src/utils.dart'; | 30 import 'package:dev_compiler/src/utils.dart'; |
| 31 | 31 |
| 32 import 'code_generator.dart'; | 32 import 'code_generator.dart'; |
| 33 import 'js_field_storage.dart'; | 33 import 'js_field_storage.dart'; |
| 34 import 'js_names.dart' show JSTemporary, invalidJSStaticMethodName; | 34 import 'js_names.dart' as JS; |
| 35 import 'js_metalet.dart'; | 35 import 'js_metalet.dart' as JS; |
| 36 import 'js_printer.dart' show writeJsLibrary; | 36 import 'js_printer.dart' show writeJsLibrary; |
| 37 import 'side_effect_analysis.dart'; | 37 import 'side_effect_analysis.dart'; |
| 38 | 38 |
| 39 // Various dynamic helpers we call. | 39 // Various dynamic helpers we call. |
| 40 // If renaming these, make sure to check other places like the | 40 // If renaming these, make sure to check other places like the |
| 41 // dart_runtime.js file and comments. | 41 // dart_runtime.js file and comments. |
| 42 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can | 42 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can |
| 43 // import and generate calls to, rather than dart_runtime.js | 43 // import and generate calls to, rather than dart_runtime.js |
| 44 const DPUT = 'dput'; | 44 const DPUT = 'dput'; |
| 45 const DLOAD = 'dload'; | 45 const DLOAD = 'dload'; |
| (...skipping 18 matching lines...) Expand all Loading... |
| 64 SimpleIdentifier _cascadeTarget; | 64 SimpleIdentifier _cascadeTarget; |
| 65 | 65 |
| 66 /// The variable for the current catch clause | 66 /// The variable for the current catch clause |
| 67 SimpleIdentifier _catchParameter; | 67 SimpleIdentifier _catchParameter; |
| 68 | 68 |
| 69 ConstantEvaluator _constEvaluator; | 69 ConstantEvaluator _constEvaluator; |
| 70 | 70 |
| 71 final _exports = new Set<String>(); | 71 final _exports = new Set<String>(); |
| 72 final _lazyFields = <VariableDeclaration>[]; | 72 final _lazyFields = <VariableDeclaration>[]; |
| 73 final _properties = <FunctionDeclaration>[]; | 73 final _properties = <FunctionDeclaration>[]; |
| 74 final _privateNames = new HashMap<String, JSTemporary>(); | 74 final _privateNames = new HashMap<String, JS.TemporaryId>(); |
| 75 final _extensionMethodNames = new HashSet<String>(); | 75 final _extensionMethodNames = new HashSet<String>(); |
| 76 final _pendingStatements = <JS.Statement>[]; | 76 final _pendingStatements = <JS.Statement>[]; |
| 77 final _temps = new HashMap<Element, JSTemporary>(); | 77 final _temps = new HashMap<Element, JS.TemporaryId>(); |
| 78 | 78 |
| 79 /// The name for the library's exports inside itself. | 79 /// The name for the library's exports inside itself. |
| 80 /// This much be a constant because we interpolate it into template strings, | 80 /// This much be a constant because we interpolate it into template strings, |
| 81 /// and otherwise it would break caching for them. | 81 /// and otherwise it would break caching for them. |
| 82 /// `exports` was chosen as the most similar to ES module patterns. | 82 /// `exports` was chosen as the most similar to ES module patterns. |
| 83 final JSTemporary _exportsVar = new JSTemporary('exports'); | 83 final _exportsVar = new JS.TemporaryId('exports'); |
| 84 final JSTemporary _namedArgTemp = new JSTemporary('opts'); | 84 final _namedArgTemp = new JS.TemporaryId('opts'); |
| 85 | 85 |
| 86 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or | 86 /// Classes we have not emitted yet. Values can be [ClassDeclaration] or |
| 87 /// [ClassTypeAlias]. | 87 /// [ClassTypeAlias]. |
| 88 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); | 88 final _pendingClasses = new HashMap<Element, CompilationUnitMember>(); |
| 89 | 89 |
| 90 /// Memoized results of [_lazyClass]. | 90 /// Memoized results of [_lazyClass]. |
| 91 final _lazyClassMemo = new HashMap<Element, bool>(); | 91 final _lazyClassMemo = new HashMap<Element, bool>(); |
| 92 | 92 |
| 93 /// Memoized results of [_inLibraryCycle]. | 93 /// Memoized results of [_inLibraryCycle]. |
| 94 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); | 94 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); |
| (...skipping 995 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1090 if (name[0] == '#') { | 1090 if (name[0] == '#') { |
| 1091 return new JS.InterpolatedExpression(name.substring(1)); | 1091 return new JS.InterpolatedExpression(name.substring(1)); |
| 1092 } else { | 1092 } else { |
| 1093 return _getTemp(element, name); | 1093 return _getTemp(element, name); |
| 1094 } | 1094 } |
| 1095 } | 1095 } |
| 1096 | 1096 |
| 1097 return new JS.Identifier(name); | 1097 return new JS.Identifier(name); |
| 1098 } | 1098 } |
| 1099 | 1099 |
| 1100 JSTemporary _getTemp(Object key, String name) => | 1100 JS.TemporaryId _getTemp(Object key, String name) => |
| 1101 _temps.putIfAbsent(key, () => new JSTemporary(name)); | 1101 _temps.putIfAbsent(key, () => new JS.TemporaryId(name)); |
| 1102 | 1102 |
| 1103 JS.ArrayInitializer _emitTypeNames(List<DartType> types) { | 1103 JS.ArrayInitializer _emitTypeNames(List<DartType> types) { |
| 1104 return new JS.ArrayInitializer(types.map(_emitTypeName).toList()); | 1104 return new JS.ArrayInitializer(types.map(_emitTypeName).toList()); |
| 1105 } | 1105 } |
| 1106 | 1106 |
| 1107 JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) { | 1107 JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) { |
| 1108 var properties = <JS.Property>[]; | 1108 var properties = <JS.Property>[]; |
| 1109 types.forEach((name, type) { | 1109 types.forEach((name, type) { |
| 1110 var key = new JS.LiteralString(name); | 1110 var key = new JS.LiteralString(name); |
| 1111 var value = _emitTypeName(type); | 1111 var value = _emitTypeName(type); |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1190 @override | 1190 @override |
| 1191 JS.Expression visitAssignmentExpression(AssignmentExpression node) { | 1191 JS.Expression visitAssignmentExpression(AssignmentExpression node) { |
| 1192 var left = node.leftHandSide; | 1192 var left = node.leftHandSide; |
| 1193 var right = node.rightHandSide; | 1193 var right = node.rightHandSide; |
| 1194 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); | 1194 if (node.operator.type == TokenType.EQ) return _emitSet(left, right); |
| 1195 return _emitOpAssign( | 1195 return _emitOpAssign( |
| 1196 left, right, node.operator.lexeme[0], node.staticElement, | 1196 left, right, node.operator.lexeme[0], node.staticElement, |
| 1197 context: node); | 1197 context: node); |
| 1198 } | 1198 } |
| 1199 | 1199 |
| 1200 JSMetaLet _emitOpAssign( | 1200 JS.MetaLet _emitOpAssign( |
| 1201 Expression left, Expression right, String op, ExecutableElement element, | 1201 Expression left, Expression right, String op, ExecutableElement element, |
| 1202 {Expression context}) { | 1202 {Expression context}) { |
| 1203 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions | 1203 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions |
| 1204 // (for example, x is IndexExpression) we evaluate those once. | 1204 // (for example, x is IndexExpression) we evaluate those once. |
| 1205 var vars = {}; | 1205 var vars = {}; |
| 1206 var lhs = _bindLeftHandSide(vars, left, context: context); | 1206 var lhs = _bindLeftHandSide(vars, left, context: context); |
| 1207 var inc = AstBuilder.binaryExpression(lhs, op, right); | 1207 var inc = AstBuilder.binaryExpression(lhs, op, right); |
| 1208 inc.staticElement = element; | 1208 inc.staticElement = element; |
| 1209 inc.staticType = getStaticType(left); | 1209 inc.staticType = getStaticType(left); |
| 1210 return new JSMetaLet(vars, [_emitSet(lhs, inc)]); | 1210 return new JS.MetaLet(vars, [_emitSet(lhs, inc)]); |
| 1211 } | 1211 } |
| 1212 | 1212 |
| 1213 JS.Expression _emitSet(Expression lhs, Expression rhs) { | 1213 JS.Expression _emitSet(Expression lhs, Expression rhs) { |
| 1214 if (lhs is IndexExpression) { | 1214 if (lhs is IndexExpression) { |
| 1215 return _emitSend(_getTarget(lhs), '[]=', [lhs.index, rhs]); | 1215 return _emitSend(_getTarget(lhs), '[]=', [lhs.index, rhs]); |
| 1216 } | 1216 } |
| 1217 | 1217 |
| 1218 Expression target = null; | 1218 Expression target = null; |
| 1219 SimpleIdentifier id; | 1219 SimpleIdentifier id; |
| 1220 if (lhs is PropertyAccess) { | 1220 if (lhs is PropertyAccess) { |
| (...skipping 550 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1771 /// We also need to ensure we can return the original value of the expression, | 1771 /// We also need to ensure we can return the original value of the expression, |
| 1772 /// and that it is only evaluated once. | 1772 /// and that it is only evaluated once. |
| 1773 /// | 1773 /// |
| 1774 /// We desugar this using let*. | 1774 /// We desugar this using let*. |
| 1775 /// | 1775 /// |
| 1776 /// For example, `expr1[expr2]++` can be transformed to this: | 1776 /// For example, `expr1[expr2]++` can be transformed to this: |
| 1777 /// | 1777 /// |
| 1778 /// // psuedocode mix of Scheme and JS: | 1778 /// // psuedocode mix of Scheme and JS: |
| 1779 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) | 1779 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) |
| 1780 /// | 1780 /// |
| 1781 /// The [JSMetaLet] nodes automatically simplify themselves if they can. | 1781 /// The [JS.JS.MetaLet] nodes automatically simplify themselves if they can. |
| 1782 /// For example, if the result value is not used, then `t` goes away. | 1782 /// For example, if the result value is not used, then `t` goes away. |
| 1783 @override | 1783 @override |
| 1784 JS.Expression visitPostfixExpression(PostfixExpression node) { | 1784 JS.Expression visitPostfixExpression(PostfixExpression node) { |
| 1785 var op = node.operator; | 1785 var op = node.operator; |
| 1786 var expr = node.operand; | 1786 var expr = node.operand; |
| 1787 | 1787 |
| 1788 var dispatchType = getStaticType(expr); | 1788 var dispatchType = getStaticType(expr); |
| 1789 if (unaryOperationIsPrimitive(dispatchType)) { | 1789 if (unaryOperationIsPrimitive(dispatchType)) { |
| 1790 if (_isNonNullableExpression(expr)) { | 1790 if (_isNonNullableExpression(expr)) { |
| 1791 return js.call('#$op', _visit(expr)); | 1791 return js.call('#$op', _visit(expr)); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 1802 // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value | 1802 // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value |
| 1803 // and `x1` is the new value for `x`. | 1803 // and `x1` is the new value for `x`. |
| 1804 var x = _bindValue(vars, 'x', left, context: expr); | 1804 var x = _bindValue(vars, 'x', left, context: expr); |
| 1805 | 1805 |
| 1806 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; | 1806 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; |
| 1807 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one) | 1807 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one) |
| 1808 ..staticElement = node.staticElement | 1808 ..staticElement = node.staticElement |
| 1809 ..staticType = getStaticType(expr); | 1809 ..staticType = getStaticType(expr); |
| 1810 | 1810 |
| 1811 var body = [_emitSet(left, increment), _visit(x)]; | 1811 var body = [_emitSet(left, increment), _visit(x)]; |
| 1812 return new JSMetaLet(vars, body, statelessResult: true); | 1812 return new JS.MetaLet(vars, body, statelessResult: true); |
| 1813 } | 1813 } |
| 1814 | 1814 |
| 1815 @override | 1815 @override |
| 1816 JS.Expression visitPrefixExpression(PrefixExpression node) { | 1816 JS.Expression visitPrefixExpression(PrefixExpression node) { |
| 1817 var op = node.operator; | 1817 var op = node.operator; |
| 1818 var expr = node.operand; | 1818 var expr = node.operand; |
| 1819 | 1819 |
| 1820 var dispatchType = getStaticType(expr); | 1820 var dispatchType = getStaticType(expr); |
| 1821 if (unaryOperationIsPrimitive(dispatchType)) { | 1821 if (unaryOperationIsPrimitive(dispatchType)) { |
| 1822 if (_isNonNullableExpression(expr)) { | 1822 if (_isNonNullableExpression(expr)) { |
| 1823 return js.call('$op#', _visit(expr)); | 1823 return js.call('$op#', _visit(expr)); |
| 1824 } else if (op.lexeme == '++' || op.lexeme == '--') { | 1824 } else if (op.lexeme == '++' || op.lexeme == '--') { |
| 1825 // We need a null check, so the increment must be expanded out. | 1825 // We need a null check, so the increment must be expanded out. |
| 1826 var mathop = op.lexeme[0]; | 1826 var mathop = op.lexeme[0]; |
| 1827 var vars = {}; | 1827 var vars = {}; |
| 1828 var x = _bindLeftHandSide(vars, expr, context: expr); | 1828 var x = _bindLeftHandSide(vars, expr, context: expr); |
| 1829 var body = js.call('# = # $mathop 1', [_visit(x), notNull(x)]); | 1829 var body = js.call('# = # $mathop 1', [_visit(x), notNull(x)]); |
| 1830 return new JSMetaLet(vars, [body]); | 1830 return new JS.MetaLet(vars, [body]); |
| 1831 } else { | 1831 } else { |
| 1832 return js.call('$op#', notNull(expr)); | 1832 return js.call('$op#', notNull(expr)); |
| 1833 } | 1833 } |
| 1834 } | 1834 } |
| 1835 | 1835 |
| 1836 if (op.lexeme == '++' || op.lexeme == '--') { | 1836 if (op.lexeme == '++' || op.lexeme == '--') { |
| 1837 // Increment or decrement requires expansion. | 1837 // Increment or decrement requires expansion. |
| 1838 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions | 1838 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions |
| 1839 // (for example, x is IndexExpression) we evaluate those once. | 1839 // (for example, x is IndexExpression) we evaluate those once. |
| 1840 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; | 1840 var one = AstBuilder.integerLiteral(1)..staticType = types.intType; |
| 1841 return _emitOpAssign(expr, one, op.lexeme[0], node.staticElement, | 1841 return _emitOpAssign(expr, one, op.lexeme[0], node.staticElement, |
| 1842 context: expr); | 1842 context: expr); |
| 1843 } | 1843 } |
| 1844 | 1844 |
| 1845 return _emitSend(expr, op.lexeme[0], []); | 1845 return _emitSend(expr, op.lexeme[0], []); |
| 1846 } | 1846 } |
| 1847 | 1847 |
| 1848 // Cascades can contain [IndexExpression], [MethodInvocation] and | 1848 // Cascades can contain [IndexExpression], [MethodInvocation] and |
| 1849 // [PropertyAccess]. The code generation for those is handled in their | 1849 // [PropertyAccess]. The code generation for those is handled in their |
| 1850 // respective visit methods. | 1850 // respective visit methods. |
| 1851 @override | 1851 @override |
| 1852 JS.Node visitCascadeExpression(CascadeExpression node) { | 1852 JS.Node visitCascadeExpression(CascadeExpression node) { |
| 1853 var savedCascadeTemp = _cascadeTarget; | 1853 var savedCascadeTemp = _cascadeTarget; |
| 1854 | 1854 |
| 1855 var vars = {}; | 1855 var vars = {}; |
| 1856 _cascadeTarget = _bindValue(vars, '_', node.target, context: node); | 1856 _cascadeTarget = _bindValue(vars, '_', node.target, context: node); |
| 1857 var sections = _visitList(node.cascadeSections); | 1857 var sections = _visitList(node.cascadeSections); |
| 1858 sections.add(_visit(_cascadeTarget)); | 1858 sections.add(_visit(_cascadeTarget)); |
| 1859 var result = new JSMetaLet(vars, sections, statelessResult: true); | 1859 var result = new JS.MetaLet(vars, sections, statelessResult: true); |
| 1860 _cascadeTarget = savedCascadeTemp; | 1860 _cascadeTarget = savedCascadeTemp; |
| 1861 return result; | 1861 return result; |
| 1862 } | 1862 } |
| 1863 | 1863 |
| 1864 @override | 1864 @override |
| 1865 visitParenthesizedExpression(ParenthesizedExpression node) => | 1865 visitParenthesizedExpression(ParenthesizedExpression node) => |
| 1866 // The printer handles precedence so we don't need to. | 1866 // The printer handles precedence so we don't need to. |
| 1867 _visit(node.expression); | 1867 _visit(node.expression); |
| 1868 | 1868 |
| 1869 @override | 1869 @override |
| (...skipping 483 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2353 /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed | 2353 /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed |
| 2354 /// for this transformation to happen, otherwise binary minus is assumed. | 2354 /// for this transformation to happen, otherwise binary minus is assumed. |
| 2355 /// | 2355 /// |
| 2356 /// Equality is a bit special, it is generated via the Dart `equals` runtime | 2356 /// Equality is a bit special, it is generated via the Dart `equals` runtime |
| 2357 /// helper, that checks for null. The user defined method is called '=='. | 2357 /// helper, that checks for null. The user defined method is called '=='. |
| 2358 /// | 2358 /// |
| 2359 JS.Expression _emitMemberName(String name, | 2359 JS.Expression _emitMemberName(String name, |
| 2360 {DartType type, bool unary: false, bool isStatic: false}) { | 2360 {DartType type, bool unary: false, bool isStatic: false}) { |
| 2361 if (name.startsWith('_')) { | 2361 if (name.startsWith('_')) { |
| 2362 return _privateNames.putIfAbsent( | 2362 return _privateNames.putIfAbsent( |
| 2363 name, () => _initSymbol(new JSTemporary(name))); | 2363 name, () => _initSymbol(new JS.TemporaryId(name))); |
| 2364 } | 2364 } |
| 2365 | 2365 |
| 2366 // Check for extension method: | 2366 // Check for extension method: |
| 2367 var extLibrary = _findExtensionLibrary(name, type); | 2367 var extLibrary = _findExtensionLibrary(name, type); |
| 2368 | 2368 |
| 2369 if (name == '[]') { | 2369 if (name == '[]') { |
| 2370 name = 'get'; | 2370 name = 'get'; |
| 2371 } else if (name == '[]=') { | 2371 } else if (name == '[]=') { |
| 2372 name = 'set'; | 2372 name = 'set'; |
| 2373 } else if (name == '-' && unary) { | 2373 } else if (name == '-' && unary) { |
| 2374 name = 'unary-'; | 2374 name = 'unary-'; |
| 2375 } | 2375 } |
| 2376 | 2376 |
| 2377 if (isStatic && invalidJSStaticMethodName(name)) { | 2377 if (isStatic && JS.invalidStaticMethodName(name)) { |
| 2378 // Choose an string name. Use an invalid identifier so it won't conflict | 2378 // Choose an string name. Use an invalid identifier so it won't conflict |
| 2379 // with any valid member names. | 2379 // with any valid member names. |
| 2380 // TODO(jmesserly): this works around the problem, but I'm pretty sure we | 2380 // TODO(jmesserly): this works around the problem, but I'm pretty sure we |
| 2381 // don't need it, as static methods seemed to work. The only concrete | 2381 // don't need it, as static methods seemed to work. The only concrete |
| 2382 // issue we saw was in the defineNamedConstructor helper function. | 2382 // issue we saw was in the defineNamedConstructor helper function. |
| 2383 name = '$name*'; | 2383 name = '$name*'; |
| 2384 } | 2384 } |
| 2385 | 2385 |
| 2386 if (extLibrary != null) { | 2386 if (extLibrary != null) { |
| 2387 return _extensionMethodName(name, extLibrary); | 2387 return _extensionMethodName(name, extLibrary); |
| (...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2511 // TODO(jmesserly): validate the library. See issue #135. | 2511 // TODO(jmesserly): validate the library. See issue #135. |
| 2512 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; | 2512 bool _isJsNameAnnotation(DartObjectImpl value) => value.type.name == 'JsName'; |
| 2513 | 2513 |
| 2514 bool _isJsPeerInterface(DartObjectImpl value) => | 2514 bool _isJsPeerInterface(DartObjectImpl value) => |
| 2515 value.type.name == 'JsPeerInterface'; | 2515 value.type.name == 'JsPeerInterface'; |
| 2516 | 2516 |
| 2517 // TODO(jacobr): we would like to do something like the following | 2517 // TODO(jacobr): we would like to do something like the following |
| 2518 // but we don't have summary support yet. | 2518 // but we don't have summary support yet. |
| 2519 // bool _supportJsExtensionMethod(AnnotatedNode node) => | 2519 // bool _supportJsExtensionMethod(AnnotatedNode node) => |
| 2520 // _getAnnotation(node, "SupportJsExtensionMethod") != null; | 2520 // _getAnnotation(node, "SupportJsExtensionMethod") != null; |
| OLD | NEW |