diff --git a/README.md b/README.md index 19c063ff..1aff50c3 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ Therefore, we do not recommend using this shortcut. - `strict` When set to `true`, generated function is in strict mode - `_with` Whether or not to use `with() {}` constructs. If `false` then the locals will be stored in the `locals` object. Set to `false` in strict mode. + - `destructuredLocals` An array of local variables that are always destructured from + the locals object, available even in strict mode. - `localsName` Name to use for the object storing local variables when not using `with` Defaults to `locals` - `rmWhitespace` Remove all safe-to-remove whitespace, including leading diff --git a/docs/jsdoc/options.jsdoc b/docs/jsdoc/options.jsdoc index 2edb1d5c..5955ea1e 100644 --- a/docs/jsdoc/options.jsdoc +++ b/docs/jsdoc/options.jsdoc @@ -17,6 +17,14 @@ * whose name is specified by {@link module:ejs.localsName} (default to * `locals`). * + * @property {Boolean} [strict=false] + * Whether to run in strict mode or not. + * Enforces `_with=false`. + * + * @property {String[]} [destructuredLocals=[]] + * An array of local variables that are always destructured from {@link module:ejs.localsName}, + * available even in strict mode. + * * @property {Boolean} [rmWhitespace=false] * Remove all safe-to-remove whitespace, including leading and trailing * whitespace. It also enables a safer version of `-%>` line slurping for all diff --git a/lib/ejs.js b/lib/ejs.js index b011424d..b02a81e9 100755 --- a/lib/ejs.js +++ b/lib/ejs.js @@ -526,6 +526,7 @@ function Template(text, opts) { options.localsName = opts.localsName || exports.localsName || _DEFAULT_LOCALS_NAME; options.views = opts.views; options.async = opts.async; + options.destructuredLocals = opts.destructuredLocals; options.legacyInclude = typeof opts.legacyInclude != 'undefined' ? !!opts.legacyInclude : true; if (options.strict) { @@ -575,6 +576,17 @@ Template.prototype = { if (opts.outputFunctionName) { prepended += ' var ' + opts.outputFunctionName + ' = __append;' + '\n'; } + if (opts.destructuredLocals && opts.destructuredLocals.length) { + var destructuring = ' var __locals = (' + opts.localsName + ' || {}),\n'; + for (var i = 0; i < opts.destructuredLocals.length; i++) { + var name = opts.destructuredLocals[i]; + if (i > 0) { + destructuring += ',\n '; + } + destructuring += name + ' = __locals.' + name; + } + prepended += destructuring + ';\n'; + } if (opts._with !== false) { prepended += ' with (' + opts.localsName + ' || {}) {' + '\n'; appended += ' }' + '\n'; diff --git a/test/ejs.js b/test/ejs.js index 5877264e..88edd796 100755 --- a/test/ejs.js +++ b/test/ejs.js @@ -124,6 +124,42 @@ suite('ejs.compile(str, options)', function () { assert.equal(ejs.render(fixture('strict.ejs'), {}, {strict: true}), 'true'); }); + test('destructuring works in strict mode as an alternative to `with`', function () { + var locals = Object.create(null); + locals.foo = 'bar'; + assert.equal(ejs.render(fixture('strict-destructuring.ejs'), locals, { + strict: true, + destructuredLocals: Object.keys(locals), + _with: true + }), locals.foo); + }); + + test('destructuring works in strict and async mode', function (done) { + try { + eval('(async function() {})'); + } catch (e) { + if (e instanceof SyntaxError) { + done(); + return; + } else { + throw e; + } + } + + var locals = Object.create(null); + locals.foo = 'bar'; + ejs.render(fixture('strict-destructuring.ejs'), locals, { + strict: true, + async: true, + destructuredLocals: Object.keys(locals), + }).then(function (value) { + assert.equal(value, locals.foo); + }).then( + () => done(), + e => done(e) + ); + }); + test('can compile to an async function', function (done) { try { eval('(async function() {})'); diff --git a/test/fixtures/strict-destructuring.ejs b/test/fixtures/strict-destructuring.ejs new file mode 100644 index 00000000..5fd2ae14 --- /dev/null +++ b/test/fixtures/strict-destructuring.ejs @@ -0,0 +1,5 @@ +<% +// Unspecified execution context should be `undefined` in strict mode +var isReallyStrict = !((function () { return this; })()); +-%> +<%= isReallyStrict && foo -%>