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