diff --git a/bin/parser_generator_for_spec.dart b/bin/parser_generator_for_spec.dart index e0dbe26e8..a4a881f2d 100644 --- a/bin/parser_generator_for_spec.dart +++ b/bin/parser_generator_for_spec.dart @@ -101,6 +101,9 @@ main(arguments) { 'items[1].name', 'list[3] = 2', 'map["square"] = 6', + 'map.isEmpty', + 'map.isNotEmpty', + 'map.length', 'method', 'method()', 'notAFn()', diff --git a/lib/change_detection/dirty_checking_change_detector.dart b/lib/change_detection/dirty_checking_change_detector.dart index f276ff7e4..3f4e640c4 100644 --- a/lib/change_detection/dirty_checking_change_detector.dart +++ b/lib/change_detection/dirty_checking_change_detector.dart @@ -3,6 +3,7 @@ library dirty_checking_change_detector; import 'dart:mirrors'; import 'dart:collection'; import 'package:angular/change_detection/change_detection.dart'; +import 'package:angular/core/parser/utils.dart' show isMapProperty; typedef FieldGetter(object); @@ -424,7 +425,7 @@ class DirtyCheckingRecord implements Record, WatchRecord { return; } - if (obj is Map) { + if (obj is Map && !isMapProperty(field)) { _mode = _MODE_MAP_FIELD_; _instanceMirror = null; } else if (_getter != null) { diff --git a/lib/core/parser/eval_access.dart b/lib/core/parser/eval_access.dart index 87bf09cd1..d76d09fc0 100644 --- a/lib/core/parser/eval_access.dart +++ b/lib/core/parser/eval_access.dart @@ -80,7 +80,7 @@ abstract class AccessReflective { if (holder == null) { _cachedKind = CACHED_VALUE; return _cachedValue = null; - } else if (holder is Map) { + } else if (holder is Map && !isMapProperty(name)) { _cachedKind = CACHED_MAP; _cachedValue = null; return holder[name]; @@ -163,7 +163,7 @@ abstract class AccessFast { _eval(holder) { if (holder == null) return null; - return (holder is Map) ? holder[name] : getter(holder); + return (holder is Map && !isMapProperty(name)) ? holder[name] : getter(holder); } _assign(scope, holder, value) { diff --git a/lib/core/parser/utils.dart b/lib/core/parser/utils.dart index 7943e7d4f..7374e4e48 100644 --- a/lib/core/parser/utils.dart +++ b/lib/core/parser/utils.dart @@ -101,3 +101,11 @@ setKeyed(object, key, value) { /// Returns a new symbol with the given name if the name is a legal /// symbol name. Otherwise, returns null. Symbol newSymbol(String name) => isReservedWord(name) ? null : new Symbol(name); + +final Set _MAP_PROPERTIES = new Set.from([ + "hashCode", + "isEmpty", + "isNotEmpty", + "length", + "runtimeType"]); +bool isMapProperty(String key) => _MAP_PROPERTIES.contains(key); diff --git a/lib/tools/parser_generator/dart_code_gen.dart b/lib/tools/parser_generator/dart_code_gen.dart index 879a55df5..a6b9e13b2 100644 --- a/lib/tools/parser_generator/dart_code_gen.dart +++ b/lib/tools/parser_generator/dart_code_gen.dart @@ -2,6 +2,7 @@ library dart_code_gen; import 'package:angular/utils.dart' show isReservedWord; import 'package:angular/core/parser/syntax.dart'; +import 'package:angular/core/parser/utils.dart'; escape(String s) => s.replaceAllMapped(new RegExp(r'(\"|\$|\n)'), (m) { var char = m[1]; @@ -11,7 +12,7 @@ escape(String s) => s.replaceAllMapped(new RegExp(r'(\"|\$|\n)'), (m) { class DartCodeGen { final HelperMap getters = new HelperMap('_', - getterTemplate, getterTemplateForReserved); + getterTemplate, getterTemplateForReserved, getterTemplateWithoutMapTest); final HelperMap holders = new HelperMap('_ensure\$', holderTemplate, holderTemplateForReserved); final HelperMap setters = new HelperMap('_set\$', @@ -226,15 +227,16 @@ class HelperMap { final String prefix; final Function template; final Function templateForReserved; + final Function templateWithoutMapTest; - HelperMap(this.prefix, this.template, this.templateForReserved); + HelperMap(this.prefix, this.template, this.templateForReserved, [this.templateWithoutMapTest]); String lookup(String key) { String name = _computeName(key); if (helpers.containsKey(key)) return name; - helpers[key] = isReservedWord(key) - ? templateForReserved(name, key) - : template(name, key); + helpers[key] = isReservedWord(key) ? templateForReserved(name, key) + : isMapProperty(key) && templateWithoutMapTest != null + ? templateWithoutMapTest(name, key) : template(name, key); return name; } @@ -264,6 +266,9 @@ $name(o) { } """; +String getterTemplateWithoutMapTest(String name, String key) => """ +$name(o) => o == null ? null : o.$key; +"""; // ------------------------------------------------------------------ // Templates for generated holders (getters for assignment). diff --git a/test/core/parser/parser_spec.dart b/test/core/parser/parser_spec.dart index 7d7187f81..d80657d0f 100644 --- a/test/core/parser/parser_spec.dart +++ b/test/core/parser/parser_spec.dart @@ -743,6 +743,14 @@ main() { }); + it('should evaluate map item and field access', () { + context['map'] = { 'a': 1.1, 'b': 'B' }; + expect(eval("map.isEmpty")).toEqual(false); + expect(eval("map.isNotEmpty")).toEqual(true); + expect(eval("map.length")).toEqual(2); + }); + + it('should evaluate JSON', () { expect(eval("[{}]")).toEqual([{}]); expect(eval("[{a:[]}, {b:1}]")).toEqual([{"a":[]},{"b":1}]); diff --git a/test/core/scope_spec.dart b/test/core/scope_spec.dart index cc700f9d9..6dfaa1028 100644 --- a/test/core/scope_spec.dart +++ b/test/core/scope_spec.dart @@ -48,6 +48,24 @@ void main() { expect(logger).toEqual(['AB', '123', 'XYZ']); }); + it('should watch map property of a primitive type', (Logger logger, Map context, RootScope rootScope) { + context['a'] = {'b': 'AB', 'length' : 99}; + rootScope.watch('a.isEmpty', (value, previous) => logger(value)); + rootScope.watch('a.isNotEmpty', (value, previous) => logger(value)); + rootScope.watch('a.length', (value, previous) => logger(value)); + rootScope.watch('a["length"]', (value, previous) => logger(value)); + rootScope.digest(); + expect(logger).toEqual([false, true, 2, 99]); + context['a']['c'] = '123'; + logger.clear(); + rootScope.digest(); + expect(logger).toEqual([3]); + context['a'] = {}; + logger.clear(); + rootScope.digest(); + expect(logger).toEqual([true, false, 0, null]); + }); + it('should watch math operations', (Logger logger, Map context, RootScope rootScope) { context['a'] = 1; context['b'] = 2;