From 0625668ece7a0df523e99d0b42920ef6feeb8ab6 Mon Sep 17 00:00:00 2001 From: Michael Rawlings Date: Tue, 27 Jul 2021 12:02:27 -0700 Subject: [PATCH] feat: bound attributes (cherry picked from commit 7287b94363965befaed1ebb500567b51bac0b3de) --- src/states/ATTRIBUTE.ts | 12 +++++++++- src/states/EXPRESSION.ts | 3 +-- src/states/OPEN_TAG.ts | 10 +++++--- src/states/TAG_NAME.ts | 12 +++++++++- src/util/constants.ts | 1 + src/util/notify-util.ts | 1 + test/autotest/attr-bound/expected.html | 23 +++++++++++++++++++ test/autotest/attr-bound/input.htmljs | 6 +++++ .../tag-var-declaration/expected.html | 2 +- test/util/TreeBuilder.js | 7 ++++-- 10 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 test/autotest/attr-bound/expected.html create mode 100644 test/autotest/attr-bound/input.htmljs diff --git a/src/states/ATTRIBUTE.ts b/src/states/ATTRIBUTE.ts index 1fe2c704..16d795f8 100644 --- a/src/states/ATTRIBUTE.ts +++ b/src/states/ATTRIBUTE.ts @@ -137,7 +137,15 @@ export const ATTRIBUTE = Parser.createState({ char(ch, code, attr) { if (isWhitespaceCode(code)) { return; - } else if (code === CODE.EQUAL) { + } else if ( + code === CODE.EQUAL || + (code === CODE.COLON && this.lookAtCharCodeAhead(1) === CODE.EQUAL) + ) { + if (code === CODE.COLON) { + attr.bound = true; + this.skip(1); + } + // TODO: make expressions consume beginning whitespace? this.consumeWhitespace(); this.enterState(STATE.EXPRESSION, { @@ -166,9 +174,11 @@ export const ATTRIBUTE = Parser.createState({ this.enterState(STATE.EXPRESSION, { part: "NAME", terminatedByWhitespace: true, + skipOperators: true, terminator: [ this.isConcise ? "]" : "/>", this.isConcise ? ";" : ">", + ":=", "=", ",", "(" diff --git a/src/states/EXPRESSION.ts b/src/states/EXPRESSION.ts index 6e236422..16a22676 100644 --- a/src/states/EXPRESSION.ts +++ b/src/states/EXPRESSION.ts @@ -106,11 +106,10 @@ export const EXPRESSION = Parser.createState({ char(ch, code, expression) { let depth = expression.groupStack.length; - let parentState = expression.parentState; if (depth === 0) { if (expression.terminatedByWhitespace && isWhitespaceCode(code)) { - var operator = this.checkForOperator(); + var operator = !expression.skipOperators && this.checkForOperator(); if (operator) { expression.isStringLiteral = false; diff --git a/src/states/OPEN_TAG.ts b/src/states/OPEN_TAG.ts index 03983ceb..0bfca249 100644 --- a/src/states/OPEN_TAG.ts +++ b/src/states/OPEN_TAG.ts @@ -188,6 +188,7 @@ export const OPEN_TAG = Parser.createState({ if (nextCharCode === CODE.PERIOD || nextCharCode === CODE.NUMBER_SIGN) { this.enterState(STATE.EXPRESSION, { part: "NAME", + skipOperators: true, terminatedByWhitespace: true, terminator: [ this.isConcise ? ";" : ">", @@ -195,7 +196,9 @@ export const OPEN_TAG = Parser.createState({ "(", "|", ".", - "#" + "#", + "=", + ":=" ] }); } @@ -394,10 +397,11 @@ export const OPEN_TAG = Parser.createState({ } else if (code === CODE.FORWARD_SLASH && !this.currentOpenTag.attributes.length) { this.enterState(STATE.EXPRESSION, { part: "VARIABLE", + skipOperators: true, terminatedByWhitespace: true, terminator: this.isConcise - ? [";", "(", "|", "="] - : [">", "/>", "(", "|", "="] + ? [";", "(", "|", "=", ":="] + : [">", "/>", "(", "|", "=", ":="] }); } else if (code === CODE.OPEN_PAREN && !this.currentOpenTag.attributes.length) { if (this.currentOpenTag.argument != null) { diff --git a/src/states/TAG_NAME.ts b/src/states/TAG_NAME.ts index 42a568e8..a43eabe3 100644 --- a/src/states/TAG_NAME.ts +++ b/src/states/TAG_NAME.ts @@ -119,7 +119,17 @@ export const TAG_NAME = Parser.createState({ tagName.text += nextCh; tagName.rawValue += ch + nextCh; - } else if (isWhitespaceCode(code) || code === CODE.EQUAL || code === CODE.OPEN_PAREN || code === CODE.FORWARD_SLASH || code === CODE.PIPE || (this.isConcise ? code === CODE.SEMICOLON : code === CODE.CLOSE_ANGLE_BRACKET)) { + } else if ( + isWhitespaceCode(code) || + code === CODE.EQUAL || + (code === CODE.COLON && this.lookAtCharCodeAhead(1) === CODE.EQUAL) || + code === CODE.OPEN_PAREN || + code === CODE.FORWARD_SLASH || + code === CODE.PIPE || + (this.isConcise + ? code === CODE.SEMICOLON + : code === CODE.CLOSE_ANGLE_BRACKET) + ) { this.exitState(); } else if (code === CODE.PERIOD || code === CODE.NUMBER_SIGN) { if (!tagName.shorthandCharCode) { diff --git a/src/util/constants.ts b/src/util/constants.ts index f0163bb6..ec197a1f 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -22,6 +22,7 @@ export enum CODE { PERCENT = 37, PERIOD = 46, COMMA = 44, + COLON = 58, SEMICOLON = 59, NUMBER_SIGN = 35, PIPE = 124, diff --git a/src/util/notify-util.ts b/src/util/notify-util.ts index 617995da..fc26b702 100644 --- a/src/util/notify-util.ts +++ b/src/util/notify-util.ts @@ -136,6 +136,7 @@ export function createNotifiers(parser, listeners) { endPos: attr.endPos, argument: attr.argument, method: attr.method, + bound: attr.bound, }; if (attr.hasOwnProperty("literalValue")) { diff --git a/test/autotest/attr-bound/expected.html b/test/autotest/attr-bound/expected.html new file mode 100644 index 00000000..6077bbe3 --- /dev/null +++ b/test/autotest/attr-bound/expected.html @@ -0,0 +1,23 @@ + + text:"test" + +text:"\n" + + text:"test" + +text:"\n" + + text:"test" + +text:"\n" + + text:"test" + +text:"\n" + + text:"test" + +text:"\n" + + text:"test" + diff --git a/test/autotest/attr-bound/input.htmljs b/test/autotest/attr-bound/input.htmljs new file mode 100644 index 00000000..f36e98f7 --- /dev/null +++ b/test/autotest/attr-bound/input.htmljs @@ -0,0 +1,6 @@ +test +test +test +test +test +test \ No newline at end of file diff --git a/test/autotest/tag-var-declaration/expected.html b/test/autotest/tag-var-declaration/expected.html index c889c25f..458eb84e 100644 --- a/test/autotest/tag-var-declaration/expected.html +++ b/test/autotest/tag-var-declaration/expected.html @@ -12,7 +12,7 @@ text:"\n" - + text:"\n" diff --git a/test/util/TreeBuilder.js b/test/util/TreeBuilder.js index 3e2e929f..31508875 100644 --- a/test/util/TreeBuilder.js +++ b/test/util/TreeBuilder.js @@ -34,7 +34,11 @@ function attributeAssignmentToString(attr, includeLiteralValues) { var result = ""; if (attr.value) { - result += '=' + attr.value; + if (attr.bound) { + result += '=BOUND(' + attr.value + ")"; + } else { + result += '=' + attr.value; + } } else if (!attr.argument) { result += '=(EMPTY)'; } @@ -285,7 +289,6 @@ class TreeBuilder { if (event.concise) { expect(src.substring(startPos, startPos + tagName.length)).to.equal(tagName); } else { - debugger; expect(src.substring(startPos, startPos + 1 + tagName.length)).to.equal('<' + tagName); if (event.selfClosed) {