Skip to content

Commit 450083b

Browse files
committed
Add liquidDoc tag to liquidHTMLParser
Add stage-2 ast tests for doc tags
1 parent 5e8a2bf commit 450083b

File tree

6 files changed

+129
-1
lines changed

6 files changed

+129
-1
lines changed

packages/liquid-html-parser/grammar/liquid-html.ohm

+14
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Liquid <: Helpers {
3737
endOfIdentifier = endOfTagName | endOfVarName
3838

3939
liquidNode =
40+
| liquidDoc
4041
| liquidBlockComment
4142
| liquidRawTag
4243
| liquidDrop
@@ -211,6 +212,15 @@ Liquid <: Helpers {
211212
commentBlockStart = "{%" "-"? space* ("comment" endOfIdentifier) space* tagMarkup "-"? "%}"
212213
commentBlockEnd = "{%" "-"? space* ("endcomment" endOfIdentifier) space* tagMarkup "-"? "%}"
213214

215+
liquidDoc =
216+
liquidDocStart
217+
liquidDocBody
218+
liquidDocEnd
219+
220+
liquidDocStart = "{%" "-"? space* ("doc" endOfIdentifier) space* tagMarkup "-"? "%}"
221+
liquidDocEnd = "{%" "-"? space* ("enddoc" endOfIdentifier) space* tagMarkup "-"? "%}"
222+
liquidDocBody = anyExceptStar<(liquidDocStart | liquidDocEnd)>
223+
214224
// In order for the grammar to "fallback" to the base case, this
215225
// rule must pass if and only if we support what we parse. This
216226
// implies that—since we don't support filters yet—we have a
@@ -373,6 +383,10 @@ LiquidStatement <: Liquid {
373383
delimTag := liquidStatementEnd
374384
}
375385

386+
LiquidDoc <: Helpers {
387+
Node := (TextNode)*
388+
}
389+
376390
LiquidHTML <: Liquid {
377391
Node := yamlFrontmatter? (HtmlNode | liquidNode | TextNode)*
378392
openControl += "<"

packages/liquid-html-parser/src/grammar.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ohm from 'ohm-js';
33
export const liquidHtmlGrammars = ohm.grammars(require('../grammar/liquid-html.ohm.js'));
44

55
export const TextNodeGrammar = liquidHtmlGrammars['Helpers'];
6+
export const LiquidDocGrammar = liquidHtmlGrammars['LiquidDoc'];
67

78
export interface LiquidGrammars {
89
Liquid: ohm.Grammar;
@@ -52,4 +53,5 @@ export const TAGS_WITHOUT_MARKUP = [
5253
'continue',
5354
'comment',
5455
'raw',
56+
'doc',
5557
];

packages/liquid-html-parser/src/stage-1-cst.spec.ts

+28
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,34 @@ describe('Unit: Stage 1 (CST)', () => {
978978
}
979979
});
980980

981+
it('should parse doc tags', () => {
982+
for (const { toCST, expectPath } of testCases) {
983+
const testStr = `{% doc -%} Renders loading-spinner. {%- enddoc %}`;
984+
985+
cst = toCST(testStr);
986+
expectPath(cst, '0.type').to.equal('LiquidRawTag');
987+
expectPath(cst, '0.name').to.equal('doc');
988+
expectPath(cst, '0.body').to.include('Renders loading-spinner');
989+
expectPath(cst, '0.whitespaceStart').to.equal('');
990+
expectPath(cst, '0.whitespaceEnd').to.equal('-');
991+
expectPath(cst, '0.delimiterWhitespaceStart').to.equal('-');
992+
expectPath(cst, '0.delimiterWhitespaceEnd').to.equal('');
993+
expectPath(cst, '0.blockStartLocStart').to.equal(0);
994+
expectPath(cst, '0.blockStartLocEnd').to.equal(0 + '{% doc -%}'.length);
995+
expectPath(cst, '0.blockEndLocStart').to.equal(testStr.length - '{%- enddoc %}'.length);
996+
expectPath(cst, '0.blockEndLocEnd').to.equal(testStr.length);
997+
expectPath(cst, '0.children').to.deep.equal([
998+
{
999+
locEnd: 35,
1000+
locStart: 11,
1001+
source: '{% doc -%} Renders loading-spinner. {%- enddoc %}',
1002+
type: 'TextNode',
1003+
value: 'Renders loading-spinner.',
1004+
},
1005+
]);
1006+
}
1007+
});
1008+
9811009
it('should parse tag open / close', () => {
9821010
BLOCKS.forEach((block: string) => {
9831011
for (const { toCST, expectPath } of testCases) {

packages/liquid-html-parser/src/stage-1-cst.ts

+57
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { Parser } from 'prettier';
3434
import ohm, { Node } from 'ohm-js';
3535
import { toAST } from 'ohm-js/extras';
3636
import {
37+
LiquidDocGrammar,
3738
LiquidGrammars,
3839
TextNodeGrammar,
3940
placeholderGrammars,
@@ -625,6 +626,30 @@ function toCST<T>(
625626
blockEndLocStart: (tokens: Node[]) => tokens[2].source.startIdx,
626627
blockEndLocEnd: (tokens: Node[]) => tokens[2].source.endIdx,
627628
},
629+
liquidDoc: {
630+
type: ConcreteNodeTypes.LiquidRawTag,
631+
name: 'doc',
632+
body: (tokens: Node[]) => tokens[1].sourceString,
633+
children: (tokens: Node[]) => {
634+
const contentNode = tokens[1];
635+
return toLiquidDocAST(
636+
source,
637+
contentNode.sourceString,
638+
offset + contentNode.source.startIdx,
639+
);
640+
},
641+
whitespaceStart: (tokens: Node[]) => tokens[0].children[1].sourceString,
642+
whitespaceEnd: (tokens: Node[]) => tokens[0].children[7].sourceString,
643+
delimiterWhitespaceStart: (tokens: Node[]) => tokens[2].children[1].sourceString,
644+
delimiterWhitespaceEnd: (tokens: Node[]) => tokens[2].children[7].sourceString,
645+
locStart,
646+
locEnd,
647+
source,
648+
blockStartLocStart: (tokens: Node[]) => tokens[0].source.startIdx,
649+
blockStartLocEnd: (tokens: Node[]) => tokens[0].source.endIdx,
650+
blockEndLocStart: (tokens: Node[]) => tokens[2].source.startIdx,
651+
blockEndLocEnd: (tokens: Node[]) => tokens[2].source.endIdx,
652+
},
628653
liquidInlineComment: {
629654
type: ConcreteNodeTypes.LiquidTag,
630655
name: 3,
@@ -1251,3 +1276,35 @@ function toCST<T>(
12511276

12521277
return toAST(res, selectedMappings) as T;
12531278
}
1279+
1280+
/**
1281+
* Builds an AST for LiquidDoc content.
1282+
*
1283+
* `toCST` includes mappings and logic that are not needed for LiquidDoc so we're separating this logic
1284+
*/
1285+
function toLiquidDocAST(source: string, matchingSource: string, offset: number) {
1286+
// When we switch parser, our locStart and locEnd functions must account
1287+
// for the offset of the {% liquid %} markup
1288+
const locStart = (tokens: Node[]) => offset + tokens[0].source.startIdx;
1289+
const locEnd = (tokens: Node[]) => offset + tokens[tokens.length - 1].source.endIdx;
1290+
1291+
const res = LiquidDocGrammar.match(matchingSource, 'Node');
1292+
if (res.failed()) {
1293+
throw new LiquidHTMLCSTParsingError(res);
1294+
}
1295+
1296+
const LiquidDocMappings: Mapping = {
1297+
Node: 0,
1298+
TextNode: {
1299+
type: ConcreteNodeTypes.TextNode,
1300+
value: function () {
1301+
return (this as any).sourceString;
1302+
},
1303+
locStart,
1304+
locEnd,
1305+
source,
1306+
},
1307+
};
1308+
1309+
return toAST(res, LiquidDocMappings);
1310+
}

packages/liquid-html-parser/src/stage-2-ast.spec.ts

+27
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,33 @@ describe('Unit: Stage 2 (AST)', () => {
12201220
expectPath(ast, 'children.0.markup.1.children.0.children.1.markup.name').to.eql('var3');
12211221
});
12221222

1223+
it(`should parse doc tags`, () => {
1224+
ast = toLiquidAST(`{% doc %}{% enddoc %}`);
1225+
expectPath(ast, 'children.0.type').to.eql('LiquidRawTag');
1226+
expectPath(ast, 'children.0.name').to.eql('doc');
1227+
expectPath(ast, 'children.0.markup').toEqual('');
1228+
expectPath(ast, 'children.0.body.value').to.eql('');
1229+
expectPath(ast, 'children.0.body.type').toEqual('RawMarkup');
1230+
expectPath(ast, 'children.0.body.nodes').toEqual([]);
1231+
1232+
ast = toLiquidAST(`{% doc -%} single line doc {%- enddoc %}`);
1233+
expectPath(ast, 'children.0.type').to.eql('LiquidRawTag');
1234+
expectPath(ast, 'children.0.name').to.eql('doc');
1235+
expectPath(ast, 'children.0.body.value').to.eql(' single line doc ');
1236+
expectPath(ast, 'children.0.body.nodes.0.type').toEqual('TextNode');
1237+
1238+
ast = toLiquidAST(`{% doc -%}
1239+
multi line doc
1240+
multi line doc
1241+
{%- enddoc %}`);
1242+
expectPath(ast, 'children.0.type').to.eql('LiquidRawTag');
1243+
expectPath(ast, 'children.0.name').to.eql('doc');
1244+
expectPath(ast, 'children.0.body.nodes.0.value').to.eql(
1245+
`multi line doc\n multi line doc`,
1246+
);
1247+
expectPath(ast, 'children.0.body.nodes.0.type').toEqual('TextNode');
1248+
});
1249+
12231250
it('should parse unclosed tables with assignments', () => {
12241251
ast = toLiquidAST(`
12251252
{%- liquid

packages/theme-check-common/src/checks/liquid-html-syntax-error/index.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ describe('Module: LiquidHTMLSyntaxError', () => {
8686
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
8787
expect(offenses).to.have.length(1);
8888
expect(offenses[0].message).to.equal(
89-
`SyntaxError: expected "#", a letter, "when", "sections", "section", "render", "liquid", "layout", "increment", "include", "elsif", "else", "echo", "decrement", "content_for", "cycle", "continue", "break", "assign", "tablerow", "unless", "if", "ifchanged", "for", "case", "capture", "paginate", "form", "end", "style", "stylesheet", "schema", "javascript", "raw", or "comment"`,
89+
`SyntaxError: expected "#", a letter, "when", "sections", "section", "render", "liquid", "layout", "increment", "include", "elsif", "else", "echo", "decrement", "content_for", "cycle", "continue", "break", "assign", "tablerow", "unless", "if", "ifchanged", "for", "case", "capture", "paginate", "form", "end", "style", "stylesheet", "schema", "javascript", "raw", "comment", or "doc"`,
9090
);
9191
});
9292

0 commit comments

Comments
 (0)