Skip to content

Commit ff32bd1

Browse files
committed
added snippet tags in stage 1 and stage 2
1 parent ec1fbc0 commit ff32bd1

File tree

7 files changed

+79
-0
lines changed

7 files changed

+79
-0
lines changed

.changeset/dull-jobs-tap.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@shopify/prettier-plugin-liquid': minor
3+
'@shopify/liquid-html-parser': minor
4+
---
5+
6+
updated theme-tools ohm rules to accept snippet tag

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,17 @@ describe('Unit: Stage 1 (CST)', () => {
794794
});
795795
});
796796

797+
it('should parse snippet arguments as a singular liquid variable lookup', () => {
798+
[{ expression: `var`, type: 'VariableLookup' }].forEach(({ expression, type }) => {
799+
for (const { toCST, expectPath } of testCases) {
800+
cst = toCST(`{% snippet ${expression} -%}`);
801+
expectPath(cst, '0.type').to.equal('LiquidTagOpen');
802+
expectPath(cst, '0.name').to.equal('snippet');
803+
expectPath(cst, '0.markup.type').to.equal(type);
804+
}
805+
});
806+
});
807+
797808
it('should parse when arguments as an array of liquid expressions', () => {
798809
[
799810
{ expression: `"string"`, args: [{ type: 'String' }] },

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ export type ConcreteLiquidTagOpen = ConcreteLiquidTagOpenBaseCase | ConcreteLiqu
237237
export type ConcreteLiquidTagOpenNamed =
238238
| ConcreteLiquidTagOpenCase
239239
| ConcreteLiquidTagOpenCapture
240+
| ConcreteLiquidTagOpenSnippet
240241
| ConcreteLiquidTagOpenIf
241242
| ConcreteLiquidTagOpenUnless
242243
| ConcreteLiquidTagOpenForm
@@ -254,6 +255,8 @@ export interface ConcreteLiquidTagOpenBaseCase extends ConcreteLiquidTagOpenNode
254255

255256
export interface ConcreteLiquidTagOpenCapture
256257
extends ConcreteLiquidTagOpenNode<NamedTags.capture, ConcreteLiquidVariableLookup> {}
258+
export interface ConcreteLiquidTagOpenSnippet
259+
extends ConcreteLiquidTagOpenNode<NamedTags.snippet, ConcreteLiquidVariableLookup> {}
257260

258261
export interface ConcreteLiquidTagOpenCase
259262
extends ConcreteLiquidTagOpenNode<NamedTags.case, ConcreteLiquidExpression> {}
@@ -746,6 +749,7 @@ function toCST<T>(
746749
},
747750

748751
liquidTagOpenCapture: 0,
752+
liquidTagOpenSnippet: 0,
749753
liquidTagOpenForm: 0,
750754
liquidTagOpenFormMarkup: 0,
751755
liquidTagOpenFor: 0,

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,31 @@ describe('Unit: Stage 2 (AST)', () => {
637637
});
638638
});
639639

640+
it('should parse snippet blocks', () => {
641+
for (const { toAST, expectPath, expectPosition } of testCases) {
642+
ast = toAST(`{% snippet hello_snippet %}<div>Hello content</div>{% endsnippet %}`);
643+
expectPath(ast, 'children.0').to.exist;
644+
expectPath(ast, 'children.0.type').to.eql('LiquidTag');
645+
expectPath(ast, 'children.0.name').to.eql('snippet');
646+
expectPath(ast, 'children.0.markup.type').to.eql('VariableLookup');
647+
expectPath(ast, 'children.0.markup.name').to.eql('hello_snippet');
648+
649+
// toLiquidHtmlAST parses HTML, toLiquidAST treats it as text
650+
if (toAST === toLiquidHtmlAST) {
651+
expectPath(ast, 'children.0.children.0.type').to.eql('HtmlElement');
652+
expectPath(ast, 'children.0.children.0.name.0.value').to.eql('div');
653+
expectPath(ast, 'children.0.children.0.children.0.type').to.eql('TextNode');
654+
expectPath(ast, 'children.0.children.0.children.0.value').to.eql('Hello content');
655+
} else {
656+
expectPath(ast, 'children.0.children.0.type').to.eql('TextNode');
657+
expectPath(ast, 'children.0.children.0.value').to.eql('<div>Hello content</div>');
658+
}
659+
660+
expectPosition(ast, 'children.0');
661+
expectPosition(ast, 'children.0.markup');
662+
}
663+
});
664+
640665
describe('Case: content_for', () => {
641666
it('should parse content_for tags with no arguments', () => {
642667
for (const { toAST, expectPath, expectPosition } of testCases) {
@@ -1230,6 +1255,23 @@ describe('Unit: Stage 2 (AST)', () => {
12301255
expectPosition(ast, 'children.0.body.nodes.2').toEqual('}');
12311256
expectPosition(ast, 'children.0');
12321257
});
1258+
1259+
it('should parse snippet blocks with HTML content', () => {
1260+
ast = toLiquidHtmlAST(`{% snippet hello_snippet %}<div class="component"><p>Hello</p></div>{% endsnippet %}`);
1261+
expectPath(ast, 'children.0.type').to.eql('LiquidTag');
1262+
expectPath(ast, 'children.0.name').to.eql('snippet');
1263+
expectPath(ast, 'children.0.markup.type').to.eql('VariableLookup');
1264+
expectPath(ast, 'children.0.markup.name').to.eql('hello_snippet');
1265+
expectPath(ast, 'children.0.children.0.type').to.eql('HtmlElement');
1266+
expectPath(ast, 'children.0.children.0.name.0.value').to.eql('div');
1267+
expectPath(ast, 'children.0.children.0.attributes.0.name.0.value').to.eql('class');
1268+
expectPath(ast, 'children.0.children.0.attributes.0.value.0.value').to.eql('component');
1269+
expectPath(ast, 'children.0.children.0.children.0.type').to.eql('HtmlElement');
1270+
expectPath(ast, 'children.0.children.0.children.0.name.0.value').to.eql('p');
1271+
expectPath(ast, 'children.0.children.0.children.0.children.0.type').to.eql('TextNode');
1272+
expectPath(ast, 'children.0.children.0.children.0.children.0.value').to.eql('Hello');
1273+
expectPosition(ast, 'children.0');
1274+
});
12331275
});
12341276

12351277
describe('Unit: toLiquidAST(text)', () => {

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ export type LiquidTagNamed =
212212
| LiquidTagRender
213213
| LiquidTagSection
214214
| LiquidTagSections
215+
| LiquidTagSnippet
215216
| LiquidTagTablerow
216217
| LiquidTagUnless;
217218

@@ -278,6 +279,10 @@ export interface LiquidTagDecrement
278279
/** https://shopify.dev/docs/api/liquid/tags#capture */
279280
export interface LiquidTagCapture extends LiquidTagNode<NamedTags.capture, LiquidVariableLookup> {}
280281

282+
// TODO: add actual docs link
283+
/** https://shopify.dev/docs/api/liquid/tags#snippet */
284+
export interface LiquidTagSnippet extends LiquidTagNode<NamedTags.snippet, LiquidVariableLookup> {}
285+
281286
/** https://shopify.dev/docs/api/liquid/tags#cycle */
282287
export interface LiquidTagCycle extends LiquidTagNode<NamedTags.cycle, CycleMarkup> {}
283288

@@ -1544,6 +1549,15 @@ function toNamedLiquidTag(
15441549
};
15451550
}
15461551

1552+
case NamedTags.snippet: {
1553+
return {
1554+
...liquidTagBaseAttributes(node),
1555+
name: node.name,
1556+
markup: toExpression(node.markup) as LiquidVariableLookup,
1557+
children: [],
1558+
};
1559+
}
1560+
15471561
case NamedTags.content_for: {
15481562
return {
15491563
...liquidTagBaseAttributes(node),

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export enum NamedTags {
7070
layout = 'layout',
7171
liquid = 'liquid',
7272
paginate = 'paginate',
73+
snippet = 'snippet',
7374
render = 'render',
7475
section = 'section',
7576
sections = 'sections',

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ function printNamedLiquidBlockStart(
189189
}
190190

191191
case NamedTags.capture:
192+
case NamedTags.snippet:
192193
case NamedTags.increment:
193194
case NamedTags.decrement:
194195
case NamedTags.layout:

0 commit comments

Comments
 (0)