Skip to content
This repository has been archived by the owner on Jun 20, 2024. It is now read-only.

Add support for case and when tags #78

Merged
merged 2 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions grammar/liquid-html.ohm
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,21 @@ LiquidHTML {
liquidNode = liquidRawTag | liquidDrop | liquidTagClose | liquidTagOpen | liquidTag | liquidInlineComment

liquidTagOpen =
| liquidTagOpenCase
| liquidTagOpenForm
| liquidTagOpenIf
| liquidTagOpenUnless
| liquidTagOpenPaginate
| liquidTagOpenUnless
| liquidTagOpenBaseCase
liquidTagClose = "{%" "-"? space* "end" blockName space* tagMarkup "-"? "%}"
liquidTag =
| liquidTagEcho
| liquidTagAssign
| liquidTagEcho
| liquidTagElsif
| liquidTagRender
| liquidTagInclude
| liquidTagRender
| liquidTagSection
| liquidTagWhen
| liquidTagBaseCase

// These two are the same but transformed differently
Expand Down Expand Up @@ -121,6 +123,13 @@ LiquidHTML {
liquidTagOpenForm = liquidTagOpenRule<"form", liquidTagOpenFormMarkup>
liquidTagOpenFormMarkup = arguments space*

liquidTagOpenCase = liquidTagOpenRule<"case", liquidTagOpenCaseMarkup>
liquidTagOpenCaseMarkup = liquidExpression space*

liquidTagWhen = liquidTagRule<"when", liquidTagWhenMarkup>
liquidTagWhenMarkup = nonemptyListOf<liquidExpression, whenMarkupSep> space*
whenMarkupSep = space* ("," | "or" ~identifier) space*

liquidTagOpenIf = liquidTagOpenRule<"if", liquidTagOpenConditionalMarkup>
liquidTagOpenUnless = liquidTagOpenRule<"unless", liquidTagOpenConditionalMarkup>
liquidTagElsif = liquidTagRule<"elsif", liquidTagOpenConditionalMarkup>
Expand Down
7 changes: 4 additions & 3 deletions src/parser/ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ describe('Unit: toLiquidHtmlAST', () => {
});

it('should parse liquid case as branches', () => {
ast = toLiquidHtmlAST(`{% case A %}{% when A %}A{% when B %}B{% else %}C{% endcase %}`);
ast = toLiquidHtmlAST(`{% case A %}{% when A %}A{% when "B" %}B{% else %}C{% endcase %}`);
expectPath(ast, 'children.0').to.exist;
expectPath(ast, 'children.0.type').to.eql('LiquidTag');
expectPath(ast, 'children.0.name').to.eql('case');
Expand All @@ -675,13 +675,14 @@ describe('Unit: toLiquidHtmlAST', () => {
expectPath(ast, 'children.0.children.1').to.exist;
expectPath(ast, 'children.0.children.1.type').to.eql('LiquidBranch');
expectPath(ast, 'children.0.children.1.name').to.eql('when');
expectPath(ast, 'children.0.children.1.markup').to.eql('A');
expectPath(ast, 'children.0.children.1.markup').to.have.lengthOf(1);
expectPath(ast, 'children.0.children.1.markup.0.type').to.equal('VariableLookup');
expectPath(ast, 'children.0.children.1.children.0.type').to.eql('TextNode');
expectPath(ast, 'children.0.children.1.children.0.value').to.eql('A');

expectPath(ast, 'children.0.children.2.type').to.eql('LiquidBranch');
expectPath(ast, 'children.0.children.2.name').to.eql('when');
expectPath(ast, 'children.0.children.2.markup').to.eql('B');
expectPath(ast, 'children.0.children.2.markup.0.type').to.equal('String');
expectPath(ast, 'children.0.children.2.children.0.type').to.eql('TextNode');
expectPath(ast, 'children.0.children.2.children.0.value').to.eql('B');

Expand Down
25 changes: 24 additions & 1 deletion src/parser/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export interface LiquidRawTag extends ASTNode<NodeTypes.LiquidRawTag> {
export type LiquidTag = LiquidTagNamed | LiquidTagBaseCase;
export type LiquidTagNamed =
| LiquidTagAssign
| LiquidTagCase
| LiquidTagEcho
| LiquidTagForm
| LiquidTagIf
Expand Down Expand Up @@ -155,6 +156,11 @@ export interface AssignMarkup extends ASTNode<NodeTypes.AssignMarkup> {
value: LiquidVariable;
}

export interface LiquidTagCase
extends LiquidTagNode<NamedTags.case, LiquidExpression> {}
export interface LiquidBranchWhen
extends LiquidBranchNode<NamedTags.when, LiquidExpression[]> {}

export interface LiquidTagForm
extends LiquidTagNode<NamedTags.form, LiquidArgument[]> {}

Expand Down Expand Up @@ -217,7 +223,7 @@ export type LiquidBranch =
| LiquidBranchUnnamed
| LiquidBranchBaseCase
| LiquidBranchNamed;
export type LiquidBranchNamed = LiquidBranchElsif;
export type LiquidBranchNamed = LiquidBranchElsif | LiquidBranchWhen;

interface LiquidBranchNode<Name, Markup>
extends ASTNode<NodeTypes.LiquidBranch> {
Expand Down Expand Up @@ -850,6 +856,23 @@ function toNamedLiquidTag(
};
}

case NamedTags.case: {
return {
...liquidTagBaseAttributes(node, source),
name: node.name,
markup: toExpression(node.markup, source),
children: [],
};
}

case NamedTags.when: {
return {
...liquidBranchBaseAttributes(node, source),
name: node.name,
markup: node.markup.map((arg) => toExpression(arg, source)),
};
}

default: {
return assertNever(node);
}
Expand Down
38 changes: 38 additions & 0 deletions src/parser/cst.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,44 @@ describe('Unit: toLiquidHtmlCST(text)', () => {
});
});

it('should parse case arguments as a singular liquid expression', () => {
[
{ expression: `"string"`, type: 'String' },
{ expression: `var.lookup`, type: 'VariableLookup' },
].forEach(({ expression, type }) => {
cst = toLiquidHtmlCST(`{% case ${expression} -%}`);
expectPath(cst, '0.type').to.equal('LiquidTagOpen');
expectPath(cst, '0.name').to.equal('case');
expectPath(cst, '0.markup.type').to.equal(type);
expectLocation(cst, '0');
expectLocation(cst, '0.markup');
});
});

it('should parse when arguments as an array of liquid expressions', () => {
[
{ expression: `"string"`, args: [{ type: 'String' }] },
{
expression: `"string", var.lookup`,
args: [{ type: 'String' }, { type: 'VariableLookup' }],
},
{
expression: `"string" or var.lookup`,
args: [{ type: 'String' }, { type: 'VariableLookup' }],
},
].forEach(({ expression, args }) => {
cst = toLiquidHtmlCST(`{% when ${expression} -%}`);
expectPath(cst, '0.type').to.equal('LiquidTag');
expectPath(cst, '0.name').to.equal('when');
expectPath(cst, '0.markup').to.have.lengthOf(args.length);
args.forEach((arg, i) => {
expectPath(cst, `0.markup.${i}.type`).to.equal(arg.type);
expectLocation(cst, `0.markup.${i}`);
});
expectLocation(cst, '0');
});
});

it('should parse the paginate tag open markup as arguments', () => {
[
{
Expand Down
13 changes: 12 additions & 1 deletion src/parser/cst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export type ConcreteLiquidTagOpen =
| ConcreteLiquidTagOpenBaseCase
| ConcreteLiquidTagOpenNamed;
export type ConcreteLiquidTagOpenNamed =
| ConcreteLiquidTagOpenCase
| ConcreteLiquidTagOpenIf
| ConcreteLiquidTagOpenUnless
| ConcreteLiquidTagOpenForm
Expand All @@ -155,6 +156,11 @@ export interface ConcreteLiquidTagOpenNode<Name, Markup>
export interface ConcreteLiquidTagOpenBaseCase
extends ConcreteLiquidTagOpenNode<string, string> {}

export interface ConcreteLiquidTagOpenCase
extends ConcreteLiquidTagOpenNode<NamedTags.case, ConcreteLiquidExpression> {}
export interface ConcreteLiquidTagWhen
extends ConcreteLiquidTagNode<NamedTags.when, ConcreteLiquidExpression[]> {}

export interface ConcreteLiquidTagOpenIf
extends ConcreteLiquidTagOpenNode<NamedTags.if, ConcreteLiquidCondition[]> {}
export interface ConcreteLiquidTagOpenUnless
Expand Down Expand Up @@ -208,7 +214,8 @@ export type ConcreteLiquidTagNamed =
| ConcreteLiquidTagElsif
| ConcreteLiquidTagInclude
| ConcreteLiquidTagRender
| ConcreteLiquidTagSection;
| ConcreteLiquidTagSection
| ConcreteLiquidTagWhen;

export interface ConcreteLiquidTagNode<Name, Markup>
extends ConcreteBasicLiquidNode<ConcreteNodeTypes.LiquidTag> {
Expand Down Expand Up @@ -500,6 +507,10 @@ export function toLiquidHtmlCST(text: string): LiquidHtmlCST {
locStart,
locEnd,
},
liquidTagOpenCase: 0,
liquidTagOpenCaseMarkup: 0,
liquidTagWhen: 0,
liquidTagWhenMarkup: 0,
liquidTagOpenIf: 0,
liquidTagOpenUnless: 0,
liquidTagElsif: 0,
Expand Down
47 changes: 29 additions & 18 deletions src/printer/print/liquid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ function printNamedLiquidBlock(
'%}',
]);

const tagWithArrayMarkup = (whitespace: Doc) =>
group([
'{%',
whitespaceStart,
' ',
node.name,
' ',
indent([
join(
[',', line],
path.map((p) => print(p), 'markup'),
),
]),
whitespace,
whitespaceEnd,
'%}',
]);

switch (node.name) {
case NamedTags.echo: {
const whitespace = node.markup.filters.length > 0 ? line : ' ';
Expand All @@ -138,24 +156,8 @@ function printNamedLiquidBlock(
}

case NamedTags.form: {
const args = node.markup;
const whitespace = args.length > 1 ? line : ' ';
return group([
'{%',
whitespaceStart,
' ',
node.name,
' ',
indent([
join(
[',', line],
path.map((p) => print(p), 'markup'),
),
]),
whitespace,
whitespaceEnd,
'%}',
]);
const whitespace = node.markup.length > 1 ? line : ' ';
return tagWithArrayMarkup(whitespace);
}

case NamedTags.paginate: {
Expand All @@ -174,6 +176,15 @@ function printNamedLiquidBlock(
return tag(whitespace);
}

case NamedTags.case: {
return tag(' ');
}

case NamedTags.when: {
const whitespace = node.markup.length > 1 ? line : ' ';
return tagWithArrayMarkup(whitespace);
}

default: {
return assertNever(node);
}
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export function isLiquidHtmlNode(value: any): value is LiquidHtmlNode {
// These are officially supported with special node types
export enum NamedTags {
assign = 'assign',
case = 'case',
echo = 'echo',
elsif = 'elsif',
form = 'form',
Expand All @@ -62,6 +63,7 @@ export enum NamedTags {
render = 'render',
section = 'section',
unless = 'unless',
when = 'when',
}

export enum Comparators {
Expand Down
38 changes: 38 additions & 0 deletions test/liquid-tag-case/fixed.liquid
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
It should never break a name
printWidth: 1
{% case 'name' %}
{% endcase %}
{% case true %}
{% endcase %}
{% case var.lookup %}
{% endcase %}
{%- case var.lookup -%}
{% endcase %}

It should break and indent when statements, and not break the when
printWidth: 1
{% case candy.type %}
{% when 'gummy bears' %}
yum!
{% endcase %}

It should break on when comma syntax
printWidth: 1
{% case candy.type %}
{% when 'gummy bears',
'chocolate'
%}
yum!
{% endcase %}

It should break on when or syntax (and prefer commas)
printWidth: 1
{% case candy.type %}
{% when 'gummy bears',
'chocolate',
'chips'
%}
Comment on lines +30 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if users would prefer something like this:

Suggested change
{% case candy.type %}
{% when 'gummy bears',
'chocolate',
'chips'
%}
{% case candy.type %}
{% when 'gummy bears',
'chocolate',
'chips'
%}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that happens in languages like python and ruby but is really frowned upon in front-end communities (never seen it in JS, CSS, TS). I did see it in clojure/clojurescript but it takes a good editor plugin to handle it properly. Trying to do that manually is a PITA.

I feel like you should be able to manually indent your code reasonably and that feels hard. It's 4 tabs in! And if you use {%-, that'd be 4 tabs + 1 space.

😬

yum!
{% else %}
ok!
{% endcase %}
20 changes: 20 additions & 0 deletions test/liquid-tag-case/index.liquid
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
It should never break on case expression
printWidth: 1
{% case "name" %} {% endcase %}
{% case true %} {% endcase %}
{%case var.lookup %} {% endcase %}
{%- case
var.lookup
-%} {% endcase %}

It should break and indent when statements, and not break the when
printWidth: 1
{% case candy.type %} {% when "gummy bears" %} yum! {% endcase %}

It should break on when comma syntax
printWidth: 1
{% case candy.type %} {% when "gummy bears", "chocolate" %} yum! {% endcase %}

It should break on when or syntax (and prefer commas)
printWidth: 1
{% case candy.type %} {% when "gummy bears" or "chocolate" or "chips" %} yum! {% else %} ok! {% endcase %}
6 changes: 6 additions & 0 deletions test/liquid-tag-case/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { assertFormattedEqualsFixed } from '../test-helpers';
import * as path from 'path';

describe(`Unit: ${path.basename(__dirname)}`, () => {
assertFormattedEqualsFixed(__dirname);
});