Skip to content

Commit 0c8230c

Browse files
committed
Merge branch '4.x'
# Conflicts: # components/bower.json # components/handlebars.js.nuspec # components/package.json # package.json
2 parents d23ccf6 + 91a1b5d commit 0c8230c

18 files changed

+550
-145
lines changed

lib/handlebars/base.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { registerDefaultHelpers } from './helpers';
44
import { registerDefaultDecorators } from './decorators';
55
import logger from './logger';
66

7-
export const VERSION = '4.5.3';
7+
export const VERSION = '4.6.0';
88
export const COMPILER_REVISION = 8;
99
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
1010

lib/handlebars/compiler/javascript-compiler.js

+31-23
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
22
import Exception from '../exception';
33
import { isArray } from '../utils';
44
import CodeGen from './code-gen';
5-
import { dangerousPropertyRegex } from '../helpers/lookup';
65

76
function Literal(value) {
87
this.value = value;
@@ -13,27 +12,8 @@ function JavaScriptCompiler() {}
1312
JavaScriptCompiler.prototype = {
1413
// PUBLIC API: You can override these methods in a subclass to provide
1514
// alternative compiled forms for name lookup and buffering semantics
16-
nameLookup: function(parent, name /* , type*/) {
17-
if (dangerousPropertyRegex.test(name)) {
18-
const isEnumerable = [
19-
this.aliasable('container.propertyIsEnumerable'),
20-
'.call(',
21-
parent,
22-
',',
23-
JSON.stringify(name),
24-
')'
25-
];
26-
return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)'];
27-
}
28-
return _actualLookup();
29-
30-
function _actualLookup() {
31-
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
32-
return [parent, '.', name];
33-
} else {
34-
return [parent, '[', JSON.stringify(name), ']'];
35-
}
36-
}
15+
nameLookup: function(parent, name /*, type */) {
16+
return this.internalNameLookup(parent, name);
3717
},
3818
depthedLookup: function(name) {
3919
return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
@@ -69,6 +49,12 @@ JavaScriptCompiler.prototype = {
6949
return this.quotedString('');
7050
},
7151
// END PUBLIC API
52+
internalNameLookup: function(parent, name) {
53+
this.lookupPropertyFunctionIsUsed = true;
54+
return ['lookupProperty(', parent, ',', JSON.stringify(name), ')'];
55+
},
56+
57+
lookupPropertyFunctionIsUsed: false,
7258

7359
compile: function(environment, options, context, asObject) {
7460
this.environment = environment;
@@ -129,7 +115,11 @@ JavaScriptCompiler.prototype = {
129115
if (!this.decorators.isEmpty()) {
130116
this.useDecorators = true;
131117

132-
this.decorators.prepend('var decorators = container.decorators;\n');
118+
this.decorators.prepend([
119+
'var decorators = container.decorators, ',
120+
this.lookupPropertyFunctionVarDeclaration(),
121+
';\n'
122+
]);
133123
this.decorators.push('return fn;');
134124

135125
if (asObject) {
@@ -246,6 +236,10 @@ JavaScriptCompiler.prototype = {
246236
}
247237
});
248238

239+
if (this.lookupPropertyFunctionIsUsed) {
240+
varDeclarations += ', ' + this.lookupPropertyFunctionVarDeclaration();
241+
}
242+
249243
let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
250244

251245
if (this.useBlockParams || this.useDepths) {
@@ -333,6 +327,17 @@ JavaScriptCompiler.prototype = {
333327
return this.source.merge();
334328
},
335329

330+
lookupPropertyFunctionVarDeclaration: function() {
331+
return `
332+
lookupProperty = container.lookupProperty || function(parent, propertyName) {
333+
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
334+
return parent[propertyName];
335+
}
336+
return undefined
337+
}
338+
`.trim();
339+
},
340+
336341
// [blockValue]
337342
//
338343
// On stack, before: hash, inverse, program, value
@@ -1132,6 +1137,9 @@ JavaScriptCompiler.prototype = {
11321137
}
11331138
})();
11341139

1140+
/**
1141+
* @deprecated May be removed in the next major version
1142+
*/
11351143
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
11361144
return (
11371145
!JavaScriptCompiler.RESERVED_WORDS[name] &&

lib/handlebars/helpers/lookup.js

+3-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
export const dangerousPropertyRegex = /^(constructor|__defineGetter__|__defineSetter__|__lookupGetter__|__proto__)$/;
2-
31
export default function(instance) {
4-
instance.registerHelper('lookup', function(obj, field) {
2+
instance.registerHelper('lookup', function(obj, field, options) {
53
if (!obj) {
4+
// Note for 5.0: Change to "obj == null" in 5.0
65
return obj;
76
}
8-
if (
9-
dangerousPropertyRegex.test(String(field)) &&
10-
!Object.prototype.propertyIsEnumerable.call(obj, field)
11-
) {
12-
return undefined;
13-
}
14-
return obj[field];
7+
return options.lookupProperty(obj, field);
158
});
169
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { extend } from '../utils';
2+
3+
/**
4+
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
5+
* The resulting object can be used with "object[property]" to check if a property exists
6+
* @param {...object} sources a varargs parameter of source objects that will be merged
7+
* @returns {object}
8+
*/
9+
export function createNewLookupObject(...sources) {
10+
return extend(Object.create(null), ...sources);
11+
}

lib/handlebars/internal/wrapHelper.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function wrapHelper(helper, transformOptionsFn) {
2+
let wrapper = function(/* dynamic arguments */) {
3+
const options = arguments[arguments.length - 1];
4+
arguments[arguments.length - 1] = transformOptionsFn(options);
5+
return helper.apply(this, arguments);
6+
};
7+
return wrapper;
8+
}

lib/handlebars/runtime.js

+51-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
REVISION_CHANGES
88
} from './base';
99
import { moveHelperToHooks } from './helpers';
10+
import { wrapHelper } from './internal/wrapHelper';
11+
import { createNewLookupObject } from './internal/createNewLookupObject';
1012

1113
export function checkRevision(compilerInfo) {
1214
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
@@ -66,13 +68,17 @@ export function template(templateSpec, env) {
6668
}
6769
partial = env.VM.resolvePartial.call(this, partial, context, options);
6870

69-
let optionsWithHooks = Utils.extend({}, options, { hooks: this.hooks });
71+
let extendedOptions = Utils.extend({}, options, {
72+
hooks: this.hooks,
73+
allowedProtoMethods: this.allowedProtoMethods,
74+
allowedProtoProperties: this.allowedProtoProperties
75+
});
7076

7177
let result = env.VM.invokePartial.call(
7278
this,
7379
partial,
7480
context,
75-
optionsWithHooks
81+
extendedOptions
7682
);
7783

7884
if (result == null && env.compile) {
@@ -81,7 +87,7 @@ export function template(templateSpec, env) {
8187
templateSpec.compilerOptions,
8288
env
8389
);
84-
result = options.partials[options.name](context, optionsWithHooks);
90+
result = options.partials[options.name](context, extendedOptions);
8591
}
8692
if (result != null) {
8793
if (options.indent) {
@@ -115,10 +121,26 @@ export function template(templateSpec, env) {
115121
}
116122
return obj[name];
117123
},
124+
lookupProperty: function(parent, propertyName) {
125+
let result = parent[propertyName];
126+
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
127+
return result;
128+
}
129+
const whitelist =
130+
typeof result === 'function'
131+
? container.allowedProtoMethods
132+
: container.allowedProtoProperties;
133+
134+
if (whitelist[propertyName] === true) {
135+
return result;
136+
}
137+
return undefined;
138+
},
118139
lookup: function(depths, name) {
119140
const len = depths.length;
120141
for (let i = 0; i < len; i++) {
121-
if (depths[i] && depths[i][name] != null) {
142+
let result = depths[i] && container.lookupProperty(depths[i], name);
143+
if (result != null) {
122144
return depths[i][name];
123145
}
124146
}
@@ -226,7 +248,9 @@ export function template(templateSpec, env) {
226248

227249
function _setup(options) {
228250
if (!options.partial) {
229-
container.helpers = Utils.extend({}, env.helpers, options.helpers);
251+
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
252+
wrapHelpersToPassLookupProperty(mergedHelpers, container);
253+
container.helpers = mergedHelpers;
230254

231255
if (templateSpec.usePartial) {
232256
// Use mergeIfNeeded here to prevent compiling global partials multiple times
@@ -244,13 +268,21 @@ export function template(templateSpec, env) {
244268
}
245269

246270
container.hooks = {};
271+
container.allowedProtoProperties = createNewLookupObject(
272+
options.allowedProtoProperties
273+
);
274+
container.allowedProtoMethods = createNewLookupObject(
275+
options.allowedProtoMethods
276+
);
247277

248278
let keepHelperInHelpers =
249279
options.allowCallsToHelperMissing ||
250280
templateWasPrecompiledWithCompilerV7;
251281
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
252282
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
253283
} else {
284+
container.allowedProtoProperties = options.allowedProtoProperties;
285+
container.allowedProtoMethods = options.allowedProtoMethods;
254286
container.helpers = options.helpers;
255287
container.partials = options.partials;
256288
container.decorators = options.decorators;
@@ -381,3 +413,17 @@ function executeDecorators(fn, prog, container, depths, data, blockParams) {
381413
}
382414
return prog;
383415
}
416+
417+
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
418+
Object.keys(mergedHelpers).forEach(helperName => {
419+
let helper = mergedHelpers[helperName];
420+
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
421+
});
422+
}
423+
424+
function passLookupPropertyOption(helper, container) {
425+
const lookupProperty = container.lookupProperty;
426+
return wrapHelper(helper, options => {
427+
return Utils.extend({ lookupProperty }, options);
428+
});
429+
}

package-lock.json

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"babel-runtime": "^5.1.10",
3636
"benchmark": "~1.0",
3737
"chai": "^4.2.0",
38+
"chai-diff": "^1.0.1",
3839
"concurrently": "^5.0.0",
3940
"dirty-chai": "^2.0.1",
4041
"dtslint": "^0.5.5",
@@ -84,7 +85,7 @@
8485
"scripts": {
8586
"format": "prettier --write '**/*.js' && eslint --fix .",
8687
"check-format": "prettier --check '**/*.js'",
87-
"lint": "eslint --max-warnings 0 . ",
88+
"lint": "eslint --max-warnings 0 .",
8889
"dtslint": "dtslint types",
8990
"test": "grunt",
9091
"extensive-tests-and-publish-to-aws": "npx mocha tasks/task-tests/ && grunt --stack extensive-tests-and-publish-to-aws",

0 commit comments

Comments
 (0)