From f43fb98c0ac3c4310454cdbab50bfdeb027159dc Mon Sep 17 00:00:00 2001 From: Int <50065494+dhlolo@users.noreply.github.com> Date: Thu, 24 Mar 2022 09:26:53 +0800 Subject: [PATCH] feat: support parse interpolation in `WXAttribute` (#25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: #19 * fix: 修正測試用例 * fix: add new unit test * fix: mv incoordination raw to rawValue * fix: inline Co-authored-by: iChenLei --- .github/CODEOWNERS | 4 +- .gitignore | 2 +- .vscode/launch.json | 3 - src/ast/build-ast.ts | 140 +++++++++++++++++++++++++++++++----- src/ast/util.ts | 29 ++++++++ src/cst/lexer.ts | 124 ++++++++++++++++++++++++++++---- src/cst/parser.ts | 114 +++++++++++++++++++++++++---- tests/base-spec.js | 13 ++++ tests/error-spec.js | 8 +-- tests/interpolation-spec.js | 138 +++++++++++++++++++++++++++-------- tests/wxs-spec.js | 21 ++++++ 11 files changed, 516 insertions(+), 80 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6ee727b..12e25b5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,5 @@ # @wxml/parser is Lei Chen's personal project # Owner -* @iChenLei \ No newline at end of file +* @iChenLei +# Collaborator +src/ @dhlolo \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7f2f1d3..d8cfe41 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ lib dist .DS_store .idea -src/vscodedebug/test.ts \ No newline at end of file +src/vscodedebug/test.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index d57a165..56d644d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,7 +1,4 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { diff --git a/src/ast/build-ast.ts b/src/ast/build-ast.ts index f66dab8..7365da8 100644 --- a/src/ast/build-ast.ts +++ b/src/ast/build-ast.ts @@ -8,6 +8,7 @@ import { convertLexerErrorToNode, convertParseErrorToNode, sortTokenChildren, + sortASTNode, } from "./util"; type ICtx = Record; @@ -87,15 +88,19 @@ class CstToAstVisitor extends BaseWxmlCstVisitor { mergeLocation(astNode.startTag, startTagLocation); } - if (ctx.SLASH_OPEN?.[0] && ctx.END?.[0]) { + if (ctx.WXS_SLASH_CLOSE?.[0]) { astNode.endTag = { type: "WXEndTag", - name: get(ctx, "END_NAME[0].image"), - }; - const endTagLocation = { - ...pick(ctx.SLASH_OPEN[0], ["startOffset", "startLine", "startColumn"]), - ...pick(ctx.END[0], ["endOffset", "endLine", "endColumn"]), + name: "wxs", }; + const endTagLocation = pick(ctx.WXS_SLASH_CLOSE[0], [ + "startOffset", + "startLine", + "startColumn", + "endOffset", + "endLine", + "endColumn", + ]); mergeLocation(astNode.endTag, endTagLocation); } @@ -118,8 +123,8 @@ class CstToAstVisitor extends BaseWxmlCstVisitor { if (ctx.SEA_WS !== undefined) { allTokens = allTokens.concat(ctx.SEA_WS); } - if (ctx.WXS_TEXT !== undefined) { - allTokens = allTokens.concat(ctx.WXS_TEXT); + if (ctx.INLINE_WXS_TEXT !== undefined) { + allTokens = allTokens.concat(ctx.INLINE_WXS_TEXT); } const sortedTokens = sortBy(allTokens, ["startOffset"]); const fullText = map(sortedTokens, "image").join(""); @@ -130,19 +135,122 @@ class CstToAstVisitor extends BaseWxmlCstVisitor { * AST - WXAttribute */ attribute(ctx, { location }) { - const rawValue = get(ctx, "STRING[0].image") || null; - const value = rawValue - ? rawValue - .split("") - .slice(1, rawValue.length - 1) - .join("") + const attributeValue = ctx.attributeValue + ? this.visit(ctx.attributeValue[0]) : null; + const astNode = { type: "WXAttribute", key: ctx.NAME[0].image, + quote: null, + value: null, + rawValue: null, + ...(attributeValue || {}), + }; + mergeLocation(astNode, location); + return astNode; + } + + /** + * AST - WXAttributeValue + */ + attributeValue(ctx, { location }) { + if (ctx.PURE_STRING !== undefined) { + const raw = ctx.PURE_STRING[0].image; + const astNode = { + value: raw + .split("") + .slice(1, raw.length - 1) + .join(""), + rawValue: ctx.PURE_STRING[0].image, + children: [], + interpolations: [], + quote: raw?.length ? raw.slice(0, 1) : null, + }; + mergeLocation(astNode, location); + return astNode; + } else if (ctx.doubleQuoteAttributeVal !== undefined) { + return this.visit(ctx.doubleQuoteAttributeVal[0]); + } else if (ctx.singleQuoteAttributeVal !== undefined) { + return this.visit(ctx.singleQuoteAttributeVal[0]); + } + } + + doubleQuoteAttributeVal(ctx, { location }) { + const interpolationASTS = map( + ctx.attributeValInterpolation, + this.visit.bind(this) + ); + const quote = '"'; + let strASTs = map(ctx.PURE_STRING_IN_DOUBLE_QUOTE, (item) => { + const astNode = { + value: item.image, + type: "WXText", + }; + mergeLocation(astNode, item); + return astNode; + }); + const sortedValue = sortASTNode(interpolationASTS.concat(strASTs)); + // @ts-expect-error + const value = sortedValue.map((v) => v.rawValue || v.value).join(""); + + const astNode = { + value, + rawValue: quote + value + quote, + children: sortedValue, + interpolations: interpolationASTS.map((intp) => { + return { + ...intp, + type: "WXInterpolation", + }; + }), + quote: quote, + }; + mergeLocation(astNode, location); + return astNode; + } + + attributeValInterpolation(ctx, { location }) { + const child = sortTokenChildren(ctx); + // @ts-expect-error + const value = (child || []).map((token) => token.image).join(""); + const astNode = { + type: "WXAttributeInterpolation", + rawValue: value, + value: value.replace(/^{{/, "").replace(/}}$/, ""), + }; + mergeLocation(astNode, location); + return astNode; + } + + singleQuoteAttributeVal(ctx, { location }) { + const interpolationASTS = map( + ctx.attributeValInterpolation, + this.visit.bind(this) + ); + const quote = "'"; + let strASTs = map(ctx.PURE_STRING_IN_SINGLE_QUOTE, (item) => { + const astNode = { + value: item.image, + type: "WXText", + }; + mergeLocation(astNode, item); + return astNode; + }); + const sortedValue = sortASTNode(interpolationASTS.concat(strASTs)); + // @ts-expect-error + const value = sortedValue.map((v) => v.rawValue || v.value).join(""); + const astNode = { value, - rawValue, - quote: rawValue?.length ? rawValue.slice(0, 1) : null, + rawValue: quote + value + quote, + children: sortedValue, + interpolations: interpolationASTS.map((intp) => { + return { + ...intp, + type: "WXInterpolation", + }; + }), + quote: quote, }; mergeLocation(astNode, location); return astNode; diff --git a/src/ast/util.ts b/src/ast/util.ts index 7ab59b5..205c83a 100644 --- a/src/ast/util.ts +++ b/src/ast/util.ts @@ -13,6 +13,35 @@ interface IEspreeError { stack: string; } +interface ASTNode { + loc: { + start: { + line: number; + column: number; + }; + }; +} + +export function sortASTNode(nodes: Array): Array { + let nodeASTs = []; + nodes.forEach((node) => { + nodeASTs.push(node); + }); + let sortedNodes = []; + sortedNodes = nodes.sort((nodeA, nodeB) => { + if ( + nodeA.loc.start.line > nodeB.loc.start.line || + (nodeA.loc.start.line === nodeB.loc.start.line && + nodeA.loc.start.column > nodeB.loc.start.column) + ) { + return 1; + } else { + return -1; + } + }); + return sortedNodes; +} + /** * sort token children */ diff --git a/src/cst/lexer.ts b/src/cst/lexer.ts index 3d72ef6..087f5a2 100644 --- a/src/cst/lexer.ts +++ b/src/cst/lexer.ts @@ -21,12 +21,10 @@ function makePattern(strings, ...args) { return new RegExp(combined); } -const tokensArray = []; export const tokensDictionary = {} as Record; function createToken(options) { const newTokenType = createTokenOrg(options); - tokensArray.push(newTokenType); tokensDictionary[options.name] = newTokenType; return newTokenType; } @@ -44,7 +42,7 @@ FRAGMENT("Name", makePattern`${f.NameStartChar}(${f.NameChar})*`); const WXS_START = createToken({ name: "WXS_START", pattern: /)/; -const INTPN = createToken({ name: "INTPN", pattern: /((?!('|"|}})).)+/ }); +const WXS_END = createToken({ + name: "WXS_END", + pattern: /(?=<\/(( |\t|\n|\r\n)*)wxs(( |\t|\n|\r\n)*))>/, + pop_mode: true, +}); +const WXS_CLOSE = createToken({ + name: "WXS_CLOSE", + pattern: /(?!<\/(( |\t|\n|\r\n)*)wxs(( |\t|\n|\r\n)*))>/, + push_mode: "WXS_CONTENT", + longer_alt: WXS_END, +}); +const WXS_SLASH_CLOSE = createToken({ + name: "WXS_SLASH_CLOSE", + pattern: /<\/(( |\t|\n|\r\n)*)wxs(( |\t|\n|\r\n)*)>/, + pop_mode: true, +}); const WXS_TEXT = createToken({ name: "WXS_TEXT", - // ( |\t|\n|\r\n)* - // allow these case or - pattern: /[^]+?(?=<\/(( |\t|\n|\r\n)*)wxs(( |\t|\n|\r\n)*)>)/, + pattern: WXS_REG, + line_breaks: true, +}); + +const INLINE_WXS_TEXT = createToken({ + name: "INLINE_WXS_TEXT", + pattern: WXS_REG, line_breaks: true, + pop_mode: true, }); const CLOSE = createToken({ name: "CLOSE", pattern: />/, pop_mode: true }); @@ -116,12 +138,24 @@ const MUSTACHE_LEFT = createToken({ push_mode: "INTPN_INSIDE", }); +const MUSTACHE_LEFT_IN_QUOTE = createToken({ + name: "MUSTACHE_LEFT_IN_QUOTE", + pattern: /\{\{/, + push_mode: "INTPN_IN_QUOTE", +}); + const MUSTACHE_RIGHT = createToken({ name: "MUSTACHE_RIGHT", pattern: /\}\}/, pop_mode: true, }); +const MUSTACHE_RIGHT_IN_QUOTE = createToken({ + name: "MUSTACHE_RIGHT_IN_QUOTE", + pattern: /\}\}/, + pop_mode: true, +}); + const EQUALS = createToken({ name: "EQUALS", pattern: /=/ }); const NAME = createToken({ name: "NAME", pattern: makePattern`${f.Name}` }); @@ -132,6 +166,45 @@ const SPACE = createToken({ group: Lexer.SKIPPED, }); +const PURE_STRING = createToken({ + name: "PURE_STRING", + pattern: /"[^"^\{\{]*"|'[^'^\{\{]*'/, +}); + +const PURE_STRING_IN_DOUBLE_QUOTE = createToken({ + name: "PURE_STRING_IN_DOUBLE_QUOTE", + pattern: /[^"^\{\{^\}\}]+/, +}); + +const PURE_STRING_IN_SINGLE_QUOTE = createToken({ + name: "PURE_STRING_IN_SINGLE_QUOTE", + pattern: /[^'^\{\{^\}\}]+/, +}); + +const DOUBLE_QUOTE_START = createToken({ + name: "DOUBLE_QUOTE_START", + pattern: /"/, + push_mode: "DOUBLE_QUOTE_STR_INSIDE", +}); + +const DOUBLE_QUOTE_END = createToken({ + name: "DOUBLE_QUOTE_END", + pattern: /"/, + pop_mode: true, +}); + +const SINGLE_QUOTE_START = createToken({ + name: "SINGLE_QUOTE_START", + pattern: /'/, + push_mode: "SINGLE_QUOTE_STR_INSIDE", +}); + +const SINGLE_QUOTE_END = createToken({ + name: "SINGLE_QUOTE_END", + pattern: /'/, + pop_mode: true, +}); + const wxmlLexerDefinition = { defaultMode: "OUTSIDE", @@ -142,25 +215,52 @@ const wxmlLexerDefinition = { SEA_WS, SLASH_OPEN, OPEN, - WXS_TEXT, TEXT, MUSTACHE_LEFT, + WXS_TEXT, ], INTPN_INSIDE: [MUSTACHE_RIGHT, INTPN, SEA_WS, STRING], + DOUBLE_QUOTE_STR_INSIDE: [ + DOUBLE_QUOTE_END, + MUSTACHE_LEFT_IN_QUOTE, + PURE_STRING_IN_DOUBLE_QUOTE, + SEA_WS, + ], + SINGLE_QUOTE_STR_INSIDE: [ + SINGLE_QUOTE_END, + MUSTACHE_LEFT_IN_QUOTE, + PURE_STRING_IN_SINGLE_QUOTE, + SEA_WS, + ], + INTPN_IN_QUOTE: [MUSTACHE_RIGHT_IN_QUOTE, INTPN, STRING, SEA_WS], INSIDE: [ - // Tokens from `OUTSIDE` to improve error recovery behavior COMMENT, INVALID_SLASH_OPEN, INVALID_OPEN_INSIDE, - // "Real" `INSIDE` tokens CLOSE, SLASH_CLOSE, SLASH, EQUALS, - STRING, + PURE_STRING, + DOUBLE_QUOTE_START, + SINGLE_QUOTE_START, + NAME, + SPACE, + ], + WXS_INSIDE: [ + WXS_SLASH_CLOSE, + WXS_CLOSE, + SLASH_CLOSE, + SLASH, + EQUALS, + PURE_STRING, + DOUBLE_QUOTE_START, + SINGLE_QUOTE_START, NAME, SPACE, + WXS_END, ], + WXS_CONTENT: [INLINE_WXS_TEXT], }, }; diff --git a/src/cst/parser.ts b/src/cst/parser.ts index bda1d06..e7aa03f 100644 --- a/src/cst/parser.ts +++ b/src/cst/parser.ts @@ -13,6 +13,10 @@ class Parser extends CstParser { content: IRule; wxscontent: IRule; interpolation: IRule; + attributeValue: IRule; + doubleQuoteAttributeVal: IRule; + singleQuoteAttributeVal: IRule; + attributeValInterpolation: IRule; constructor() { super(t, { @@ -59,23 +63,15 @@ class Parser extends CstParser { $.OR([ { ALT: () => { - $.CONSUME(t.CLOSE, { + $.CONSUME(t.WXS_CLOSE, { LABEL: "START_CLOSE", ERR_MSG: "wxs element missing close '>'", }); $.OPTION(() => { $.SUBRULE($.wxscontent); }); - $.CONSUME(t.SLASH_OPEN, { - ERR_MSG: "wxs element missing slash open ''", + $.CONSUME(t.WXS_SLASH_CLOSE, { + ERR_MSG: "wxs element missing slash open ''", }); }, }, @@ -153,13 +149,105 @@ class Parser extends CstParser { { ALT: () => { $.CONSUME(t.EQUALS); - $.CONSUME(t.STRING, { ERR_MSG: "wx attributes missing value" }); + $.SUBRULE($.attributeValue); }, }, ]); }); }); + $.RULE("attributeValue", () => { + $.OR([ + { + ALT: () => + $.CONSUME(t.PURE_STRING, { + ERR_MSG: "wx attributes missing value", + }), + }, + { + ALT: () => $.SUBRULE($.doubleQuoteAttributeVal), + }, + { + ALT: () => $.SUBRULE($.singleQuoteAttributeVal), + }, + ]); + }); + + $.RULE("doubleQuoteAttributeVal", () => { + $.CONSUME(t.DOUBLE_QUOTE_START, { + ERR_MSG: "wx attributes unexpected start", + }); + $.MANY(() => { + $.OR([ + { + ALT: () => + $.CONSUME(t.PURE_STRING_IN_DOUBLE_QUOTE, { + ERR_MSG: "wx attributes missing value", + }), + }, + { + ALT: () => $.SUBRULE($.attributeValInterpolation), + }, + ]); + }); + $.CONSUME(t.DOUBLE_QUOTE_END, { + ERR_MSG: "wx attribute value unexpected end", + }); + }); + + $.RULE("singleQuoteAttributeVal", () => { + $.CONSUME(t.SINGLE_QUOTE_START, { + ERR_MSG: "wx attributes unexpected start", + }); + $.MANY(() => { + $.OR([ + { + ALT: () => + $.CONSUME(t.PURE_STRING_IN_SINGLE_QUOTE, { + ERR_MSG: "wx attributes missing value", + }), + }, + { + ALT: () => $.SUBRULE($.attributeValInterpolation), + }, + ]); + }); + $.CONSUME(t.SINGLE_QUOTE_END, { + ERR_MSG: "wx attribute value unexpected end", + }); + }); + + $.RULE("attributeValInterpolation", () => { + $.CONSUME(t.MUSTACHE_LEFT_IN_QUOTE, { + ERR_MSG: "wx interpolation in attributes value unexpected start", + }); + $.MANY(() => { + $.OR([ + { + ALT: () => + $.CONSUME(t.INTPN, { + ERR_MSG: "wx interpolation in attributes unexpected intpn", + }), + }, + { + ALT: () => + $.CONSUME(t.STRING, { + ERR_MSG: "wx interpolation in attributes unexpected string", + }), + }, + { + ALT: () => + $.CONSUME(t.SEA_WS, { + ERR_MSG: "wx interpolation in attributes unexpected intpn", + }), + }, + ]); + }); + $.CONSUME(t.MUSTACHE_RIGHT_IN_QUOTE, { + ERR_MSG: "wx interpolation in attribute value unexpected end", + }); + }); + $.RULE("chardata", () => { $.OR([ { ALT: () => $.CONSUME(t.TEXT) }, @@ -172,7 +260,7 @@ class Parser extends CstParser { $.CONSUME(t.SEA_WS); }); $.OPTION(() => { - $.CONSUME(t.WXS_TEXT); + $.CONSUME(t.INLINE_WXS_TEXT); }); }); diff --git a/tests/base-spec.js b/tests/base-spec.js index 92a36db..d2a2054 100644 --- a/tests/base-spec.js +++ b/tests/base-spec.js @@ -121,6 +121,19 @@ describe("Base Test Suite", () => { expect(matches[2].quote).to.be.equals("'"); }) + it("can parse WXAttribute #2", () => { + const ast = parse(` + + `); + const matches = esquery(ast, "WXAttribute"); + expect(matches).to.be.lengthOf(1); + expect(matches[0].key).to.be.equals("class"); + expect(matches[0].value).to.be.equals(" {{isOdd ? 'odd' : 'even' }} num-{{idx}} show fixed 2"); + expect(matches[0].rawValue).to.be.equals("\" {{isOdd ? 'odd' : 'even' }} num-{{idx}} show fixed 2\""); + const matches2 = esquery(ast, "WXAttributeInterpolation"); + expect(matches2).to.be.lengthOf(2); + }), + it("can parse WXComment", () => { const ast = parse(` diff --git a/tests/error-spec.js b/tests/error-spec.js index 88773fa..afea2ab 100644 --- a/tests/error-spec.js +++ b/tests/error-spec.js @@ -32,9 +32,9 @@ describe("Error Test Suite", () => { // ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ const parseErrorMatchs = _.get(ast, 'errors') || []; - expect(parseErrorMatchs).to.be.lengthOf(1); + expect(parseErrorMatchs).to.be.lengthOf(2); - const parseError = parseErrorMatchs[0]; + const parseError = parseErrorMatchs[1]; expect(parseError).to.have.property("rawType"); expect(parseError.rawType).to.be.equals("MismatchedTokenException"); }); @@ -53,9 +53,9 @@ describe("Error Test Suite", () => { const parseError = parseErrorMatchs[0]; expect(parseError).to.have.property("rawType"); - expect(parseError.rawType).to.be.equals("MismatchedTokenException"); + expect(parseError.rawType).to.be.equals("NoViableAltException"); expect(parseError).to.have.property("value"); - expect(parseError.value).to.be.equals("wx attributes missing value"); + expect(parseError.value).to.be.equals('Expecting: one of these possible Token sequences:\n 1. [PURE_STRING]\n 2. [DOUBLE_QUOTE_START]\n 3. [SINGLE_QUOTE_START]\nbut found: \'>\'' ); // check WXAttribute const attrsMatchs = esquery(ast, "WXAttribute") diff --git a/tests/interpolation-spec.js b/tests/interpolation-spec.js index 98b8ecb..84ec724 100644 --- a/tests/interpolation-spec.js +++ b/tests/interpolation-spec.js @@ -29,23 +29,61 @@ describe("Interpolation Test Suite", () => { expect(wxInterpolation.value).to.be.equals(" message "); }) - /** - * @TODO not support parse WXInterpolation in "" string yet - */ // #2 termplate attr // + it("can parse WXInterpolation - literal template", () => { + const ast = parse(` + + `); + expect(ast.errors).to.be.lengthOf(0); + const matches = esquery(ast, "WXInterpolation"); + expect(matches).to.be.lengthOf(1); + const intp = _.get(matches, '[0]'); + expect(intp).to.have.property("value"); + expect(intp).to.have.property("rawValue"); + expect(intp.rawValue).to.be.equals("{{id}}"); + expect(intp.value).to.be.equals("id"); + // raw WXAttribute is also exist + const matches2 = esquery(ast, "WXAttribute"); + expect(matches2).to.be.lengthOf(1); + const wxAttr = matches2[0]; + expect(wxAttr).to.have.property("value"); + expect(wxAttr).to.have.property("rawValue"); + expect(wxAttr.rawValue).to.be.equals("\"item-{{id}}\""); + expect(wxAttr.value).to.be.equals("item-{{id}}"); + }) - /** - * @TODO not support parse WXInterpolation in "" string yet - */ // #3 condition // + it("can parse WXInterpolation - condition", () => { + const ast = parse(` + + `); + expect(ast.errors).to.be.lengthOf(0); + const matches = esquery(ast, "WXInterpolation"); + expect(matches).to.be.lengthOf(1); + const intp = _.get(matches, '[0]'); + expect(intp).to.have.property("value"); + expect(intp).to.have.property("rawValue"); + expect(intp.rawValue).to.be.equals("{{condition}}"); + expect(intp.value).to.be.equals("condition"); + }) - /** - * @TODO not support parse WXInterpolation in "" string yet - */ // #4 keyword // + it("can parse WXInterpolation - keyword", () => { + const ast = parse(` + + `); + expect(ast.errors).to.be.lengthOf(0); + const matches = esquery(ast, "WXInterpolation"); + expect(matches).to.be.lengthOf(1); + const intp = _.get(matches, '[0]'); + expect(intp).to.have.property("value"); + expect(intp).to.have.property("rawValue"); + expect(intp.rawValue).to.be.equals("{{false}}"); + expect(intp.value).to.be.equals("false"); + }) // #5 Ternary operation // @@ -75,23 +113,22 @@ describe("Interpolation Test Suite", () => { expect(wxInterpolation.value).to.be.equals("a + b"); }) - /** - * @TODO not support parse WXInterpolation in "" string yet - */ // #7 logic check // // - // it("can parse WXInterpolation - logic check", () => { - // const ast = parse(` - // - // `); - // expect(ast.errors).to.be.lengthOf(0); - // const matches = esquery(ast, "WXInterpolation"); - // expect(matches).to.be.lengthOf(1); - // const wxInterpolation = matches[0]; - // expect(wxInterpolation.rawValue).to.be.equals("{{length < 5}}"); - // expect(wxInterpolation.value).to.be.equals("length < 5"); - // }) + it("can parse WXInterpolation - logic check", () => { + const ast = parse(` + + `); + expect(ast.errors).to.be.lengthOf(0); + const matches = esquery(ast, "WXInterpolation"); + expect(matches).to.be.lengthOf(1); + const intp = _.get(matches, '[0]'); + expect(intp).to.have.property("value"); + expect(intp).to.have.property("rawValue"); + expect(intp.rawValue).to.be.equals("{{length < 5}}"); + expect(intp.value).to.be.equals("length < 5"); + }) // #8 string compute // {{"hello<>}}{{}}{{" + name}} @@ -124,20 +161,47 @@ describe("Interpolation Test Suite", () => { expect(wxInterpolation2.value).to.be.equals("array[0]"); }) - /** - * @TODO not support parse WXInterpolation in "" string yet - */ // #10 combine - array // {{item}} + it("can parse WXInterpolation - combine array", () => { + const ast = parse(` + {{item}} + `); + expect(ast.errors).to.be.lengthOf(0); + const matches = esquery(ast, "WXInterpolation"); + expect(matches).to.be.lengthOf(2); + const intp = _.get(matches, '[1]'); + expect(intp).to.have.property("value"); + expect(intp).to.have.property("rawValue"); + expect(intp.rawValue).to.be.equals("{{[zero, 1, 2, 3, 4]}}"); + expect(intp.value).to.be.equals("[zero, 1, 2, 3, 4]"); + }) - /** - * @TODO not support parse WXInterpolation in "" string yet - */ // #11 combine - object // // // - // + it("can parse WXInterpolation - combine object", () => { + const ast = parse(` + + + + `); + expect(ast.errors).to.be.lengthOf(0); + const matches = esquery(ast, "WXAttribute"); + expect(matches[1].interpolations).to.be.lengthOf(1); + const intp = _.get(matches, '[1].interpolations[0]'); + const intp2 = _.get(matches, '[3].interpolations[0]'); + const intp3 = _.get(matches, '[5].interpolations[0]'); + expect(intp).to.have.property("value"); + expect(intp).to.have.property("rawValue"); + expect(intp.rawValue).to.be.equals("{{for: a, bar: b}}"); + expect(intp.value).to.be.equals("for: a, bar: b"); + expect(intp2.rawValue).to.be.equals("{{foo, bar}}"); + expect(intp2.value).to.be.equals("foo, bar"); + expect(intp3.rawValue).to.be.equals("{{...obj1, ...obj2, e: 5}}"); + expect(intp3.value).to.be.equals("...obj1, ...obj2, e: 5"); + }) // #12 multi line it("can parse WXInterpolation - multi line", () => { @@ -153,4 +217,18 @@ describe("Interpolation Test Suite", () => { expect(matches).to.be.lengthOf(1); }) + // #12 WXAttributeInterpolation count + it("can parse WXAttributeInterpolation - count correct", () => { + const ast = parse(` + + {{i}}-{{j}} + + `); + expect(ast.errors).to.be.lengthOf(0); + const matches = esquery(ast, "WXInterpolation"); + expect(matches).to.be.lengthOf(4); + const matches2 = esquery(ast, "WXAttributeInterpolation"); + expect(matches2).to.be.lengthOf(2); + }) + }); diff --git a/tests/wxs-spec.js b/tests/wxs-spec.js index 7556f6e..c4ce889 100644 --- a/tests/wxs-spec.js +++ b/tests/wxs-spec.js @@ -123,4 +123,25 @@ describe('WXS Test Suite', () => { expect(matchs[0].name).to.be.equals("wxs"); }); + it("WXSxript parse success when follow WXInterpolation", () => { + const ast = parse(` + {{100}} + var s = 22; + `) + + expect(ast.errors.length).to.be.equals(0); + const matchs = esquery(ast, "WXScript"); + expect(matchs[0].name).to.be.equals("wxs"); + }); + + it("WXSxript parse success when follow WXText", () => { + const ast = parse(` + text + var s = 22; + `) + + expect(ast.errors.length).to.be.equals(0); + const matchs = esquery(ast, "WXScript"); + expect(matchs[0].name).to.be.equals("wxs"); + }); })