diff --git a/bin/parser_generator_for_spec.dart b/bin/parser_generator_for_spec.dart index dfa436cb7..b1e6396c8 100644 --- a/bin/parser_generator_for_spec.dart +++ b/bin/parser_generator_for_spec.dart @@ -227,6 +227,204 @@ main(arguments) { "_privateField", "'World'|hello", "1;'World'|hello", - "'World'|hello;1" + "'World'|hello;1", + + "assert", + "break", + "case", + "catch", + "class", + "const", + "continue", + "default", + "do", + "else", + "enum", + "extends", + "final", + "finally", + "for", + "if", + "in", + "is", + "new", + "rethrow", + "return", + "super", + "switch", + "this", + "throw", + "try", + "var", + "void", + "while", + "with", + + "assert = 42", + "break = 42", + "case = 42", + "catch = 42", + "class = 42", + "const = 42", + "continue = 42", + "default = 42", + "do = 42", + "else = 42", + "enum = 42", + "extends = 42", + "false = 42", + "final = 42", + "finally = 42", + "for = 42", + "if = 42", + "in = 42", + "is = 42", + "new = 42", + "null = 42", + "rethrow = 42", + "return = 42", + "super = 42", + "switch = 42", + "this = 42", + "throw = 42", + "true = 42", + "try = 42", + "var = 42", + "void = 42", + "while = 42", + "with = 42", + + "assert()", + "break()", + "case()", + "catch()", + "class()", + "const()", + "continue()", + "default()", + "do()", + "else()", + "enum()", + "extends()", + "final()", + "finally()", + "for()", + "if()", + "in()", + "is()", + "new()", + "rethrow()", + "return()", + "super()", + "switch()", + "this()", + "throw()", + "try()", + "var()", + "void()", + "while()", + "with()", + + "o.assert", + "o.break", + "o.case", + "o.catch", + "o.class", + "o.const", + "o.continue", + "o.default", + "o.do", + "o.else", + "o.enum", + "o.extends", + "o.false", + "o.final", + "o.finally", + "o.for", + "o.if", + "o.in", + "o.is", + "o.new", + "o.null", + "o.rethrow", + "o.return", + "o.super", + "o.switch", + "o.this", + "o.throw", + "o.true", + "o.try", + "o.var", + "o.void", + "o.while", + "o.with", + + "o.assert = 42", + "o.break = 42", + "o.case = 42", + "o.catch = 42", + "o.class = 42", + "o.const = 42", + "o.continue = 42", + "o.default = 42", + "o.do = 42", + "o.else = 42", + "o.enum = 42", + "o.extends = 42", + "o.false = 42", + "o.final = 42", + "o.finally = 42", + "o.for = 42", + "o.if = 42", + "o.in = 42", + "o.is = 42", + "o.new = 42", + "o.null = 42", + "o.rethrow = 42", + "o.return = 42", + "o.super = 42", + "o.switch = 42", + "o.this = 42", + "o.throw = 42", + "o.true = 42", + "o.try = 42", + "o.var = 42", + "o.void = 42", + "o.while = 42", + "o.with = 42", + + "o.assert()", + "o.break()", + "o.case()", + "o.catch()", + "o.class()", + "o.const()", + "o.continue()", + "o.default()", + "o.do()", + "o.else()", + "o.enum()", + "o.extends()", + "o.false()", + "o.final()", + "o.finally()", + "o.for()", + "o.if()", + "o.in()", + "o.is()", + "o.new()", + "o.null()", + "o.rethrow()", + "o.return()", + "o.super()", + "o.switch()", + "o.this()", + "o.throw()", + "o.true()", + "o.try()", + "o.var()", + "o.void()", + "o.while()", + "o.with()", ]); } diff --git a/lib/core/parser/eval_access.dart b/lib/core/parser/eval_access.dart index 1e1f53a68..d8f47fea7 100644 --- a/lib/core/parser/eval_access.dart +++ b/lib/core/parser/eval_access.dart @@ -8,7 +8,7 @@ import 'package:angular/core/module.dart'; class AccessScope extends syntax.AccessScope with AccessReflective { final Symbol symbol; - AccessScope(String name) : super(name), symbol = new Symbol(name); + AccessScope(String name) : super(name), symbol = newSymbol(name); eval(scope, [FilterMap filters]) => _eval(scope); assign(scope, value) => _assign(scope, scope, value); } @@ -24,7 +24,7 @@ class AccessScopeFast extends syntax.AccessScope with AccessFast { class AccessMember extends syntax.AccessMember with AccessReflective { final Symbol symbol; AccessMember(object, String name) - : super(object, name), symbol = new Symbol(name); + : super(object, name), symbol = newSymbol(name); eval(scope, [FilterMap filters]) => _eval(object.eval(scope, filters)); assign(scope, value) => _assign(scope, object.eval(scope), value); _assignToNonExisting(scope, value) => object.assign(scope, { name: value }); @@ -84,6 +84,9 @@ abstract class AccessReflective { _cachedKind = CACHED_MAP; _cachedValue = null; return holder[name]; + } else if (symbol == null) { + _cachedHolder = UNINITIALIZED; + return null; } InstanceMirror mirror = reflect(holder); try { @@ -119,7 +122,7 @@ abstract class AccessReflective { holder[name] = value; } else if (holder == null) { _assignToNonExisting(scope, value); - } else { + } else if (symbol != null) { reflect(holder).setField(symbol, value); } return value; diff --git a/lib/core/parser/eval_calls.dart b/lib/core/parser/eval_calls.dart index 4c1bab706..c7c152a6a 100644 --- a/lib/core/parser/eval_calls.dart +++ b/lib/core/parser/eval_calls.dart @@ -9,7 +9,7 @@ class CallScope extends syntax.CallScope with CallReflective { final Symbol symbol; CallScope(name, arguments) : super(name, arguments) - , symbol = new Symbol(name); + , symbol = newSymbol(name); eval(scope, [FilterMap filters]) => _eval(scope, scope); } @@ -17,7 +17,7 @@ class CallMember extends syntax.CallMember with CallReflective { final Symbol symbol; CallMember(object, name, arguments) : super(object, name, arguments) - , symbol = new Symbol(name); + , symbol = newSymbol(name); eval(scope, [FilterMap filters]) => _eval(scope, object.eval(scope, filters)); } @@ -94,6 +94,9 @@ abstract class CallReflective { _cachedKind = CACHED_MAP; _cachedValue = null; return relaxFnApply(ensureFunctionFromMap(holder, name), arguments); + } else if (symbol == null) { + _cachedHolder = UNINITIALIZED; + throw new EvalError("Undefined function $name"); } else { InstanceMirror mirror = reflect(holder); _cachedKind = CACHED_FUNCTION; diff --git a/lib/core/parser/utils.dart b/lib/core/parser/utils.dart index 45535acff..d79c9c96f 100644 --- a/lib/core/parser/utils.dart +++ b/lib/core/parser/utils.dart @@ -2,6 +2,7 @@ library angular.core.parser.utils; import 'package:angular/core/parser/syntax.dart' show Expression; import 'package:angular/core/module.dart'; +import 'package:angular/utils.dart' show isReservedWord; export 'package:angular/utils.dart' show relaxFnApply, relaxFnArgs, toBool; /// Marker for an uninitialized value. @@ -96,3 +97,9 @@ setKeyed(object, key, value) { } return value; } + +/// Returns a new symbol with the given name if the name is a legal +/// symbol name. Otherwise, returns null. +Symbol newSymbol(String name) { + return isReservedWord(name) ? null : new Symbol(name); +} diff --git a/lib/core_dom/module.dart b/lib/core_dom/module.dart index 6fe343371..d193675d3 100644 --- a/lib/core_dom/module.dart +++ b/lib/core_dom/module.dart @@ -10,7 +10,6 @@ import 'package:perf_api/perf_api.dart'; import 'package:angular/core/module.dart'; import 'package:angular/core/parser/parser.dart'; -import 'package:angular/utils.dart'; part 'block.dart'; part 'block_factory.dart'; diff --git a/lib/directive/ng_pluralize.dart b/lib/directive/ng_pluralize.dart index 6be4a6458..7b2903a7d 100644 --- a/lib/directive/ng_pluralize.dart +++ b/lib/directive/ng_pluralize.dart @@ -96,7 +96,16 @@ class NgPluralizeDirective { int offset; var discreteRules = {}; var categoryRules = {}; + static final RegExp IS_WHEN = new RegExp(r'^when-(minus-)?.'); + static const Map SYMBOLS = const { + 'zero' : #zero, + 'one' : #one, + 'two' : #two, + 'few' : #few, + 'many' : #many, + 'other' : #other, + }; NgPluralizeDirective(this.scope, this.element, this.interpolate, NodeAttrs attributes, this.parser) { @@ -116,10 +125,11 @@ class NgPluralizeDirective { } whens.forEach((k, v) { - if (['zero', 'one', 'two', 'few', 'many', 'other'].contains(k)) { - this.categoryRules[new Symbol(k.toString())] = v; + Symbol symbol = SYMBOLS[k]; + if (symbol != null) { + this.categoryRules[symbol] = v; } else { - this.discreteRules[k.toString()] = v; + this.discreteRules[k] = v; } }); } diff --git a/lib/tools/parser_generator/dart_code_gen.dart b/lib/tools/parser_generator/dart_code_gen.dart index eda4b6ca5..5bac0003c 100644 --- a/lib/tools/parser_generator/dart_code_gen.dart +++ b/lib/tools/parser_generator/dart_code_gen.dart @@ -1,6 +1,6 @@ library dart_code_gen; -import 'package:angular/tools/reserved_dart_keywords.dart'; +import 'package:angular/utils.dart' show isReservedWord; import 'package:angular/core/parser/syntax.dart'; escape(String s) => s.replaceAllMapped(new RegExp(r'(\"|\$|\n)'), (m) { @@ -229,7 +229,7 @@ class HelperMap { String lookup(String key) { String name = _computeName(key); if (helpers.containsKey(key)) return name; - helpers[key] = isReserved(key) + helpers[key] = isReservedWord(key) ? templateForReserved(name, key) : template(name, key); return name; diff --git a/lib/tools/parser_getter_setter/generator.dart b/lib/tools/parser_getter_setter/generator.dart index e1b4b7de5..b33244e10 100644 --- a/lib/tools/parser_getter_setter/generator.dart +++ b/lib/tools/parser_getter_setter/generator.dart @@ -1,5 +1,5 @@ import 'package:angular/core/parser/parser.dart'; -import 'package:angular/tools/reserved_dart_keywords.dart'; +import 'package:angular/utils.dart' show isReservedWord; import 'dart:math'; class DartGetterSetterGen extends ParserBackend { @@ -9,12 +9,12 @@ class DartGetterSetterGen extends ParserBackend { bool isAssignable(expression) => true; registerAccess(String name) { - if (isReserved(name)) return; + if (isReservedWord(name)) return; properties.add(name); } registerCall(String name, List arguments) { - if (isReserved(name)) return; + if (isReservedWord(name)) return; Set arities = calls.putIfAbsent(name, () => new Set()); arities.add(arguments.length); } diff --git a/lib/tools/reserved_dart_keywords.dart b/lib/tools/reserved_dart_keywords.dart deleted file mode 100644 index 5d009f5fc..000000000 --- a/lib/tools/reserved_dart_keywords.dart +++ /dev/null @@ -1,10 +0,0 @@ -library reserved_dart_keywords; - -// From https://www.dartlang.org/docs/spec/latest/dart-language-specification.html#h.huusvrzea3q -List RESERVED_DART_KEYWORDS = [ - "assert", "break", "case", "catch", "class", "const", "continue", - "default", "do", "else", "enum", "extends", "false", "final", - "finally", "for", "if", "in", "is", "new", "null", "rethrow", - "return", "super", "switch", "this", "throw", "true", "try", - "var", "void", "while", "with"]; -isReserved(String key) => RESERVED_DART_KEYWORDS.contains(key); diff --git a/lib/tools/source_metadata_extractor.dart b/lib/tools/source_metadata_extractor.dart index c125f244d..ebe36844f 100644 --- a/lib/tools/source_metadata_extractor.dart +++ b/lib/tools/source_metadata_extractor.dart @@ -4,7 +4,6 @@ import 'package:analyzer/src/generated/ast.dart'; import 'package:angular/tools/source_crawler.dart'; import 'package:angular/tools/common.dart'; -import 'package:angular/utils.dart'; const String _COMPONENT = '-component'; const String _DIRECTIVE = '-directive'; diff --git a/lib/utils.dart b/lib/utils.dart index 6c35b862e..1a476b737 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -78,3 +78,43 @@ relaxFnArgs(Function fn) { } capitalize(String s) => s.substring(0, 1).toUpperCase() + s.substring(1); + + +/// Returns whether or not the given identifier is a reserved word in Dart. +bool isReservedWord(String identifier) => RESERVED_WORDS.contains(identifier); + +final Set RESERVED_WORDS = new Set.from(const [ + "assert", + "break", + "case", + "catch", + "class", + "const", + "continue", + "default", + "do", + "else", + "enum", + "extends", + "false", + "final", + "finally", + "for", + "if", + "in", + "is", + "new", + "null", + "rethrow", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "var", + "void", + "while", + "with" +]); diff --git a/perf/dom/compile_perf.dart b/perf/dom/compile_perf.dart index 0698b1e68..3fcf72831 100644 --- a/perf/dom/compile_perf.dart +++ b/perf/dom/compile_perf.dart @@ -9,11 +9,11 @@ main() => describe('compiler', () { items.add({"text":'text_$i', "done": i & 1 == 1}); } var empty = []; - tb.rootScope.classFor = (item) => 'ng-${item["done"]}'; + tb.rootScope.context['classFor'] = (item) => 'ng-${item["done"]}'; time('create 100 blocks', - () => tb.rootScope.apply(() => tb.rootScope.items = items), - cleanUp: () => tb.rootScope.apply(() => tb.rootScope.items = empty), + () => tb.rootScope.apply(() => tb.rootScope.context['items'] = items), + cleanUp: () => tb.rootScope.apply(() => tb.rootScope.context['items'] = empty), verify: () => expect(tb.rootElement.querySelectorAll('li').length).toEqual(100)); })); }); diff --git a/test/core/parser/parser_spec.dart b/test/core/parser/parser_spec.dart index 437fa66b6..6db4a5b3f 100644 --- a/test/core/parser/parser_spec.dart +++ b/test/core/parser/parser_spec.dart @@ -1,6 +1,7 @@ library parser_spec; import '../../_specs.dart'; +import 'package:angular/utils.dart' show RESERVED_WORDS; // Used to test getter / setter logic. class TestData { @@ -396,6 +397,66 @@ main() { }); }); + describe('reserved words', () { + it('should support reserved words in member get access', () { + for (String reserved in RESERVED_WORDS) { + expect(parser("o.$reserved").eval({ 'o': new Object() })).toEqual(null); + expect(parser("o.$reserved").eval({ 'o': { reserved: reserved }})).toEqual(reserved); + } + }); + + + it('should support reserved words in member set access', () { + for (String reserved in RESERVED_WORDS) { + expect(parser("o.$reserved = 42").eval({ 'o': new Object() })).toEqual(42); + var map = { reserved: 0 }; + expect(parser("o.$reserved = 42").eval({ 'o': map })).toEqual(42); + expect(map[reserved]).toEqual(42); + } + }); + + + it('should support reserved words in member calls', () { + for (String reserved in RESERVED_WORDS) { + expect(() { + parser("o.$reserved()").eval({ 'o': new Object() }); + }).toThrow('Undefined function $reserved'); + expect(parser("o.$reserved()").eval({ 'o': { reserved: () => reserved }})).toEqual(reserved); + } + }); + + + it('should support reserved words in scope get access', () { + for (String reserved in RESERVED_WORDS) { + if ([ "true", "false", "null"].contains(reserved)) continue; + expect(parser("$reserved").eval(new Object())).toEqual(null); + expect(parser("$reserved").eval({ reserved: reserved })).toEqual(reserved); + } + }); + + + it('should support reserved words in scope set access', () { + for (String reserved in RESERVED_WORDS) { + if ([ "true", "false", "null"].contains(reserved)) continue; + expect(parser("$reserved = 42").eval(new Object())).toEqual(42); + var map = { reserved: 0 }; + expect(parser("$reserved = 42").eval(map)).toEqual(42); + expect(map[reserved]).toEqual(42); + } + }); + + + it('should support reserved words in scope calls', () { + for (String reserved in RESERVED_WORDS) { + if ([ "true", "false", "null"].contains(reserved)) continue; + expect(() { + parser("$reserved()").eval(new Object()); + }).toThrow('Undefined function $reserved'); + expect(parser("$reserved()").eval({ reserved: () => reserved })).toEqual(reserved); + } + }); + }); + describe('test cases imported from AngularJS', () { //// ==== IMPORTED ITs it('should parse expressions', () {