Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic prettier support for doc tag #652

Closed
wants to merge 10 commits into from
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
Loading