Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 0ea5350

Browse files
committed
feat($parse): provide a mechanism to access the locals object
Extends the built-in identifiers definitions by adding `$local`. This is a non-assignable reference to the locals object. Closes: #13247 Closes: #13454
1 parent e9aba90 commit 0ea5350

File tree

3 files changed

+59
-3
lines changed

3 files changed

+59
-3
lines changed

docs/content/guide/expression.ngdoc

+3
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ This restriction is intentional. It prevents accidental access to the global sta
115115
Instead use services like `$window` and `$location` in functions called from expressions. Such services
116116
provide mockable access to globals.
117117

118+
It is possible to access the context object using the identifier `this` and the locals object using the
119+
identifier `$locals`.
120+
118121
<example module="expressionExample">
119122
<file name="index.html">
120123
<div class="example2" ng-controller="ExampleController">

src/ng/parse.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ AST.ArrayExpression = 'ArrayExpression';
325325
AST.Property = 'Property';
326326
AST.ObjectExpression = 'ObjectExpression';
327327
AST.ThisExpression = 'ThisExpression';
328+
AST.LocalsExpression = 'LocalsExpression';
328329

329330
// Internal use only
330331
AST.NGValueParameter = 'NGValueParameter';
@@ -625,7 +626,8 @@ AST.prototype = {
625626
'false': { type: AST.Literal, value: false },
626627
'null': { type: AST.Literal, value: null },
627628
'undefined': {type: AST.Literal, value: undefined },
628-
'this': {type: AST.ThisExpression }
629+
'this': {type: AST.ThisExpression },
630+
'$locals': {type: AST.LocalsExpression }
629631
}
630632
};
631633

@@ -745,6 +747,10 @@ function findConstantAndWatchExpressions(ast, $filter) {
745747
ast.constant = false;
746748
ast.toWatch = [];
747749
break;
750+
case AST.LocalsExpression:
751+
ast.constant = false;
752+
ast.toWatch = [];
753+
break;
748754
}
749755
}
750756

@@ -1114,6 +1120,10 @@ ASTCompiler.prototype = {
11141120
this.assign(intoId, 's');
11151121
recursionFn('s');
11161122
break;
1123+
case AST.LocalsExpression:
1124+
this.assign(intoId, 'l');
1125+
recursionFn('l');
1126+
break;
11171127
case AST.NGValueParameter:
11181128
this.assign(intoId, 'v');
11191129
recursionFn('v');
@@ -1441,6 +1451,10 @@ ASTInterpreter.prototype = {
14411451
return function(scope) {
14421452
return context ? {value: scope} : scope;
14431453
};
1454+
case AST.LocalsExpression:
1455+
return function(scope, locals) {
1456+
return context ? {value: locals} : locals;
1457+
};
14441458
case AST.NGValueParameter:
14451459
return function(scope, locals, assign, inputs) {
14461460
return context ? {value: assign} : assign;

test/ng/parseSpec.js

+41-2
Original file line numberDiff line numberDiff line change
@@ -550,8 +550,23 @@ describe('parser', function() {
550550
});
551551

552552

553-
it('should not confuse `this`, `undefined`, `true`, `false`, `null` when used as identfiers', function() {
554-
forEach(['this', 'undefined', 'true', 'false', 'null'], function(identifier) {
553+
it('should understand the `$locals` expression', function() {
554+
expect(createAst('$locals')).toEqual(
555+
{
556+
type: 'Program',
557+
body: [
558+
{
559+
type: 'ExpressionStatement',
560+
expression: { type: 'LocalsExpression' }
561+
}
562+
]
563+
}
564+
);
565+
});
566+
567+
568+
it('should not confuse `this`, `$locals`, `undefined`, `true`, `false`, `null` when used as identfiers', function() {
569+
forEach(['this', '$locals', 'undefined', 'true', 'false', 'null'], function(identifier) {
555570
expect(createAst('foo.' + identifier)).toEqual(
556571
{
557572
type: 'Program',
@@ -3589,6 +3604,30 @@ describe('parser', function() {
35893604
$rootScope.null = {a: 42};
35903605
expect($rootScope.$eval('this.null.a')).toBe(42);
35913606
}));
3607+
3608+
it('should allow accessing $locals', inject(function($rootScope) {
3609+
$rootScope.foo = 'foo';
3610+
$rootScope.bar = 'bar';
3611+
$rootScope.$locals = 'foo';
3612+
var locals = {foo: 42};
3613+
expect($rootScope.$eval('$locals')).toBeUndefined();
3614+
expect($rootScope.$eval('$locals.foo')).toBeUndefined();
3615+
expect($rootScope.$eval('this.$locals')).toBe('foo');
3616+
expect(function() {
3617+
$rootScope.$eval('$locals = {}');
3618+
}).toThrow();
3619+
expect(function() {
3620+
$rootScope.$eval('$locals.bar = 23');
3621+
}).toThrow();
3622+
expect($rootScope.$eval('$locals', locals)).toBe(locals);
3623+
expect($rootScope.$eval('$locals.foo', locals)).toBe(42);
3624+
expect($rootScope.$eval('this.$locals', locals)).toBe('foo');
3625+
expect(function() {
3626+
$rootScope.$eval('$locals = {}', locals);
3627+
}).toThrow();
3628+
expect($rootScope.$eval('$locals.bar = 23', locals)).toEqual(23);
3629+
expect(locals.bar).toBe(23);
3630+
}));
35923631
});
35933632
});
35943633
});

0 commit comments

Comments
 (0)