Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

fix(parser): Allow access to Map properties in expressions #605

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bin/parser_generator_for_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ main(arguments) {
'items[1].name',
'list[3] = 2',
'map["square"] = 6',
'map.isEmpty',
'map.isNotEmpty',
'map.length',
'method',
'method()',
'notAFn()',
Expand Down
3 changes: 2 additions & 1 deletion lib/change_detection/dirty_checking_change_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -424,7 +425,7 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
return;
}

if (obj is Map) {
if (obj is Map && !isMapProperty(field)) {
_mode = _MODE_MAP_FIELD_;
_instanceMirror = null;
} else if (_getter != null) {
Expand Down
4 changes: 2 additions & 2 deletions lib/core/parser/eval_access.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions lib/core/parser/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> _MAP_PROPERTIES = new Set<String>.from([
"hashCode",
"isEmpty",
"isNotEmpty",
"length",
"runtimeType"]);
bool isMapProperty(String key) => _MAP_PROPERTIES.contains(key);
15 changes: 10 additions & 5 deletions lib/tools/parser_generator/dart_code_gen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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\$',
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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).
Expand Down
8 changes: 8 additions & 0 deletions test/core/parser/parser_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,14 @@ main() {
});


it('should evaluate map item and field access', () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this is only half the work. Same kinds of tests also have to work on scope watching, and I think right now, they would fail. Could you create a scope.watch('may.isEmpty') test to make sure it works there as well. Change detection takes a different path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Will try to find previous example of a scope.watch() test as an example (probably in scope_spec.dart).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done for map properties whose values are of a primitive type like map.length, map.isEmpty. Still need to work on map.keys and map.values but that will have to wait until midday tomorrow.

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}]);
Expand Down
18 changes: 18 additions & 0 deletions test/core/scope_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down