diff --git a/packages/liquid-html-parser/grammar/liquid-html.ohm b/packages/liquid-html-parser/grammar/liquid-html.ohm index a431a97be..4a82668b0 100644 --- a/packages/liquid-html-parser/grammar/liquid-html.ohm +++ b/packages/liquid-html-parser/grammar/liquid-html.ohm @@ -37,6 +37,7 @@ Liquid <: Helpers { endOfIdentifier = endOfTagName | endOfVarName liquidNode = + | liquidDoc | liquidBlockComment | liquidRawTag | liquidDrop @@ -210,6 +211,13 @@ Liquid <: Helpers { commentBlockEnd commentBlockStart = "{%" "-"? space* ("comment" endOfIdentifier) space* tagMarkup "-"? "%}" commentBlockEnd = "{%" "-"? space* ("endcomment" endOfIdentifier) space* tagMarkup "-"? "%}" + + liquidDoc = + liquidDocStart + anyExceptStar<(liquidDocStart|liquidDocEnd)> + liquidDocEnd + liquidDocStart = "{%" "-"? space* ("doc" endOfIdentifier) space* tagMarkup "-"? "%}" + liquidDocEnd = "{%" "-"? space* ("enddoc" endOfIdentifier) space* tagMarkup "-"? "%}" // In order for the grammar to "fallback" to the base case, this // rule must pass if and only if we support what we parse. This @@ -366,7 +374,7 @@ LiquidStatement <: Liquid { liquidStatementEnd = newline | end delimTag := liquidStatementEnd -} +} LiquidHTML <: Liquid { Node := yamlFrontmatter? (HtmlNode | liquidNode | TextNode)* diff --git a/packages/liquid-html-parser/src/stage-1-cst.spec.ts b/packages/liquid-html-parser/src/stage-1-cst.spec.ts index 147916b23..217282338 100644 --- a/packages/liquid-html-parser/src/stage-1-cst.spec.ts +++ b/packages/liquid-html-parser/src/stage-1-cst.spec.ts @@ -978,6 +978,33 @@ describe('Unit: Stage 1 (CST)', () => { } }); + it('should parse doc tags', () => { + for (const { toCST, expectPath } of testCases) { + const testStr = '{% doc -%} single line doc {%- enddoc %}'; + cst = toCST(testStr); + expectPath(cst, '0.type').to.equal('LiquidRawTag'); + expectPath(cst, '0.name').to.equal('doc'); + expectPath(cst, '0.body').to.equal(' single line doc '); + expectPath(cst, '0.whitespaceStart').to.equal(''); + expectPath(cst, '0.whitespaceEnd').to.equal('-'); + expectPath(cst, '0.delimiterWhitespaceStart').to.equal('-'); + expectPath(cst, '0.delimiterWhitespaceEnd').to.equal(''); + expectPath(cst, '0.blockStartLocStart').to.equal(0); + expectPath(cst, '0.blockStartLocEnd').to.equal(0 + '{% doc -%}'.length); + expectPath(cst, '0.blockEndLocStart').to.equal(testStr.length - '{%- enddoc %}'.length); + expectPath(cst, '0.blockEndLocEnd').to.equal(testStr.length); + expectPath(cst, '0.children').to.deep.equal([ + { + locEnd: 26, + locStart: 11, + source: '{% doc -%} single line doc {%- enddoc %}', + type: 'TextNode', + value: 'single line doc', + }, + ]); + } + }); + it('should parse tag open / close', () => { BLOCKS.forEach((block: string) => { for (const { toCST, expectPath } of testCases) { diff --git a/packages/liquid-html-parser/src/stage-1-cst.ts b/packages/liquid-html-parser/src/stage-1-cst.ts index 36c2ab853..323a66e25 100644 --- a/packages/liquid-html-parser/src/stage-1-cst.ts +++ b/packages/liquid-html-parser/src/stage-1-cst.ts @@ -625,6 +625,32 @@ function toCST( blockEndLocStart: (tokens: Node[]) => tokens[2].source.startIdx, blockEndLocEnd: (tokens: Node[]) => tokens[2].source.endIdx, }, + liquidDoc: { + type: ConcreteNodeTypes.LiquidRawTag, + name: 'doc', + body: (tokens: Node[]) => tokens[1].sourceString, + children: (tokens: Node[]) => { + return toCST( + source, + grammars, + TextNodeGrammar, + ['HelperMappings'], + tokens[1].sourceString, + offset + tokens[1].source.startIdx, + ); + }, + whitespaceStart: (tokens: Node[]) => tokens[0].children[1].sourceString, + whitespaceEnd: (tokens: Node[]) => tokens[0].children[7].sourceString, + delimiterWhitespaceStart: (tokens: Node[]) => tokens[2].children[1].sourceString, + delimiterWhitespaceEnd: (tokens: Node[]) => tokens[2].children[7].sourceString, + locStart, + locEnd, + source, + blockStartLocStart: (tokens: Node[]) => tokens[0].source.startIdx, + blockStartLocEnd: (tokens: Node[]) => tokens[0].source.endIdx, + blockEndLocStart: (tokens: Node[]) => tokens[2].source.startIdx, + blockEndLocEnd: (tokens: Node[]) => tokens[2].source.endIdx, + }, liquidInlineComment: { type: ConcreteNodeTypes.LiquidTag, name: 3, diff --git a/packages/liquid-html-parser/src/stage-2-ast.spec.ts b/packages/liquid-html-parser/src/stage-2-ast.spec.ts index de6de2631..a23104964 100644 --- a/packages/liquid-html-parser/src/stage-2-ast.spec.ts +++ b/packages/liquid-html-parser/src/stage-2-ast.spec.ts @@ -1220,6 +1220,28 @@ describe('Unit: Stage 2 (AST)', () => { expectPath(ast, 'children.0.markup.1.children.0.children.1.markup.name').to.eql('var3'); }); + it(`should parse doc tags`, () => { + ast = toLiquidAST(`{% doc %}{% enddoc %}`); + expectPath(ast, 'children.0.type').to.eql('LiquidRawTag'); + expectPath(ast, 'children.0.name').to.eql('doc'); + expectPath(ast, 'children.0.body.value').to.eql(''); + + ast = toLiquidAST(`{% doc -%} single line doc {%- enddoc %}`); + expectPath(ast, 'children.0.type').to.eql('LiquidRawTag'); + expectPath(ast, 'children.0.name').to.eql('doc'); + expectPath(ast, 'children.0.body.value').to.eql(' single line doc '); + + ast = toLiquidAST(`{% doc -%} + multi line doc + multi line doc + {%- enddoc %}`); + expectPath(ast, 'children.0.type').to.eql('LiquidRawTag'); + expectPath(ast, 'children.0.name').to.eql('doc'); + expectPath(ast, 'children.0.body.source').to.eql( + '{% doc -%}\n multi line doc\n multi line doc\n {%- enddoc %}', + ); + }); + it('should parse unclosed tables with assignments', () => { ast = toLiquidAST(` {%- liquid