From 0dc88bb4126c28e290455dd18b33b94cf77f66c1 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Wed, 11 Jul 2012 22:46:16 -0400 Subject: [PATCH] Fix template regressions. [Closes #44] Former-commit-id: 15e98d86c290fea74780822b49b5d71e54064e01 --- lodash.js | 102 ++++++++++++++++++--------------------------------- test/test.js | 25 ++++++++++++- 2 files changed, 60 insertions(+), 67 deletions(-) diff --git a/lodash.js b/lodash.js index 6ee75ebc7..17094979d 100644 --- a/lodash.js +++ b/lodash.js @@ -50,15 +50,10 @@ /** Used to detect delimiter values that should be processed by `tokenizeEvaluate` */ var reComplexDelimiter = /[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; - /** Used to match code generated in place of template delimiters */ - var reDelimiterCodeLeading = /^';\n/, - reDelimiterCodeMiddle = /^' \+\n/, - reDelimiterCodeTrailing = /(?:__p \+= '|\+\n')$/; - /** Used to match empty string literals in compiled template source */ var reEmptyStringLeading = /\b__p \+= '';/g, - reEmptyStringMiddle = /\b(__p \+?=) '' \+/g, - reEmptyStringTrailing = /(__w?e\(.*?\)|\b__w?t\)) \+\n'';/g; + reEmptyStringMiddle = /\b(__p \+=) '' \+/g, + reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; /** Used to insert the data object variable into compiled template source */ var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g; @@ -672,12 +667,10 @@ var index = tokenized.length; if (value) { tokenized[index] = "';\n" + value + ";\n__p += '" - } - else if (escapeValue) { - tokenized[index] = "' +\n__we(" + escapeValue + ") +\n'"; - } - else if (interpolateValue) { - tokenized[index] = "' +\n((__wt = (" + interpolateValue + ")) == null ? '' : __wt) +\n'"; + } else if (escapeValue) { + tokenized[index] = "' +\n__e(" + escapeValue + ") +\n'"; + } else if (interpolateValue) { + tokenized[index] = "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'"; } return token + index; } @@ -3307,11 +3300,8 @@ // https://github.com/olado/doT options || (options = {}); - var endIndex, - isEvaluating, - startIndex, + var isEvaluating, result, - useWith, escapeDelimiter = options.escape, evaluateDelimiter = options.evaluate, interpolateDelimiter = options.interpolate, @@ -3337,83 +3327,63 @@ text = text.replace(interpolateDelimiter, tokenizeInterpolate); } if (evaluateDelimiter != lastEvaluateDelimiter) { + // generate `reEvaluateDelimiter` to match `_.templateSettings.evaluate` + // and internal ``, `` delimiters lastEvaluateDelimiter = evaluateDelimiter; reEvaluateDelimiter = RegExp( (evaluateDelimiter ? evaluateDelimiter.source : '($^)') + '||' , 'g'); } - startIndex = tokenized.length; + isEvaluating = tokenized.length; text = text.replace(reEvaluateDelimiter, tokenizeEvaluate); - endIndex = tokenized.length - 1; - isEvaluating = startIndex <= endIndex; - - // if `options.variable` is not specified and the template contains "evaluate" - // delimiters, inject a with-statement around all "evaluate" delimiters to - // add the data object to the top of the scope chain - if (!variable) { - variable = settings.variable || lastVariable || 'obj'; - useWith = isEvaluating; - - if (useWith) { - tokenized[startIndex] = "';\n__with (" + variable + ') {\n' + tokenized[startIndex] - .replace(reDelimiterCodeLeading, '') - .replace(reDelimiterCodeMiddle, '__p += '); - - tokenized[endIndex] = tokenized[endIndex] - .replace(reDelimiterCodeTrailing, '') + "\n}__\n__p += '"; - } - } - - var strInsertVariable = '$&' + variable + '.', - strDoubleVariable = '$1__d'; + isEvaluating = isEvaluating != tokenized.length; // escape characters that cannot be included in string literals and // detokenize delimiter code snippets - text = "__p = '" + text + text = "__p += '" + text .replace(reUnescapedString, escapeStringChar) .replace(reToken, detokenize) + "';\n"; // clear stored code snippets tokenized.length = 0; - // find the start and end indexes of the with-statement - if (useWith) { - startIndex = text.indexOf('__with'); - endIndex = text.indexOf('}__', startIndex + 10); + // if `options.variable` is not specified and the template contains "evaluate" + // delimiters, wrap a with-statement around the generated code to add the + // data object to the top of the scope chain + if (!variable) { + variable = settings.variable || lastVariable || 'obj'; + + if (isEvaluating) { + text = 'with (' + variable + ') {\n' + text + '\n}\n'; + } + else { + if (variable != lastVariable) { + // generate `reDoubleVariable` to match references like `obj.obj` inside + // transformed "escape" and "interpolate" delimiters + lastVariable = variable; + reDoubleVariable = RegExp('(\\(\\s*)' + variable + '\\.' + variable + '\\b', 'g'); + } + // avoid a with-statement by prepending data object references to property names + text = text + .replace(reInsertVariable, '$&' + variable + '.') + .replace(reDoubleVariable, '$1__d'); + } } - // memoize `reDoubleVariable` - if (variable != lastVariable) { - lastVariable = variable; - reDoubleVariable = RegExp('([(\\s])' + variable + '\\.' + variable + '\\b', 'g'); - } - // prepend data object references to property names outside of the with-statement - text = (useWith ? text.slice(0, startIndex) : text) - .replace(reInsertVariable, strInsertVariable) - .replace(reDoubleVariable, strDoubleVariable) + - (useWith - ? text.slice(startIndex + 2, endIndex + 1) + - text.slice(endIndex + 3) - .replace(reInsertVariable, strInsertVariable) - .replace(reDoubleVariable, strDoubleVariable) - : '' - ); // cleanup code by stripping empty strings - text = (isEvaluating ? text.replace(reEmptyStringLeading, '') : text) + text = ( isEvaluating ? text.replace(reEmptyStringLeading, '') : text) .replace(reEmptyStringMiddle, '$1') .replace(reEmptyStringTrailing, '$1;'); // frame code as the function body text = 'function(' + variable + ') {\n' + variable + ' || (' + variable + ' = {});\n' + - 'var __p, __t, __wt' + - ', __d = ' + variable + '.' + variable + ' || ' + variable + - ', __e = _.escape, __we = __e' + + 'var __t, __p = \'\', __e = _.escape' + (isEvaluating ? ', __j = Array.prototype.join;\n' + 'function print() { __p += __j.call(arguments, \'\') }\n' - : ';\n' + : ', __d = ' + variable + '.' + variable + ' || ' + variable + ';\n' ) + text + 'return __p\n}'; diff --git a/test/test.js b/test/test.js index e6bed5281..c240e244c 100644 --- a/test/test.js +++ b/test/test.js @@ -771,9 +771,32 @@ var compiled = _.template(key), data = { 'a': 1, 'b': 2 }; - equal(compiled(data), value); + equal(compiled(data), value, key); }); }); + + test('should allow referencing variables declared in "evaluate" delimiters from other delimiters', function() { + var compiled = _.template('<% var b = a; %><%= b.value %>'), + data = { 'a': { 'value': 1 } }; + + equal(compiled(data), '1'); + }); + + test('should work when passing `options.variable`', function() { + var compiled = _.template( + '<% _.forEach( data.a, function( value ) { %>' + + '<%= value.valueOf() %>' + + '<% }) %>', null, { 'variable': 'data' } + ); + + var data = { 'a': [1, 2, 3] }; + + try { + equal(compiled(data), '123'); + } catch(e) { + ok(false); + } + }); }()); /*--------------------------------------------------------------------------*/