From a6b207d1e1676d68eabefa861963c129d3d480da Mon Sep 17 00:00:00 2001 From: James Meng Date: Mon, 2 Dec 2024 15:31:18 -0800 Subject: [PATCH] Refactor: create new grammar for doc tags LiquidDoc is essentially a new language that we are adding. Creating a new grammar allows us to directly parse and test the contents of the doc tag. This also allows us to avoid the complexity of including liquid support from within the doc tag grammar. --- .../grammar/liquid-html.ohm | 6 ++- packages/liquid-html-parser/src/grammar.ts | 1 + .../src/stage-1-cst.spec.ts | 16 ++++---- .../liquid-html-parser/src/stage-1-cst.ts | 39 +++++++++---------- .../src/stage-2-ast.spec.ts | 8 +++- 5 files changed, 40 insertions(+), 30 deletions(-) diff --git a/packages/liquid-html-parser/grammar/liquid-html.ohm b/packages/liquid-html-parser/grammar/liquid-html.ohm index 3d806237..6b1c4045 100644 --- a/packages/liquid-html-parser/grammar/liquid-html.ohm +++ b/packages/liquid-html-parser/grammar/liquid-html.ohm @@ -376,7 +376,11 @@ LiquidStatement <: Liquid { liquidStatementEnd = newline | end delimTag := liquidStatementEnd -} +} + +LiquidDoc <: Helpers { + Node := (TextNode)* +} LiquidHTML <: Liquid { Node := yamlFrontmatter? (HtmlNode | liquidNode | TextNode)* diff --git a/packages/liquid-html-parser/src/grammar.ts b/packages/liquid-html-parser/src/grammar.ts index ab673506..79675ad7 100644 --- a/packages/liquid-html-parser/src/grammar.ts +++ b/packages/liquid-html-parser/src/grammar.ts @@ -3,6 +3,7 @@ import ohm from 'ohm-js'; export const liquidHtmlGrammars = ohm.grammars(require('../grammar/liquid-html.ohm.js')); export const TextNodeGrammar = liquidHtmlGrammars['Helpers']; +export const LiquidDocGrammar = liquidHtmlGrammars['LiquidDoc']; export interface LiquidGrammars { Liquid: ohm.Grammar; 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 3d366609..71ae05a6 100644 --- a/packages/liquid-html-parser/src/stage-1-cst.spec.ts +++ b/packages/liquid-html-parser/src/stage-1-cst.spec.ts @@ -994,13 +994,15 @@ describe('Unit: Stage 1 (CST)', () => { 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: 35, - locStart: 11, - source: '{% doc -%} Renders loading-spinner. {%- enddoc %}', - type: 'LiquidDocBody', - description: 'Renders loading-spinner.', - }); + expectPath(cst, '0.children').to.deep.equal([ + { + locEnd: 25, + locStart: 1, + source: '{% doc -%} Renders loading-spinner. {%- enddoc %}', + type: 'TextNode', + value: 'Renders loading-spinner.', + }, + ]); } }); diff --git a/packages/liquid-html-parser/src/stage-1-cst.ts b/packages/liquid-html-parser/src/stage-1-cst.ts index b158fb66..2bb9bfa8 100644 --- a/packages/liquid-html-parser/src/stage-1-cst.ts +++ b/packages/liquid-html-parser/src/stage-1-cst.ts @@ -34,6 +34,7 @@ import { Parser } from 'prettier'; import ohm, { Node } from 'ohm-js'; import { toAST } from 'ohm-js/extras'; import { + LiquidDocGrammar, LiquidGrammars, TextNodeGrammar, placeholderGrammars, @@ -81,7 +82,6 @@ export enum ConcreteNodeTypes { RenderMarkup = 'RenderMarkup', PaginateMarkup = 'PaginateMarkup', RenderVariableExpression = 'RenderVariableExpression', - LiquidDocBody = 'LiquidDocBody', } export const LiquidLiteralValues = { @@ -504,13 +504,7 @@ function toCST( source: string /* the original file */, grammars: LiquidGrammars, grammar: ohm.Grammar, - cstMappings: ( - | 'HelperMappings' - | 'LiquidMappings' - | 'LiquidHTMLMappings' - | 'LiquidStatement' - | 'LiquidDocMappings' - )[], + cstMappings: ('HelperMappings' | 'LiquidMappings' | 'LiquidHTMLMappings' | 'LiquidStatement')[], matchingSource: string = source /* for subtree parsing */, offset: number = 0 /* for subtree parsing location offsets */, ): T { @@ -638,11 +632,8 @@ function toCST( body: (tokens: Node[]) => tokens[1].sourceString, children: (tokens: Node[]) => { const contentNode = tokens[1]; - return toCST( + return toLiquidDocAST( source, - grammars, - grammars.Liquid, - ['LiquidDocMappings'], contentNode.sourceString, offset + contentNode.source.startIdx, ); @@ -1108,15 +1099,24 @@ function toCST( }; const LiquidDocMappings: Mapping = { - Node: { - type: ConcreteNodeTypes.LiquidDocBody, - locStart, - locEnd, - source, - description: (tokens: Node[]) => tokens[0].sourceString, - }, + Node: 0, + TextNode: textNode, }; + function toLiquidDocAST(source: string, matchingSource: string, offset: number) { + const res = LiquidDocGrammar.match(matchingSource, 'Node'); + if (res.failed()) { + throw new LiquidHTMLCSTParsingError(res); + } + + const LiquidDocMappings: Mapping = { + Node: 0, + TextNode: textNode, + }; + + return toAST(res, LiquidDocMappings); + } + const LiquidHTMLMappings: Mapping = { Node(frontmatter: Node, nodes: Node) { const self = this as any; @@ -1272,7 +1272,6 @@ function toCST( LiquidMappings, LiquidHTMLMappings, LiquidStatement, - LiquidDocMappings, }; const selectedMappings = cstMappings.reduce( 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 a2310496..d88a557e 100644 --- a/packages/liquid-html-parser/src/stage-2-ast.spec.ts +++ b/packages/liquid-html-parser/src/stage-2-ast.spec.ts @@ -1225,11 +1225,14 @@ describe('Unit: Stage 2 (AST)', () => { expectPath(ast, 'children.0.type').to.eql('LiquidRawTag'); expectPath(ast, 'children.0.name').to.eql('doc'); expectPath(ast, 'children.0.body.value').to.eql(''); + expectPath(ast, 'children.0.body.type').toEqual('RawMarkup'); + expectPath(ast, 'children.0.body.nodes').toEqual([]); 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 '); + expectPath(ast, 'children.0.body.nodes.0.type').toEqual('TextNode'); ast = toLiquidAST(`{% doc -%} multi line doc @@ -1237,9 +1240,10 @@ describe('Unit: Stage 2 (AST)', () => { {%- 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 %}', + expectPath(ast, 'children.0.body.nodes.0.value').to.eql( + `multi line doc\n multi line doc`, ); + expectPath(ast, 'children.0.body.nodes.0.type').toEqual('TextNode'); }); it('should parse unclosed tables with assignments', () => {