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

Commit 88c2193

Browse files
rodyhaddadIgorMinar
authored andcommitted
refactor($interpolate): split .parts into .expressions and .separators
BREAKING CHANGE: the function returned by $interpolate no longer has a `.parts` array set on it. It has been replaced by two arrays: * `.expressions`, an array of the expressions in the interpolated text. The expressions are parsed with $parse, with an extra layer converting them to strings when computed * `.separators`, an array of strings representing the separations between interpolations in the text. This array is **always** 1 item longer than the `.expressions` array for easy merging with it
1 parent 21f9316 commit 88c2193

File tree

3 files changed

+118
-80
lines changed

3 files changed

+118
-80
lines changed

src/ng/interpolate.js

+61-37
Original file line numberDiff line numberDiff line change
@@ -125,32 +125,36 @@ function $InterpolateProvider() {
125125
var startIndex,
126126
endIndex,
127127
index = 0,
128-
parts = [],
129-
length = text.length,
128+
separators = [],
129+
expressions = [],
130+
textLength = text.length,
130131
hasInterpolation = false,
132+
hasText = false,
131133
fn,
132134
exp,
133135
concat = [];
134136

135-
while(index < length) {
137+
while(index < textLength) {
136138
if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
137139
((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
138-
(index != startIndex) && parts.push(text.substring(index, startIndex));
139-
parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
140+
if (index !== startIndex) hasText = true;
141+
separators.push(text.substring(index, startIndex));
142+
expressions.push(fn = interpolateParse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
140143
fn.exp = exp;
141144
index = endIndex + endSymbolLength;
142145
hasInterpolation = true;
143146
} else {
144-
// we did not find anything, so we have to add the remainder to the parts array
145-
(index != length) && parts.push(text.substring(index));
146-
index = length;
147+
// we did not find an interpolation, so we have to add the remainder to the separators array
148+
if (index !== textLength) {
149+
hasText = true;
150+
separators.push(text.substring(index));
151+
}
152+
break;
147153
}
148154
}
149155

150-
if (!(length = parts.length)) {
151-
// we added, nothing, must have been an empty string.
152-
parts.push('');
153-
length = 1;
156+
if (separators.length === expressions.length) {
157+
separators.push('');
154158
}
155159

156160
// Concatenating expressions makes it hard to reason about whether some combination of
@@ -159,44 +163,64 @@ function $InterpolateProvider() {
159163
// that's used is assigned or constructed by some JS code somewhere that is more testable or
160164
// make it obvious that you bound the value to some user controlled value. This helps reduce
161165
// the load when auditing for XSS issues.
162-
if (trustedContext && parts.length > 1) {
166+
if (trustedContext && hasInterpolation && (hasText || expressions.length > 1)) {
163167
throw $interpolateMinErr('noconcat',
164168
"Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
165169
"interpolations that concatenate multiple expressions when a trusted value is " +
166170
"required. See http://docs.angularjs.org/api/ng.$sce", text);
167171
}
168172

169-
if (!mustHaveExpression || hasInterpolation) {
170-
concat.length = length;
173+
if (!mustHaveExpression || hasInterpolation) {
174+
concat.length = separators.length + expressions.length;
175+
var computeFn = function (values, context) {
176+
for(var i = 0, ii = expressions.length; i < ii; i++) {
177+
concat[2*i] = separators[i];
178+
concat[(2*i)+1] = values ? values[i] : expressions[i](context);
179+
}
180+
concat[2*ii] = separators[ii];
181+
return concat.join('');
182+
};
183+
171184
fn = function(context) {
185+
return computeFn(null, context);
186+
};
187+
fn.exp = text;
188+
189+
// hack in order to preserve existing api
190+
fn.$$invoke = function (listener) {
191+
return function (values, oldValues, scope) {
192+
var current = computeFn(values, scope);
193+
listener(current, this.$$lastInter == null ? current : this.$$lastInter, scope);
194+
this.$$lastInter = current;
195+
};
196+
};
197+
fn.separators = separators;
198+
fn.expressions = expressions;
199+
return fn;
200+
}
201+
202+
function interpolateParse(expression) {
203+
var exp = $parse(expression);
204+
return function (scope) {
172205
try {
173-
for(var i = 0, ii = length, part; i<ii; i++) {
174-
if (typeof (part = parts[i]) == 'function') {
175-
part = part(context);
176-
if (trustedContext) {
177-
part = $sce.getTrusted(trustedContext, part);
178-
} else {
179-
part = $sce.valueOf(part);
180-
}
181-
if (part === null || isUndefined(part)) {
182-
part = '';
183-
} else if (typeof part != 'string') {
184-
part = toJson(part);
185-
}
186-
}
187-
concat[i] = part;
206+
var value = exp(scope);
207+
if (trustedContext) {
208+
value = $sce.getTrusted(trustedContext, value);
209+
} else {
210+
value = $sce.valueOf(value);
188211
}
189-
return concat.join('');
190-
}
191-
catch(err) {
212+
if (value === null || isUndefined(value)) {
213+
value = '';
214+
} else if (typeof value != 'string') {
215+
value = toJson(value);
216+
}
217+
return value;
218+
} catch(err) {
192219
var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
193-
err.toString());
220+
err.toString());
194221
$exceptionHandler(newErr);
195222
}
196223
};
197-
fn.exp = text;
198-
fn.parts = parts;
199-
return fn;
200224
}
201225
}
202226

src/ngScenario/Scenario.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
314314
}
315315
for(var fns, j=0, jj=binding.length; j<jj; j++) {
316316
fns = binding[j];
317-
if (fns.parts) {
318-
fns = fns.parts;
317+
if (fns.expressions) {
318+
fns = fns.expressions;
319319
} else {
320320
fns = [fns];
321321
}

test/ng/interpolateSpec.js

+55-41
Original file line numberDiff line numberDiff line change
@@ -123,77 +123,84 @@ describe('$interpolate', function() {
123123
}));
124124

125125
it('should not get confused with same markers', inject(function($interpolate) {
126-
expect($interpolate('---').parts).toEqual(['---']);
126+
expect($interpolate('---').separators).toEqual(['---']);
127+
expect($interpolate('---').expressions).toEqual([]);
127128
expect($interpolate('----')()).toEqual('');
128129
expect($interpolate('--1--')()).toEqual('1');
129130
}));
130131
});
131132

132-
133133
describe('parseBindings', function() {
134134
it('should Parse Text With No Bindings', inject(function($interpolate) {
135-
var parts = $interpolate("a").parts;
136-
expect(parts.length).toEqual(1);
137-
expect(parts[0]).toEqual("a");
135+
expect($interpolate("a").separators).toEqual(['a']);
136+
expect($interpolate("a").expressions).toEqual([]);
138137
}));
139138

140139
it('should Parse Empty Text', inject(function($interpolate) {
141-
var parts = $interpolate("").parts;
142-
expect(parts.length).toEqual(1);
143-
expect(parts[0]).toEqual("");
140+
expect($interpolate("").separators).toEqual(['']);
141+
expect($interpolate("").expressions).toEqual([]);
144142
}));
145143

146144
it('should Parse Inner Binding', inject(function($interpolate) {
147-
var parts = $interpolate("a{{b}}C").parts;
148-
expect(parts.length).toEqual(3);
149-
expect(parts[0]).toEqual("a");
150-
expect(parts[1].exp).toEqual("b");
151-
expect(parts[1]({b:123})).toEqual(123);
152-
expect(parts[2]).toEqual("C");
145+
var interpolateFn = $interpolate("a{{b}}C"),
146+
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
147+
expect(separators).toEqual(['a', 'C']);
148+
expect(expressions.length).toEqual(1);
149+
expect(expressions[0].exp).toEqual('b');
150+
expect(expressions[0]({b:123})).toEqual('123');
153151
}));
154152

155153
it('should Parse Ending Binding', inject(function($interpolate) {
156-
var parts = $interpolate("a{{b}}").parts;
157-
expect(parts.length).toEqual(2);
158-
expect(parts[0]).toEqual("a");
159-
expect(parts[1].exp).toEqual("b");
160-
expect(parts[1]({b:123})).toEqual(123);
154+
var interpolateFn = $interpolate("a{{b}}"),
155+
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
156+
expect(separators).toEqual(['a', '']);
157+
expect(expressions.length).toEqual(1);
158+
expect(expressions[0].exp).toEqual('b');
159+
expect(expressions[0]({b:123})).toEqual('123');
161160
}));
162161

163162
it('should Parse Begging Binding', inject(function($interpolate) {
164-
var parts = $interpolate("{{b}}c").parts;
165-
expect(parts.length).toEqual(2);
166-
expect(parts[0].exp).toEqual("b");
167-
expect(parts[1]).toEqual("c");
163+
var interpolateFn = $interpolate("{{b}}c"),
164+
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
165+
expect(separators).toEqual(['', 'c']);
166+
expect(expressions.length).toEqual(1);
167+
expect(expressions[0].exp).toEqual('b');
168+
expect(expressions[0]({b:123})).toEqual('123');
168169
}));
169170

170171
it('should Parse Loan Binding', inject(function($interpolate) {
171-
var parts = $interpolate("{{b}}").parts;
172-
expect(parts.length).toEqual(1);
173-
expect(parts[0].exp).toEqual("b");
172+
var interpolateFn = $interpolate("{{b}}"),
173+
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
174+
expect(separators).toEqual(['', '']);
175+
expect(expressions.length).toEqual(1);
176+
expect(expressions[0].exp).toEqual('b');
177+
expect(expressions[0]({b:123})).toEqual('123');
174178
}));
175179

176180
it('should Parse Two Bindings', inject(function($interpolate) {
177-
var parts = $interpolate("{{b}}{{c}}").parts;
178-
expect(parts.length).toEqual(2);
179-
expect(parts[0].exp).toEqual("b");
180-
expect(parts[1].exp).toEqual("c");
181+
var interpolateFn = $interpolate("{{b}}{{c}}"),
182+
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
183+
expect(separators).toEqual(['', '', '']);
184+
expect(expressions.length).toEqual(2);
185+
expect(expressions[0].exp).toEqual('b');
186+
expect(expressions[1].exp).toEqual('c');
181187
}));
182188

183189
it('should Parse Two Bindings With Text In Middle', inject(function($interpolate) {
184-
var parts = $interpolate("{{b}}x{{c}}").parts;
185-
expect(parts.length).toEqual(3);
186-
expect(parts[0].exp).toEqual("b");
187-
expect(parts[1]).toEqual("x");
188-
expect(parts[2].exp).toEqual("c");
190+
var interpolateFn = $interpolate("{{b}}x{{c}}"),
191+
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
192+
expect(separators).toEqual(['', 'x', '']);
193+
expect(expressions.length).toEqual(2);
194+
expect(expressions[0].exp).toEqual('b');
195+
expect(expressions[1].exp).toEqual('c');
189196
}));
190197

191198
it('should Parse Multiline', inject(function($interpolate) {
192-
var parts = $interpolate('"X\nY{{A\n+B}}C\nD"').parts;
193-
expect(parts.length).toEqual(3);
194-
expect(parts[0]).toEqual('"X\nY');
195-
expect(parts[1].exp).toEqual('A\n+B');
196-
expect(parts[2]).toEqual('C\nD"');
199+
var interpolateFn = $interpolate('"X\nY{{A\n+B}}C\nD"'),
200+
separators = interpolateFn.separators, expressions = interpolateFn.expressions;
201+
expect(separators).toEqual(['"X\nY', 'C\nD"']);
202+
expect(expressions.length).toEqual(1);
203+
expect(expressions[0].exp).toEqual('A\n+B');
197204
}));
198205
});
199206

@@ -207,6 +214,12 @@ describe('$interpolate', function() {
207214
"$interpolate", "noconcat", "Error while interpolating: constant/{{var}}\nStrict " +
208215
"Contextual Escaping disallows interpolations that concatenate multiple expressions " +
209216
"when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce");
217+
expect(function() {
218+
$interpolate('{{var}}/constant', true, isTrustedContext);
219+
}).toThrowMinErr(
220+
"$interpolate", "noconcat", "Error while interpolating: {{var}}/constant\nStrict " +
221+
"Contextual Escaping disallows interpolations that concatenate multiple expressions " +
222+
"when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce");
210223
expect(function() {
211224
$interpolate('{{foo}}{{bar}}', true, isTrustedContext);
212225
}).toThrowMinErr(
@@ -248,7 +261,8 @@ describe('$interpolate', function() {
248261
});
249262

250263
inject(function($interpolate) {
251-
expect($interpolate('---').parts).toEqual(['---']);
264+
expect($interpolate('---').separators).toEqual(['---']);
265+
expect($interpolate('---').expressions).toEqual([]);
252266
expect($interpolate('----')()).toEqual('');
253267
expect($interpolate('--1--')()).toEqual('1');
254268
});

0 commit comments

Comments
 (0)