Skip to content

Commit

Permalink
Add Parsing + Prettier support for {% doc %} open and close tags
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jamesmengo committed Dec 11, 2024
1 parent 026da63 commit e7ed99d
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 29 deletions.
9 changes: 8 additions & 1 deletion packages/liquid-html-parser/grammar/liquid-html.ohm
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,14 @@ LiquidStatement <: Liquid {
}

LiquidDoc <: Helpers {
Node := (TextNode)*
Node := (LiquidDocNode | TextNode)*
LiquidDocNode =
| paramNode
| fallbackNode

fallbackNode = "@" anyExceptStar<newline>
paramNode = "@param" space* paramNodeName
paramNodeName = anyExceptStar<newline>
}

LiquidHTML <: Liquid {
Expand Down
27 changes: 16 additions & 11 deletions packages/liquid-html-parser/src/stage-1-cst.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('-');
Expand All @@ -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,
);
}
});

Expand Down
32 changes: 30 additions & 2 deletions packages/liquid-html-parser/src/stage-1-cst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ export enum ConcreteNodeTypes {
PaginateMarkup = 'PaginateMarkup',
RenderVariableExpression = 'RenderVariableExpression',
ContentForNamedArgument = 'ContentForNamedArgument',

LiquidDocParamNode = 'LiquidDocParamNode',
}

export const LiquidLiteralValues = {
Expand All @@ -105,6 +107,12 @@ export interface ConcreteBasicNode<T> {
locEnd: number;
}

export interface ConcreteLiquidDocParamNode
extends ConcreteBasicNode<ConcreteNodeTypes.LiquidDocParamNode> {
name: string;
value: string;
}

export interface ConcreteHtmlNodeBase<T> extends ConcreteBasicNode<T> {
attrList?: ConcreteAttributeNode[];
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
23 changes: 11 additions & 12 deletions packages/liquid-html-parser/src/stage-2-ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
19 changes: 18 additions & 1 deletion packages/liquid-html-parser/src/stage-2-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<NodeTypes.Document> {
Expand Down Expand Up @@ -754,6 +755,11 @@ export interface TextNode extends ASTNode<NodeTypes.TextNode> {
value: string;
}

export interface LiquidDocParamNode extends ASTNode<NodeTypes.LiquidDocParamNode> {
name: string;
value: string;
}

export interface ASTNode<T> {
/**
* The type of the node, as a string.
Expand Down Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions packages/liquid-html-parser/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export enum NodeTypes {
RawMarkup = 'RawMarkup',
RenderMarkup = 'RenderMarkup',
RenderVariableExpression = 'RenderVariableExpression',
LiquidDocParamNode = 'LiquidDocParamNode',
}

// These are officially supported with special node types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ function getCssDisplay(node: AugmentedNode<WithSiblings>, options: LiquidParserO
case NodeTypes.RenderVariableExpression:
case NodeTypes.LogicalExpression:
case NodeTypes.Comparison:
case NodeTypes.LiquidDocParamNode:
return 'should not be relevant';

default:
Expand Down Expand Up @@ -233,6 +234,7 @@ function getNodeCssStyleWhiteSpace(
case NodeTypes.RenderVariableExpression:
case NodeTypes.LogicalExpression:
case NodeTypes.Comparison:
case NodeTypes.LiquidDocParamNode:
return 'should not be relevant';

default:
Expand Down
12 changes: 11 additions & 1 deletion packages/prettier-plugin-liquid/src/printer/print/liquid.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -490,6 +490,16 @@ export function printLiquidRawTag(
return [blockStart, ...body, blockEnd];
}

export function printLiquidDoc(
path: AstPath<RawMarkup>,
_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) {
Expand Down
11 changes: 11 additions & 0 deletions packages/prettier-plugin-liquid/src/printer/printer-liquid-html.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
getConditionalComment,
LiquidDocParamNode,
NodeTypes,
Position,
RawMarkupKinds,
Expand Down Expand Up @@ -30,6 +31,7 @@ import {
LiquidTag,
LiquidVariableOutput,
nonTraversableProperties,
RawMarkup,
TextNode,
} from '../types';
import { assertNever } from '../utils';
Expand All @@ -40,6 +42,7 @@ import { printChildren } from './print/children';
import { printElement } from './print/element';
import {
printLiquidBranch,
printLiquidDoc,
printLiquidRawTag,
printLiquidTag,
printLiquidVariableOutput,
Expand Down Expand Up @@ -210,6 +213,10 @@ function printNode(
}

case NodeTypes.RawMarkup: {
if (node.parentNode?.name === 'doc') {
return printLiquidDoc(path as AstPath<RawMarkup>, options, print, args);
}

const isRawMarkupIdentationSensitive = () => {
switch (node.kind) {
case RawMarkupKinds.typescript:
Expand Down Expand Up @@ -547,6 +554,10 @@ function printNode(
return [...doc, ...lookups];
}

case NodeTypes.LiquidDocParamNode: {
return [node.name, ' ', node.value];
}

default: {
return assertNever(node);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
It should indent the body
{% doc %}
@param body
{% enddoc %}

It should not dedent to root
{% doc %}
@param body
{% enddoc %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
It should indent the body
{% doc %}
@param body
{% enddoc %}

It should not dedent to root
{% doc %}
@param body
{% enddoc %}
Original file line number Diff line number Diff line change
@@ -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);
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down

0 comments on commit e7ed99d

Please sign in to comment.