diff --git a/lib/mixins/property-effects.html b/lib/mixins/property-effects.html index 15f9e5cdc7..b33b386b0b 100644 --- a/lib/mixins/property-effects.html +++ b/lib/mixins/property-effects.html @@ -13,7 +13,6 @@ - @@ -821,6 +820,24 @@ const emptyArray = []; + // Regular expressions used for binding + const IDENT = '(?:' + '[a-zA-Z_$][\\w.:$\\-*]*' + ')'; + const NUMBER = '(?:' + '[-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?' + ')'; + const SQUOTE_STRING = '(?:' + '\'(?:[^\'\\\\]|\\\\.)*\'' + ')'; + const DQUOTE_STRING = '(?:' + '"(?:[^"\\\\]|\\\\.)*"' + ')'; + const STRING = '(?:' + SQUOTE_STRING + '|' + DQUOTE_STRING + ')'; + const ARGUMENT = '(?:(' + IDENT + '|' + NUMBER + '|' + STRING + ')\\s*' + ')'; + const ARGUMENTS = '(?:' + ARGUMENT + '(?:,\\s*' + ARGUMENT + ')*' + ')'; + const ARGUMENT_LIST = '(?:' + '\\(\\s*' + + '(?:' + ARGUMENTS + '?' + ')' + + '\\)\\s*' + ')'; + const BINDING = '(' + IDENT + '\\s*' + ARGUMENT_LIST + '?' + ')'; // Group 3 + const OPEN_BRACKET = '(\\[\\[|{{)' + '\\s*'; + const CLOSE_BRACKET = '(?:]]|}})'; + const NEGATE = '(?:(!)\\s*)?'; // Group 2 + const EXPRESSION = OPEN_BRACKET + NEGATE + BINDING + CLOSE_BRACKET; + const bindingRegex = new RegExp(EXPRESSION, "g"); + /** * Create a string from binding parts of all the literal parts * @@ -2610,9 +2627,67 @@ * @protected */ static _parseBindings(text, templateInfo) { - const parserParts = Polymer.BindingParser.parse(text, templateInfo); - if (parserParts.length) { - return parserParts; + let parts = []; + let lastIndex = 0; + let m; + // Example: "literal1{{prop}}literal2[[!compute(foo,bar)]]final" + // Regex matches: + // Iteration 1: Iteration 2: + // m[1]: '{{' '[[' + // m[2]: '' '!' + // m[3]: 'prop' 'compute(foo,bar)' + while ((m = bindingRegex.exec(text)) !== null) { + // Add literal part + if (m.index > lastIndex) { + parts.push({literal: text.slice(lastIndex, m.index)}); + } + // Add binding part + let mode = m[1][0]; + let negate = Boolean(m[2]); + let source = m[3].trim(); + let customEvent = false, notifyEvent = '', colon = -1; + if (mode == '{' && (colon = source.indexOf('::')) > 0) { + notifyEvent = source.substring(colon + 2); + source = source.substring(0, colon); + customEvent = true; + } + let signature = parseMethod(source); + let dependencies = []; + if (signature) { + // Inline computed function + let {args, methodName} = signature; + for (let i=0; i + \ No newline at end of file diff --git a/lib/utils/binding-parser.html b/lib/utils/binding-parser.html index 357d17a357..0e614d6a2b 100644 --- a/lib/utils/binding-parser.html +++ b/lib/utils/binding-parser.html @@ -9,117 +9,151 @@ --> + + diff --git a/test/unit/property-effects.html b/test/unit/property-effects.html index bef3730a68..40f93aa5c0 100644 --- a/test/unit/property-effects.html +++ b/test/unit/property-effects.html @@ -358,30 +358,6 @@ assert.equal(el.$.boundWithDash.textContent, 'yes'); }); - test('binding with slash', function() { - el.objectWithSlash = { - 'binding/with/slash': 'yes' - }; - assert.equal(el.$.boundWithSlash.textContent, 'yes'); - }); - - test('json should not be a binding', function() { - assert.equal(el.$.jsonContent.textContent, '[["Jan", 31],["Feb", 28],["Mar", 31]]'); - }); - - test('binding with non-English unicode', function() { - el.objectWithNonEnglishUnicode = { - 商品名: 'yes' - }; - assert.equal(el.$.nonEnglishUnicode.textContent, 'yes'); - }); - - test('binding with booleans', function() { - el.otherValue = 10; - assert.equal(el.$.booleanTrue.textContent, 'foo(field, true): 10'); - assert.equal(el.$.booleanFalse.textContent, 'foo(field, false): 20'); - }); - test('class attribute without template scope not erased', function() { var el = document.querySelector('.class1'); assert.notEqual(el, null, 'class without template scope is undefined'); @@ -443,6 +419,48 @@ assert.equal(el.__observerCalled, 1); }); }); + + suite('can work with binding parser', function() { + setup(function() { + document.body.removeChild(el); + el = document.createElement('x-basic-binding-parser'); + document.body.appendChild(el); + }); + + test('binding with slash', function() { + el.objectWithSlash = { + 'binding/with/slash': 'yes' + }; + assert.equal(el.$.boundWithSlash.textContent, 'yes'); + }); + + test('json should not be a binding', function() { + assert.equal(el.$.jsonContent.textContent, '[["Jan", 31],["Feb", 28],["Mar", 31]]'); + }); + + test('binding with non-English unicode', function() { + el.objectWithNonEnglishUnicode = { + 商品名: 'yes' + }; + assert.equal(el.$.nonEnglishUnicode.textContent, 'yes'); + }); + + test('binding with booleans', function() { + el.otherValue = 10; + assert.equal(el.$.booleanTrue.textContent, 'foo(field, true): 10'); + assert.equal(el.$.booleanFalse.textContent, 'foo(field, false): 20'); + }); + + suite('equivalent behavior as regex', function() { + // Loop over the suite "single-element binding effects" (parent of the parent of this suite) + // And make sure that the tests there also pass on the binding-parser + // + // t.title is the name of the test and t.fn contains the test body + this.parent.parent.tests.forEach(t => { + test(t.title, t.fn); + });; + }); + }); }); suite('computed bindings with dynamic functions', function() {