diff --git a/packages/liquid-html-parser/grammar/liquid-html.ohm b/packages/liquid-html-parser/grammar/liquid-html.ohm index 5d6481c26..7cb62f317 100644 --- a/packages/liquid-html-parser/grammar/liquid-html.ohm +++ b/packages/liquid-html-parser/grammar/liquid-html.ohm @@ -389,7 +389,14 @@ LiquidStatement <: Liquid { } LiquidDoc <: Helpers { - Node := (TextNode)* + Node := (LiquidDocNode | TextNode)* + LiquidDocNode = + | paramNode + | fallbackNode + + fallbackNode = "@" anyExceptStar + paramNode = "@param" space* paramNodeName + paramNodeName = anyExceptStar } LiquidHTML <: Liquid { 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 7f02c68a8..b5f21991a 100644 --- a/packages/liquid-html-parser/src/stage-1-cst.spec.ts +++ b/packages/liquid-html-parser/src/stage-1-cst.spec.ts @@ -984,12 +984,16 @@ describe('Unit: Stage 1 (CST)', () => { it('should parse doc tags', () => { for (const { toCST, expectPath } of testCases) { - const testStr = `{% doc -%} Renders loading-spinner. {%- enddoc %}`; + const testStr = `{% doc -%} + @param asdf + @unsupported + {%- enddoc %}`; cst = toCST(testStr); + expectPath(cst, '0.type').to.equal('LiquidRawTag'); expectPath(cst, '0.name').to.equal('doc'); - expectPath(cst, '0.body').to.include('Renders loading-spinner'); + expectPath(cst, '0.body').to.include('@param asdf'); expectPath(cst, '0.whitespaceStart').to.equal(''); expectPath(cst, '0.whitespaceEnd').to.equal('-'); expectPath(cst, '0.delimiterWhitespaceStart').to.equal('-'); @@ -998,15 +1002,16 @@ 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: 'TextNode', - value: 'Renders loading-spinner.', - }, - ]); + + expectPath(cst, '0.children.0.type').to.equal('LiquidDocParamNode'); + expectPath(cst, '0.children.0.locStart').to.equal(testStr.indexOf('@param')); + expectPath(cst, '0.children.0.locEnd').to.equal(testStr.indexOf('asdf') + 'asdf'.length); + + expectPath(cst, '0.children.1.type').to.equal('TextNode'); + expectPath(cst, '0.children.1.locStart').to.equal(testStr.indexOf('@unsupported')); + expectPath(cst, '0.children.1.locEnd').to.equal( + testStr.indexOf('@unsupported') + '@unsupported'.length, + ); } }); diff --git a/packages/liquid-html-parser/src/stage-1-cst.ts b/packages/liquid-html-parser/src/stage-1-cst.ts index 4fdeffb8c..40b0d12cb 100644 --- a/packages/liquid-html-parser/src/stage-1-cst.ts +++ b/packages/liquid-html-parser/src/stage-1-cst.ts @@ -83,6 +83,8 @@ export enum ConcreteNodeTypes { PaginateMarkup = 'PaginateMarkup', RenderVariableExpression = 'RenderVariableExpression', ContentForNamedArgument = 'ContentForNamedArgument', + + LiquidDocParamNode = 'LiquidDocParamNode', } export const LiquidLiteralValues = { @@ -105,6 +107,12 @@ export interface ConcreteBasicNode { locEnd: number; } +export interface ConcreteLiquidDocParamNode + extends ConcreteBasicNode { + name: string; + value: string; +} + export interface ConcreteHtmlNodeBase extends ConcreteBasicNode { attrList?: ConcreteAttributeNode[]; } @@ -440,10 +448,13 @@ export type LiquidConcreteNode = | ConcreteTextNode | ConcreteYamlFrontmatterNode; -export type LiquidHtmlCST = LiquidHtmlConcreteNode[]; +export type LiquidHtmlCST = LiquidHtmlConcreteNode[] | LiquidDocCST; export type LiquidCST = LiquidConcreteNode[]; +type LiquidDocCST = LiquidDocConcreteNode[]; +export type LiquidDocConcreteNode = ConcreteLiquidDocParamNode; + interface Mapping { [k: string]: number | TemplateMapping | TopLevelFunctionMapping; } @@ -1306,7 +1317,24 @@ function toLiquidDocAST(source: string, matchingSource: string, offset: number) const LiquidDocMappings: Mapping = { Node: 0, - TextNode: { + textNode: { + type: ConcreteNodeTypes.TextNode, + value: function () { + return (this as any).sourceString; + }, + locStart, + locEnd, + source, + }, + paramNode: { + type: ConcreteNodeTypes.LiquidDocParamNode, + name: 0, + value: 2, + locStart, + locEnd, + source, + }, + fallbackNode: { type: ConcreteNodeTypes.TextNode, value: function () { return (this as any).sourceString; 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 84848ccb2..ed1c2467f 100644 --- a/packages/liquid-html-parser/src/stage-2-ast.spec.ts +++ b/packages/liquid-html-parser/src/stage-2-ast.spec.ts @@ -1229,22 +1229,21 @@ describe('Unit: Stage 2 (AST)', () => { expectPath(ast, 'children.0.body.type').toEqual('RawMarkup'); expectPath(ast, 'children.0.body.nodes').toEqual([]); - ast = toLiquidAST(`{% doc -%} single line doc {%- enddoc %}`); + ast = toLiquidAST(` + {% doc -%} + @param asdf + @unsupported this node falls back to a text node + {%- 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'); + expectPath(ast, 'children.0.body.nodes.0.type').to.eql('LiquidDocParamNode'); + expectPath(ast, 'children.0.body.nodes.0.name').to.eql('@param'); - 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.nodes.0.value').to.eql( - `multi line doc\n multi line doc`, + expectPath(ast, 'children.0.body.nodes.1.type').to.eql('TextNode'); + expectPath(ast, 'children.0.body.nodes.1.value').to.eql( + '@unsupported this node falls back to a text node', ); - expectPath(ast, 'children.0.body.nodes.0.type').toEqual('TextNode'); }); it('should parse unclosed tables with assignments', () => { diff --git a/packages/liquid-html-parser/src/stage-2-ast.ts b/packages/liquid-html-parser/src/stage-2-ast.ts index 15c7e61cb..a89defbea 100644 --- a/packages/liquid-html-parser/src/stage-2-ast.ts +++ b/packages/liquid-html-parser/src/stage-2-ast.ts @@ -107,7 +107,8 @@ export type LiquidHtmlNode = | RenderVariableExpression | LiquidLogicalExpression | LiquidComparison - | TextNode; + | TextNode + | LiquidDocParamNode; /** The root node of all LiquidHTML ASTs. */ export interface DocumentNode extends ASTNode { @@ -754,6 +755,11 @@ export interface TextNode extends ASTNode { value: string; } +export interface LiquidDocParamNode extends ASTNode { + name: string; + value: string; +} + export interface ASTNode { /** * The type of the node, as a string. @@ -1268,6 +1274,17 @@ function buildAst( break; } + case ConcreteNodeTypes.LiquidDocParamNode: { + builder.push({ + type: NodeTypes.LiquidDocParamNode, + name: node.name, + position: position(node), + source: node.source, + value: node.value, + }); + break; + } + default: { assertNever(node); } diff --git a/packages/liquid-html-parser/src/types.ts b/packages/liquid-html-parser/src/types.ts index 29648aebb..49efa1bf3 100644 --- a/packages/liquid-html-parser/src/types.ts +++ b/packages/liquid-html-parser/src/types.ts @@ -44,6 +44,7 @@ export enum NodeTypes { RawMarkup = 'RawMarkup', RenderMarkup = 'RenderMarkup', RenderVariableExpression = 'RenderVariableExpression', + LiquidDocParamNode = 'LiquidDocParamNode', } // These are officially supported with special node types diff --git a/packages/prettier-plugin-liquid/src/printer/preprocess/augment-with-css-properties.ts b/packages/prettier-plugin-liquid/src/printer/preprocess/augment-with-css-properties.ts index cc16285e5..4ad565c6d 100644 --- a/packages/prettier-plugin-liquid/src/printer/preprocess/augment-with-css-properties.ts +++ b/packages/prettier-plugin-liquid/src/printer/preprocess/augment-with-css-properties.ts @@ -128,6 +128,7 @@ function getCssDisplay(node: AugmentedNode, options: LiquidParserO case NodeTypes.RenderVariableExpression: case NodeTypes.LogicalExpression: case NodeTypes.Comparison: + case NodeTypes.LiquidDocParamNode: return 'should not be relevant'; default: @@ -233,6 +234,7 @@ function getNodeCssStyleWhiteSpace( case NodeTypes.RenderVariableExpression: case NodeTypes.LogicalExpression: case NodeTypes.Comparison: + case NodeTypes.LiquidDocParamNode: return 'should not be relevant'; default: diff --git a/packages/prettier-plugin-liquid/src/printer/print/liquid.ts b/packages/prettier-plugin-liquid/src/printer/print/liquid.ts index 778c7262e..ad1ff03bc 100644 --- a/packages/prettier-plugin-liquid/src/printer/print/liquid.ts +++ b/packages/prettier-plugin-liquid/src/printer/print/liquid.ts @@ -1,4 +1,4 @@ -import { NodeTypes, NamedTags, isBranchedTag } from '@shopify/liquid-html-parser'; +import { NodeTypes, NamedTags, isBranchedTag, RawMarkup } from '@shopify/liquid-html-parser'; import { Doc, doc } from 'prettier'; import { @@ -490,6 +490,16 @@ export function printLiquidRawTag( return [blockStart, ...body, blockEnd]; } +export function printLiquidDoc( + path: AstPath, + _options: LiquidParserOptions, + print: LiquidPrinter, + _args: LiquidPrinterArgs, +) { + const body = path.map((p: any) => print(p), 'nodes'); + return [indent([hardline, body]), hardline]; +} + function innerLeadingWhitespace(node: LiquidTag | LiquidBranch) { if (!node.firstChild) { if (node.isDanglingWhitespaceSensitive && node.hasDanglingWhitespace) { diff --git a/packages/prettier-plugin-liquid/src/printer/printer-liquid-html.ts b/packages/prettier-plugin-liquid/src/printer/printer-liquid-html.ts index 3905c8008..92797d8c6 100644 --- a/packages/prettier-plugin-liquid/src/printer/printer-liquid-html.ts +++ b/packages/prettier-plugin-liquid/src/printer/printer-liquid-html.ts @@ -1,5 +1,6 @@ import { getConditionalComment, + LiquidDocParamNode, NodeTypes, Position, RawMarkupKinds, @@ -30,6 +31,7 @@ import { LiquidTag, LiquidVariableOutput, nonTraversableProperties, + RawMarkup, TextNode, } from '../types'; import { assertNever } from '../utils'; @@ -40,6 +42,7 @@ import { printChildren } from './print/children'; import { printElement } from './print/element'; import { printLiquidBranch, + printLiquidDoc, printLiquidRawTag, printLiquidTag, printLiquidVariableOutput, @@ -210,6 +213,10 @@ function printNode( } case NodeTypes.RawMarkup: { + if (node.parentNode?.name === 'doc') { + return printLiquidDoc(path as AstPath, options, print, args); + } + const isRawMarkupIdentationSensitive = () => { switch (node.kind) { case RawMarkupKinds.typescript: @@ -547,6 +554,10 @@ function printNode( return [...doc, ...lookups]; } + case NodeTypes.LiquidDocParamNode: { + return [node.name, ' ', node.value]; + } + default: { return assertNever(node); } diff --git a/packages/prettier-plugin-liquid/src/test/liquid-doc/fixed.liquid b/packages/prettier-plugin-liquid/src/test/liquid-doc/fixed.liquid new file mode 100644 index 000000000..c11d85f6f --- /dev/null +++ b/packages/prettier-plugin-liquid/src/test/liquid-doc/fixed.liquid @@ -0,0 +1,9 @@ +It should indent the body +{% doc %} + @param body +{% enddoc %} + +It should not dedent to root +{% doc %} + @param body +{% enddoc %} diff --git a/packages/prettier-plugin-liquid/src/test/liquid-doc/index.liquid b/packages/prettier-plugin-liquid/src/test/liquid-doc/index.liquid new file mode 100644 index 000000000..45638eb76 --- /dev/null +++ b/packages/prettier-plugin-liquid/src/test/liquid-doc/index.liquid @@ -0,0 +1,9 @@ +It should indent the body +{% doc %} +@param body +{% enddoc %} + +It should not dedent to root +{% doc %} + @param body +{% enddoc %} diff --git a/packages/prettier-plugin-liquid/src/test/liquid-doc/index.spec.ts b/packages/prettier-plugin-liquid/src/test/liquid-doc/index.spec.ts new file mode 100644 index 000000000..bf0e16d16 --- /dev/null +++ b/packages/prettier-plugin-liquid/src/test/liquid-doc/index.spec.ts @@ -0,0 +1,7 @@ +import { test } from 'vitest'; +import { assertFormattedEqualsFixed } from '../test-helpers'; +import * as path from 'path'; + +test('Unit: liquid-doc', async () => { + await assertFormattedEqualsFixed(__dirname); +}); diff --git a/packages/theme-language-server-common/src/completions/params/LiquidCompletionParams.ts b/packages/theme-language-server-common/src/completions/params/LiquidCompletionParams.ts index 75d6e1e75..9ed9bcaf0 100644 --- a/packages/theme-language-server-common/src/completions/params/LiquidCompletionParams.ts +++ b/packages/theme-language-server-common/src/completions/params/LiquidCompletionParams.ts @@ -402,7 +402,8 @@ function findCurrentNode( case NodeTypes.TextNode: case NodeTypes.LiquidLiteral: case NodeTypes.String: - case NodeTypes.Number: { + case NodeTypes.Number: + case NodeTypes.LiquidDocParamNode: { break; }