Skip to content

Commit e7ed99d

Browse files
committed
Add Parsing + Prettier support for {% doc %} open and close tags
Implement fallbackNode for LiquidDoc Enhance Liquid HTML Parser: Introduce paramNode and update CST tests - Added `paramNode` to the grammar for LiquidDoc, allowing for parameter definitions in doc tags. - Updated the parsing logic to recognize and handle `@param` syntax correctly. - Modified CST tests to validate the new structure, ensuring proper identification of `LiquidDocParamNode` and associated text nodes. - Refactored existing tests to align with the new parsing rules and improve accuracy. Introduce LiquidDocCST and LiquidDocConcrete Add liquidDocParam handling to stage2 AST - Updated the `toLiquidDocAST` function to include handling for `@param` syntax and fallback text node Prettier - Add CSS Handling Enhance LiquidCompletionParams: Add handling for LiquidDocParamNode in LiquidCompletionParams Add LiquidCST to union of LiquidHTMLCST types Add LiquidDocParamNode case to print function Add basic prettier support for doc tag
1 parent 026da63 commit e7ed99d

File tree

13 files changed

+135
-29
lines changed

13 files changed

+135
-29
lines changed

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,14 @@ LiquidStatement <: Liquid {
389389
}
390390

391391
LiquidDoc <: Helpers {
392-
Node := (TextNode)*
392+
Node := (LiquidDocNode | TextNode)*
393+
LiquidDocNode =
394+
| paramNode
395+
| fallbackNode
396+
397+
fallbackNode = "@" anyExceptStar<newline>
398+
paramNode = "@param" space* paramNodeName
399+
paramNodeName = anyExceptStar<newline>
393400
}
394401

395402
LiquidHTML <: Liquid {

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

+16-11
Original file line numberDiff line numberDiff line change
@@ -984,12 +984,16 @@ describe('Unit: Stage 1 (CST)', () => {
984984

985985
it('should parse doc tags', () => {
986986
for (const { toCST, expectPath } of testCases) {
987-
const testStr = `{% doc -%} Renders loading-spinner. {%- enddoc %}`;
987+
const testStr = `{% doc -%}
988+
@param asdf
989+
@unsupported
990+
{%- enddoc %}`;
988991

989992
cst = toCST(testStr);
993+
990994
expectPath(cst, '0.type').to.equal('LiquidRawTag');
991995
expectPath(cst, '0.name').to.equal('doc');
992-
expectPath(cst, '0.body').to.include('Renders loading-spinner');
996+
expectPath(cst, '0.body').to.include('@param asdf');
993997
expectPath(cst, '0.whitespaceStart').to.equal('');
994998
expectPath(cst, '0.whitespaceEnd').to.equal('-');
995999
expectPath(cst, '0.delimiterWhitespaceStart').to.equal('-');
@@ -998,15 +1002,16 @@ describe('Unit: Stage 1 (CST)', () => {
9981002
expectPath(cst, '0.blockStartLocEnd').to.equal(0 + '{% doc -%}'.length);
9991003
expectPath(cst, '0.blockEndLocStart').to.equal(testStr.length - '{%- enddoc %}'.length);
10001004
expectPath(cst, '0.blockEndLocEnd').to.equal(testStr.length);
1001-
expectPath(cst, '0.children').to.deep.equal([
1002-
{
1003-
locEnd: 35,
1004-
locStart: 11,
1005-
source: '{% doc -%} Renders loading-spinner. {%- enddoc %}',
1006-
type: 'TextNode',
1007-
value: 'Renders loading-spinner.',
1008-
},
1009-
]);
1005+
1006+
expectPath(cst, '0.children.0.type').to.equal('LiquidDocParamNode');
1007+
expectPath(cst, '0.children.0.locStart').to.equal(testStr.indexOf('@param'));
1008+
expectPath(cst, '0.children.0.locEnd').to.equal(testStr.indexOf('asdf') + 'asdf'.length);
1009+
1010+
expectPath(cst, '0.children.1.type').to.equal('TextNode');
1011+
expectPath(cst, '0.children.1.locStart').to.equal(testStr.indexOf('@unsupported'));
1012+
expectPath(cst, '0.children.1.locEnd').to.equal(
1013+
testStr.indexOf('@unsupported') + '@unsupported'.length,
1014+
);
10101015
}
10111016
});
10121017

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

+30-2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ export enum ConcreteNodeTypes {
8383
PaginateMarkup = 'PaginateMarkup',
8484
RenderVariableExpression = 'RenderVariableExpression',
8585
ContentForNamedArgument = 'ContentForNamedArgument',
86+
87+
LiquidDocParamNode = 'LiquidDocParamNode',
8688
}
8789

8890
export const LiquidLiteralValues = {
@@ -105,6 +107,12 @@ export interface ConcreteBasicNode<T> {
105107
locEnd: number;
106108
}
107109

110+
export interface ConcreteLiquidDocParamNode
111+
extends ConcreteBasicNode<ConcreteNodeTypes.LiquidDocParamNode> {
112+
name: string;
113+
value: string;
114+
}
115+
108116
export interface ConcreteHtmlNodeBase<T> extends ConcreteBasicNode<T> {
109117
attrList?: ConcreteAttributeNode[];
110118
}
@@ -440,10 +448,13 @@ export type LiquidConcreteNode =
440448
| ConcreteTextNode
441449
| ConcreteYamlFrontmatterNode;
442450

443-
export type LiquidHtmlCST = LiquidHtmlConcreteNode[];
451+
export type LiquidHtmlCST = LiquidHtmlConcreteNode[] | LiquidDocCST;
444452

445453
export type LiquidCST = LiquidConcreteNode[];
446454

455+
type LiquidDocCST = LiquidDocConcreteNode[];
456+
export type LiquidDocConcreteNode = ConcreteLiquidDocParamNode;
457+
447458
interface Mapping {
448459
[k: string]: number | TemplateMapping | TopLevelFunctionMapping;
449460
}
@@ -1306,7 +1317,24 @@ function toLiquidDocAST(source: string, matchingSource: string, offset: number)
13061317

13071318
const LiquidDocMappings: Mapping = {
13081319
Node: 0,
1309-
TextNode: {
1320+
textNode: {
1321+
type: ConcreteNodeTypes.TextNode,
1322+
value: function () {
1323+
return (this as any).sourceString;
1324+
},
1325+
locStart,
1326+
locEnd,
1327+
source,
1328+
},
1329+
paramNode: {
1330+
type: ConcreteNodeTypes.LiquidDocParamNode,
1331+
name: 0,
1332+
value: 2,
1333+
locStart,
1334+
locEnd,
1335+
source,
1336+
},
1337+
fallbackNode: {
13101338
type: ConcreteNodeTypes.TextNode,
13111339
value: function () {
13121340
return (this as any).sourceString;

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

+11-12
Original file line numberDiff line numberDiff line change
@@ -1229,22 +1229,21 @@ describe('Unit: Stage 2 (AST)', () => {
12291229
expectPath(ast, 'children.0.body.type').toEqual('RawMarkup');
12301230
expectPath(ast, 'children.0.body.nodes').toEqual([]);
12311231

1232-
ast = toLiquidAST(`{% doc -%} single line doc {%- enddoc %}`);
1232+
ast = toLiquidAST(`
1233+
{% doc -%}
1234+
@param asdf
1235+
@unsupported this node falls back to a text node
1236+
{%- enddoc %}
1237+
`);
12331238
expectPath(ast, 'children.0.type').to.eql('LiquidRawTag');
12341239
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');
1240+
expectPath(ast, 'children.0.body.nodes.0.type').to.eql('LiquidDocParamNode');
1241+
expectPath(ast, 'children.0.body.nodes.0.name').to.eql('@param');
12371242

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`,
1243+
expectPath(ast, 'children.0.body.nodes.1.type').to.eql('TextNode');
1244+
expectPath(ast, 'children.0.body.nodes.1.value').to.eql(
1245+
'@unsupported this node falls back to a text node',
12461246
);
1247-
expectPath(ast, 'children.0.body.nodes.0.type').toEqual('TextNode');
12481247
});
12491248

12501249
it('should parse unclosed tables with assignments', () => {

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

+18-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ export type LiquidHtmlNode =
107107
| RenderVariableExpression
108108
| LiquidLogicalExpression
109109
| LiquidComparison
110-
| TextNode;
110+
| TextNode
111+
| LiquidDocParamNode;
111112

112113
/** The root node of all LiquidHTML ASTs. */
113114
export interface DocumentNode extends ASTNode<NodeTypes.Document> {
@@ -754,6 +755,11 @@ export interface TextNode extends ASTNode<NodeTypes.TextNode> {
754755
value: string;
755756
}
756757

758+
export interface LiquidDocParamNode extends ASTNode<NodeTypes.LiquidDocParamNode> {
759+
name: string;
760+
value: string;
761+
}
762+
757763
export interface ASTNode<T> {
758764
/**
759765
* The type of the node, as a string.
@@ -1268,6 +1274,17 @@ function buildAst(
12681274
break;
12691275
}
12701276

1277+
case ConcreteNodeTypes.LiquidDocParamNode: {
1278+
builder.push({
1279+
type: NodeTypes.LiquidDocParamNode,
1280+
name: node.name,
1281+
position: position(node),
1282+
source: node.source,
1283+
value: node.value,
1284+
});
1285+
break;
1286+
}
1287+
12711288
default: {
12721289
assertNever(node);
12731290
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export enum NodeTypes {
4444
RawMarkup = 'RawMarkup',
4545
RenderMarkup = 'RenderMarkup',
4646
RenderVariableExpression = 'RenderVariableExpression',
47+
LiquidDocParamNode = 'LiquidDocParamNode',
4748
}
4849

4950
// These are officially supported with special node types

packages/prettier-plugin-liquid/src/printer/preprocess/augment-with-css-properties.ts

+2
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ function getCssDisplay(node: AugmentedNode<WithSiblings>, options: LiquidParserO
128128
case NodeTypes.RenderVariableExpression:
129129
case NodeTypes.LogicalExpression:
130130
case NodeTypes.Comparison:
131+
case NodeTypes.LiquidDocParamNode:
131132
return 'should not be relevant';
132133

133134
default:
@@ -233,6 +234,7 @@ function getNodeCssStyleWhiteSpace(
233234
case NodeTypes.RenderVariableExpression:
234235
case NodeTypes.LogicalExpression:
235236
case NodeTypes.Comparison:
237+
case NodeTypes.LiquidDocParamNode:
236238
return 'should not be relevant';
237239

238240
default:

packages/prettier-plugin-liquid/src/printer/print/liquid.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NodeTypes, NamedTags, isBranchedTag } from '@shopify/liquid-html-parser';
1+
import { NodeTypes, NamedTags, isBranchedTag, RawMarkup } from '@shopify/liquid-html-parser';
22
import { Doc, doc } from 'prettier';
33

44
import {
@@ -490,6 +490,16 @@ export function printLiquidRawTag(
490490
return [blockStart, ...body, blockEnd];
491491
}
492492

493+
export function printLiquidDoc(
494+
path: AstPath<RawMarkup>,
495+
_options: LiquidParserOptions,
496+
print: LiquidPrinter,
497+
_args: LiquidPrinterArgs,
498+
) {
499+
const body = path.map((p: any) => print(p), 'nodes');
500+
return [indent([hardline, body]), hardline];
501+
}
502+
493503
function innerLeadingWhitespace(node: LiquidTag | LiquidBranch) {
494504
if (!node.firstChild) {
495505
if (node.isDanglingWhitespaceSensitive && node.hasDanglingWhitespace) {

packages/prettier-plugin-liquid/src/printer/printer-liquid-html.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
getConditionalComment,
3+
LiquidDocParamNode,
34
NodeTypes,
45
Position,
56
RawMarkupKinds,
@@ -30,6 +31,7 @@ import {
3031
LiquidTag,
3132
LiquidVariableOutput,
3233
nonTraversableProperties,
34+
RawMarkup,
3335
TextNode,
3436
} from '../types';
3537
import { assertNever } from '../utils';
@@ -40,6 +42,7 @@ import { printChildren } from './print/children';
4042
import { printElement } from './print/element';
4143
import {
4244
printLiquidBranch,
45+
printLiquidDoc,
4346
printLiquidRawTag,
4447
printLiquidTag,
4548
printLiquidVariableOutput,
@@ -210,6 +213,10 @@ function printNode(
210213
}
211214

212215
case NodeTypes.RawMarkup: {
216+
if (node.parentNode?.name === 'doc') {
217+
return printLiquidDoc(path as AstPath<RawMarkup>, options, print, args);
218+
}
219+
213220
const isRawMarkupIdentationSensitive = () => {
214221
switch (node.kind) {
215222
case RawMarkupKinds.typescript:
@@ -547,6 +554,10 @@ function printNode(
547554
return [...doc, ...lookups];
548555
}
549556

557+
case NodeTypes.LiquidDocParamNode: {
558+
return [node.name, ' ', node.value];
559+
}
560+
550561
default: {
551562
return assertNever(node);
552563
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
It should indent the body
2+
{% doc %}
3+
@param body
4+
{% enddoc %}
5+
6+
It should not dedent to root
7+
{% doc %}
8+
@param body
9+
{% enddoc %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
It should indent the body
2+
{% doc %}
3+
@param body
4+
{% enddoc %}
5+
6+
It should not dedent to root
7+
{% doc %}
8+
@param body
9+
{% enddoc %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test } from 'vitest';
2+
import { assertFormattedEqualsFixed } from '../test-helpers';
3+
import * as path from 'path';
4+
5+
test('Unit: liquid-doc', async () => {
6+
await assertFormattedEqualsFixed(__dirname);
7+
});

packages/theme-language-server-common/src/completions/params/LiquidCompletionParams.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,8 @@ function findCurrentNode(
402402
case NodeTypes.TextNode:
403403
case NodeTypes.LiquidLiteral:
404404
case NodeTypes.String:
405-
case NodeTypes.Number: {
405+
case NodeTypes.Number:
406+
case NodeTypes.LiquidDocParamNode: {
406407
break;
407408
}
408409

0 commit comments

Comments
 (0)