From 897ba436728dc71381e300d69237b8b9a32a758e Mon Sep 17 00:00:00 2001 From: Nick Iaconis Date: Fri, 1 Apr 2016 17:20:55 -0700 Subject: [PATCH 1/2] Preserve commas that are not part of a brace expansion --- packages/ember-metal/lib/expand_properties.js | 62 ++++++++++++------- .../tests/expand_properties_test.js | 12 +++- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/packages/ember-metal/lib/expand_properties.js b/packages/ember-metal/lib/expand_properties.js index a89dce5b654..1f206a4b0c0 100644 --- a/packages/ember-metal/lib/expand_properties.js +++ b/packages/ember-metal/lib/expand_properties.js @@ -5,8 +5,7 @@ import { assert } from './debug'; @submodule ember-metal */ -const SPLIT_REGEX = /\{|\}/; -const END_WITH_EACH_REGEX = /\.@each$/; +var END_WITH_EACH_REGEX = /\.@each$/; /** Expands `pattern`, invoking `callback` for each expansion. @@ -63,31 +62,46 @@ export default function expandProperties(pattern, callback) { return true; })(pattern)); - let parts = pattern.split(SPLIT_REGEX); - let properties = [parts]; - - for (let i = 0; i < parts.length; i++) { - let part = parts[i]; - if (part.indexOf(',') >= 0) { - properties = duplicateAndReplace(properties, part.split(','), i); + let properties = [pattern]; + + // Iterating backward over the pattern makes dealing with indices easier. + let bookmark; + let inside = false; + for (let i = pattern.length; i > 0; --i) { + let current = pattern[i - 1]; + + switch (current) { + // Closing curly brace will be the first character of the brace expansion we encounter. + // Bookmark its index so long as we're not already inside a brace expansion. + case '}': + if (!inside) { + bookmark = i - 1; + inside = true; + } + break; + // Opening curly brace will be the last character of the brace expansion we encounter. + // Apply the brace expansion so long as we've already seen a closing curly brace. + case '{': + if (inside) { + let expansion = pattern.slice(i, bookmark).split(','); + // Iterating backward allows us to push new properties w/out affecting our "cursor". + for (let j = properties.length; j > 0; --j) { + // Extract the unexpanded property from the array. + let property = properties.splice(j - 1, 1)[0]; + // Iterate over the expansion, pushing the newly formed properties onto the array. + for (let k = 0; k < expansion.length; ++k) { + properties.push(property.slice(0, i - 1) + + expansion[k] + + property.slice(bookmark + 1)); + } + } + inside = false; + } + break; } } for (let i = 0; i < properties.length; i++) { - callback(properties[i].join('').replace(END_WITH_EACH_REGEX, '.[]')); + callback(properties[i].replace(END_WITH_EACH_REGEX, '.[]')); } } - -function duplicateAndReplace(properties, currentParts, index) { - let all = []; - - properties.forEach(property => { - currentParts.forEach(part => { - let current = property.slice(0); - current[index] = part; - all.push(current); - }); - }); - - return all; -} diff --git a/packages/ember-metal/tests/expand_properties_test.js b/packages/ember-metal/tests/expand_properties_test.js index dea11f2e722..16efd573b01 100644 --- a/packages/ember-metal/tests/expand_properties_test.js +++ b/packages/ember-metal/tests/expand_properties_test.js @@ -89,6 +89,14 @@ QUnit.test('Nested brace expansions are not allowed', function() { }, /Brace expanded properties have to be balanced and cannot be nested/); }); +QUnit.test('A property with no braces does not expand', function() { + expect(1); + + expandProperties('a,b,c.d.e,f', addProperty); + + deepEqual(foundProperties, ['a,b,c.d.e,f']); +}); + QUnit.test('A pattern must be a string', function() { expect(1); @@ -100,7 +108,7 @@ QUnit.test('A pattern must be a string', function() { QUnit.test('A pattern must not contain a space', function() { expect(1); - expectAssertion(() => { - expandProperties('a, b', addProperty); + expectAssertion(function() { + expandProperties('{a, b}', addProperty); }, /Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"/); }); From 14edb5c899e3d7e3e22c4ec3434d869181643d02 Mon Sep 17 00:00:00 2001 From: Nick Iaconis Date: Wed, 4 Jan 2017 11:18:08 -0800 Subject: [PATCH 2/2] fixup! Preserve commas that are not part of a brace expansion Roll unbalanced and nested brace assertion into mini-parser iteration --- packages/ember-metal/lib/expand_properties.js | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/packages/ember-metal/lib/expand_properties.js b/packages/ember-metal/lib/expand_properties.js index 1f206a4b0c0..17ecc2ff3d3 100644 --- a/packages/ember-metal/lib/expand_properties.js +++ b/packages/ember-metal/lib/expand_properties.js @@ -40,28 +40,8 @@ export default function expandProperties(pattern, callback) { 'Brace expanded properties cannot contain spaces, e.g. "user.{firstName, lastName}" should be "user.{firstName,lastName}"', pattern.indexOf(' ') === -1 ); - assert( - `Brace expanded properties have to be balanced and cannot be nested, pattern: ${pattern}`, - ((str) => { - let inBrace = 0; - let char; - for (let i = 0; i < str.length; i++) { - char = str.charAt(i); - - if (char === '{') { - inBrace++; - } else if (char === '}') { - inBrace--; - } - - if (inBrace > 1 || inBrace < 0) { - return false; - } - } - - return true; - })(pattern)); + let unbalancedNestedError = `Brace expanded properties have to be balanced and cannot be nested, pattern: ${pattern}`; let properties = [pattern]; // Iterating backward over the pattern makes dealing with indices easier. @@ -77,6 +57,8 @@ export default function expandProperties(pattern, callback) { if (!inside) { bookmark = i - 1; inside = true; + } else { + assert(unbalancedNestedError, false); } break; // Opening curly brace will be the last character of the brace expansion we encounter. @@ -96,10 +78,15 @@ export default function expandProperties(pattern, callback) { } } inside = false; + } else { + assert(unbalancedNestedError, false); } break; } } + if (inside) { + assert(unbalancedNestedError, false); + } for (let i = 0; i < properties.length; i++) { callback(properties[i].replace(END_WITH_EACH_REGEX, '.[]'));