diff --git a/src/lib/annotations/annotations.html b/src/lib/annotations/annotations.html index 4e484357eb..0f2f678d65 100644 --- a/src/lib/annotations/annotations.html +++ b/src/lib/annotations/annotations.html @@ -75,7 +75,7 @@ parseAnnotations: function(template) { var list = []; var content = template._content || template.content; - this._parseNodeAnnotations(content, list, + this._parseNodeAnnotations(content, list, template.hasAttribute('strip-whitespace')); return list; }, @@ -89,7 +89,33 @@ this._parseElementAnnotations(node, list, stripWhiteSpace); }, - _bindingRegex: /([^{[]*)(\{\{|\[\[)(?!\}\}|\]\])(.+?)(?:\]\]|\}\})/g, + _bindingRegex: (function() { + var IDENT = '([\\w\\s-_.:$]+)'; + // The following lookahead group (?= ... ) and backreference \\4, etc. + // for repeating identifier characters prevent catastrophic + // backtracking when the argument which contains the identifier itself + // is repeated. This approximates possessive/atomic groups which are + // otherwise unsupported in Javascript. Unfortunately this means each + // usage of identifier must be separated to index the backreference + // correctly. + // v..............v v + var PROPERTY = '(?:(?=' + IDENT + ')\\4)'; + var METHOD = '(?:(?=' + IDENT + ')\\5)'; + var ARG_IDENT = '(?:(?=' + IDENT + ')\\6)'; + var NUMBER = '(?:' + '[0-9]+' + ')'; + var SQUOTE_STRING = '(?:' + '\'(?:[^\'\\\\]|\\\\\'|\\\\,)*\'' + ')'; + var DQUOTE_STRING = '(?:' + '"(?:[^"\\\\]|\\\\"|\\\\,)*"' + ')'; + var STRING = '(?:' + SQUOTE_STRING + '|' + DQUOTE_STRING + ')'; + var ARGUMENT = '(?:' + ARG_IDENT + '|' + NUMBER + '|' + STRING + ')'; + var ARGUMENT_LIST = '(?:(?:' + ARGUMENT + ',?' + ')*)'; + var COMPUTED_FUNCTION = '(?:' + METHOD + '\\(' + ARGUMENT_LIST + '\\)' + ')'; + var BINDING = '(' + PROPERTY + '|' + COMPUTED_FUNCTION + ')'; // Group 3 + var OPEN_BRACKET = '(\\[\\[|{{)'; // Group 1 + var CLOSE_BRACKET = '(?:]]|}})'; + var NEGATE = '(!?)'; // Group 2 + var EXPRESSION = OPEN_BRACKET + NEGATE + BINDING + CLOSE_BRACKET; + return new RegExp(EXPRESSION, "g"); + })(), // TODO(kschaaf): We could modify this to allow an escape mechanism by // looking for the escape sequence in each of the matches and converting @@ -98,29 +124,24 @@ _parseBindings: function(text) { var re = this._bindingRegex; var parts = []; - var m, lastIndex; - // Example: "literal1{{binding1}}literal2[[binding2]]final" + var lastIndex = 0; + var m; + // Example: "literal1{{prop}}literal2[[!compute(foo,bar)]]final" // Regex matches: - // Iteration 1: Iteration 2: - // m[1]: 'literal1' 'literal2' - // m[2]: '{{' '[[' - // m[3]: 'binding1' 'binding2' - // 'final' is manually substring'ed from end + // Iteration 1: Iteration 2: + // m[1]: '{{' '[[' + // m[2]: '' '!' + // m[3]: 'prop' 'compute(foo,bar)' while ((m = re.exec(text)) !== null) { // Add literal part - if (m[1]) { - parts.push({literal: m[1]}); + if (m.index > lastIndex) { + parts.push({literal: text.slice(lastIndex, m.index)}); } // Add binding part // Mode (one-way or two) - var mode = m[2][0]; + var mode = m[1][0]; + var negate = Boolean(m[2]); var value = m[3].trim(); - // Negate - var negate = false; - if (value[0] == '!') { - negate = true; - value = value.substring(1).trim(); - } var customEvent, notifyEvent, colon; if (mode == '{' && (colon = value.indexOf('::')) > 0) { notifyEvent = value.substring(colon + 2); @@ -242,7 +263,7 @@ } // if this node didn't get evacipated, parse it. if (node.parentNode) { - var childAnnotation = this._parseNodeAnnotations(node, list, + var childAnnotation = this._parseNodeAnnotations(node, list, stripWhiteSpace); if (childAnnotation) { childAnnotation.parent = annote; diff --git a/test/unit/bind-elements.html b/test/unit/bind-elements.html index 0d1c535058..5f7c066fee 100644 --- a/test/unit/bind-elements.html +++ b/test/unit/bind-elements.html @@ -26,9 +26,14 @@ no-computed="{{foobared(noInlineComputed)}}" compoundAttr1$="{{cpnd1}}{{ cpnd2 }}{{cpnd3.prop}}{{ computeCompound(cpnd4, cpnd5, 'literal')}}" compoundAttr2$="literal1 {{cpnd1}} literal2 {{cpnd2}}{{cpnd3.prop}} literal3 {{computeCompound(cpnd4, cpnd5, 'literal')}} literal4" - compoundAttr3$="{{computeCompound('world', 'username ', 'Hello {0} ')}}" + compoundAttr3$="[yes/no]: {{cpnd1}}, {{computeCompound('world', 'username ', 'Hello {0} ')}}" > Test + + {{really.long.identifier.in.malformed.binding.should.be.ignored]} + {{computeFromLiterals(3, 'really.long.literal.in.malformed.binding.should.be.ignored)]} + + {{computeFromLiterals(3, 'foo', bool)}} = 0, true); + assert.isTrue(el.$.boundChild.textContent.indexOf('really.long.literal.in.malformed.binding.should.be.ignored') >= 0, true); + assert.isTrue(el.$.boundChild.textContent.indexOf('3foo') >= 0, true); + }); + });