diff --git a/grammar/liquid-html.ohm b/grammar/liquid-html.ohm
index 770fb46..88b3262 100644
--- a/grammar/liquid-html.ohm
+++ b/grammar/liquid-html.ohm
@@ -77,6 +77,7 @@ LiquidHTML {
liquidTagOpen =
| liquidTagOpenForm
+ | liquidTagOpenPaginate
| liquidTagOpenBaseCase
liquidTagClose = "{%" "-"? space* "end" blockName space* tagMarkup "-"? "%}"
liquidTag =
@@ -107,17 +108,20 @@ LiquidHTML {
liquidTagInclude = liquidTagRule<"include", liquidTagRenderMarkup>
liquidTagRender = liquidTagRule<"render", liquidTagRenderMarkup>
liquidTagRenderMarkup =
- snippetExpression renderVariableExpression? renderAliasExpression? (argumentSeparatorOptionalComma renderArguments) space*
+ snippetExpression renderVariableExpression? renderAliasExpression? (argumentSeparatorOptionalComma tagArguments) space*
snippetExpression = liquidString | variableSegmentAsLookup
renderVariableExpression = space+ ("for" | "with") space+ liquidExpression
renderAliasExpression = space+ "as" space+ variableSegment
- renderArguments = listOf
liquidTagOpenBaseCase = liquidTagOpenRule
liquidTagOpenForm = liquidTagOpenRule<"form", liquidTagOpenFormMarkup>
liquidTagOpenFormMarkup = arguments space*
+ liquidTagOpenPaginate = liquidTagOpenRule<"paginate", liquidTagOpenPaginateMarkup>
+ liquidTagOpenPaginateMarkup =
+ liquidExpression space+ "by" space+ liquidExpression (argumentSeparatorOptionalComma tagArguments)? space*
+
liquidDrop = "{{" "-"? space* liquidDropCases "-"? "}}"
liquidDropCases = liquidVariable | liquidDropBaseCase
liquidDropBaseCase = anyExceptStar<("-}}" | "}}")>
@@ -186,6 +190,7 @@ LiquidHTML {
argumentSeparatorOptionalComma = space* ","? space*
positionalArgument = liquidExpression ~(space* ":")
namedArgument = variableSegment space* ":" space* liquidExpression
+ tagArguments = listOf
variableSegment = (letter | "_") identifierCharacter*
variableSegmentAsLookup = variableSegment
diff --git a/src/parser/ast.ts b/src/parser/ast.ts
index d3b416a..9da7fd2 100644
--- a/src/parser/ast.ts
+++ b/src/parser/ast.ts
@@ -27,6 +27,7 @@ import {
ConcreteLiquidTagOpenNamed,
ConcreteLiquidTagOpen,
ConcreteLiquidArgument,
+ ConcretePaginateMarkup,
} from '~/parser/cst';
import { isLiquidHtmlNode, NamedTags, NodeTypes, Position } from '~/types';
import { assertNever, deepGet, dropLast } from '~/utils';
@@ -50,6 +51,7 @@ export type LiquidHtmlNode =
| LiquidNamedArgument
| AssignMarkup
| RenderMarkup
+ | PaginateMarkup
| RenderVariableExpression
| TextNode;
@@ -106,6 +108,7 @@ export type LiquidTagNamed =
| LiquidTagEcho
| LiquidTagForm
| LiquidTagInclude
+ | LiquidTagPaginate
| LiquidTagRender
| LiquidTagSection;
@@ -142,6 +145,15 @@ export interface AssignMarkup extends ASTNode {
export interface LiquidTagForm
extends LiquidTagNode {}
+
+export interface LiquidTagPaginate
+ extends LiquidTagNode {}
+export interface PaginateMarkup extends ASTNode {
+ collection: LiquidExpression;
+ pageSize: LiquidExpression;
+ args: LiquidNamedArgument[];
+}
+
export interface LiquidTagRender
extends LiquidTagNode {}
export interface LiquidTagInclude
@@ -730,6 +742,7 @@ function toNamedLiquidTag(
...liquidTagBaseAttributes(node, source),
};
}
+
case NamedTags.assign: {
return {
name: NamedTags.assign,
@@ -737,6 +750,7 @@ function toNamedLiquidTag(
...liquidTagBaseAttributes(node, source),
};
}
+
case NamedTags.include:
case NamedTags.render: {
return {
@@ -745,6 +759,7 @@ function toNamedLiquidTag(
...liquidTagBaseAttributes(node, source),
};
}
+
case NamedTags.section: {
return {
name: node.name,
@@ -752,6 +767,7 @@ function toNamedLiquidTag(
...liquidTagBaseAttributes(node, source),
};
}
+
case NamedTags.form: {
return {
name: node.name,
@@ -761,6 +777,15 @@ function toNamedLiquidTag(
};
}
+ case NamedTags.paginate: {
+ return {
+ name: node.name,
+ markup: toPaginateMarkup(node.markup, source),
+ children: [],
+ ...liquidTagBaseAttributes(node, source),
+ };
+ }
+
default: {
return assertNever(node);
}
@@ -780,6 +805,20 @@ function toAssignMarkup(
};
}
+function toPaginateMarkup(
+ node: ConcretePaginateMarkup,
+ source: string,
+): PaginateMarkup {
+ return {
+ type: NodeTypes.PaginateMarkup,
+ collection: toExpression(node.collection, source),
+ pageSize: toExpression(node.pageSize, source),
+ position: position(node),
+ args: node.args ? node.args.map((arg) => toNamedArgument(arg, source)) : [],
+ source,
+ };
+}
+
function toRenderMarkup(
node: ConcreteLiquidTagRenderMarkup,
source: string,
diff --git a/src/parser/cst.spec.ts b/src/parser/cst.spec.ts
index 2c3b731..f6122d9 100644
--- a/src/parser/cst.spec.ts
+++ b/src/parser/cst.spec.ts
@@ -576,6 +576,51 @@ describe('Unit: toLiquidHtmlCST(text)', () => {
expectLocation(cst, '0');
});
});
+
+ it('should parse the paginate tag open markup as arguments', () => {
+ [
+ {
+ expression: `collection.products by 50`,
+ collection: { type: 'VariableLookup' },
+ pageSize: { type: 'Number' },
+ },
+ {
+ expression: `collection.products by setting.value`,
+ collection: { type: 'VariableLookup' },
+ pageSize: { type: 'VariableLookup' },
+ },
+ {
+ expression: `collection.products by setting.value window_size: 2`,
+ collection: { type: 'VariableLookup' },
+ pageSize: { type: 'VariableLookup' },
+ args: [{ type: 'Number' }],
+ },
+ {
+ expression: `collection.products by setting.value, window_size: 2`,
+ collection: { type: 'VariableLookup' },
+ pageSize: { type: 'VariableLookup' },
+ args: [{ type: 'Number' }],
+ },
+ ].forEach(({ expression, collection, pageSize, args }) => {
+ cst = toLiquidHtmlCST(`{% paginate ${expression} -%}`);
+ expectPath(cst, '0.type').to.equal('LiquidTagOpen');
+ expectPath(cst, '0.name').to.equal('paginate');
+ expectPath(cst, '0.markup.type').to.equal('PaginateMarkup');
+ expectPath(cst, '0.markup.collection.type').to.equal(collection.type);
+ expectPath(cst, '0.markup.pageSize.type').to.equal(pageSize.type);
+ if (args) {
+ expectPath(cst, '0.markup.args').to.have.lengthOf(args.length);
+ args.forEach((arg, i) => {
+ expectPath(cst, `0.markup.args.${i}.type`).to.equal('NamedArgument');
+ expectPath(cst, `0.markup.args.${i}.value.type`).to.equal(arg.type);
+ });
+ } else {
+ expectPath(cst, '0.markup.args').to.have.lengthOf(0);
+ }
+ expectLocation(cst, '0');
+ expectLocation(cst, '0.markup');
+ });
+ });
});
describe('Case: LiquidNode', () => {
diff --git a/src/parser/cst.ts b/src/parser/cst.ts
index e5465e6..f49505d 100644
--- a/src/parser/cst.ts
+++ b/src/parser/cst.ts
@@ -35,6 +35,7 @@ export enum ConcreteNodeTypes {
AssignMarkup = 'AssignMarkup',
RenderMarkup = 'RenderMarkup',
+ PaginateMarkup = 'PaginateMarkup',
RenderVariableExpression = 'RenderVariableExpression',
}
@@ -137,7 +138,9 @@ export interface ConcreteLiquidRawTag
export type ConcreteLiquidTagOpen =
| ConcreteLiquidTagOpenBaseCase
| ConcreteLiquidTagOpenNamed;
-export type ConcreteLiquidTagOpenNamed = ConcreteLiquidTagOpenForm;
+export type ConcreteLiquidTagOpenNamed =
+ | ConcreteLiquidTagOpenForm
+ | ConcreteLiquidTagOpenPaginate;
export interface ConcreteLiquidTagOpenNode
extends ConcreteBasicLiquidNode {
@@ -151,6 +154,19 @@ export interface ConcreteLiquidTagOpenBaseCase
export interface ConcreteLiquidTagOpenForm
extends ConcreteLiquidTagOpenNode {}
+export interface ConcreteLiquidTagOpenPaginate
+ extends ConcreteLiquidTagOpenNode<
+ NamedTags.paginate,
+ ConcretePaginateMarkup
+ > {}
+
+export interface ConcretePaginateMarkup
+ extends ConcreteBasicNode {
+ collection: ConcreteLiquidExpression;
+ pageSize: ConcreteLiquidExpression;
+ args: ConcreteLiquidNamedArgument[] | null;
+}
+
export interface ConcreteLiquidTagClose
extends ConcreteBasicLiquidNode {
name: string;
@@ -447,6 +463,15 @@ export function toLiquidHtmlCST(text: string): LiquidHtmlCST {
liquidTagOpenForm: 0,
liquidTagOpenFormMarkup: 0,
+ liquidTagOpenPaginate: 0,
+ liquidTagOpenPaginateMarkup: {
+ type: ConcreteNodeTypes.PaginateMarkup,
+ collection: 0,
+ pageSize: 4,
+ args: 6,
+ locStart,
+ locEnd,
+ },
liquidTagClose: {
type: ConcreteNodeTypes.LiquidTagClose,
@@ -508,7 +533,6 @@ export function toLiquidHtmlCST(text: string): LiquidHtmlCST {
locEnd,
},
renderAliasExpression: 3,
- renderArguments: 0,
liquidDrop: {
type: ConcreteNodeTypes.LiquidDrop,
@@ -550,6 +574,7 @@ export function toLiquidHtmlCST(text: string): LiquidHtmlCST {
},
},
arguments: 0,
+ tagArguments: 0,
positionalArgument: 0,
namedArgument: {
type: ConcreteNodeTypes.NamedArgument,
diff --git a/src/printer/preprocess/augment-with-css-properties.ts b/src/printer/preprocess/augment-with-css-properties.ts
index b7fc41a..05b4ea3 100644
--- a/src/printer/preprocess/augment-with-css-properties.ts
+++ b/src/printer/preprocess/augment-with-css-properties.ts
@@ -92,6 +92,7 @@ function getCssDisplay(
case NodeTypes.Range:
case NodeTypes.VariableLookup:
case NodeTypes.AssignMarkup:
+ case NodeTypes.PaginateMarkup:
case NodeTypes.RenderMarkup:
case NodeTypes.RenderVariableExpression:
return 'should not be relevant';
@@ -148,6 +149,7 @@ function getNodeCssStyleWhiteSpace(node: AugmentedNode): string {
case NodeTypes.Range:
case NodeTypes.VariableLookup:
case NodeTypes.AssignMarkup:
+ case NodeTypes.PaginateMarkup:
case NodeTypes.RenderMarkup:
case NodeTypes.RenderVariableExpression:
return 'should not be relevant';
diff --git a/src/printer/print/liquid.ts b/src/printer/print/liquid.ts
index f2d3e9c..986c5d1 100644
--- a/src/printer/print/liquid.ts
+++ b/src/printer/print/liquid.ts
@@ -157,6 +157,10 @@ function printNamedLiquidBlock(
]);
}
+ case NamedTags.paginate: {
+ return tag(line);
+ }
+
default: {
return assertNever(node);
}
diff --git a/src/printer/printer-liquid-html.ts b/src/printer/printer-liquid-html.ts
index 19a550a..08e7173 100644
--- a/src/printer/printer-liquid-html.ts
+++ b/src/printer/printer-liquid-html.ts
@@ -382,6 +382,28 @@ function printNode(
return [node.name, ' = ', path.call(print, 'value')];
}
+ case NodeTypes.PaginateMarkup: {
+ const doc = [
+ path.call(print, 'collection'),
+ line,
+ 'by ',
+ path.call(print, 'pageSize'),
+ ];
+
+ if (node.args.length > 0) {
+ doc.push([
+ ',',
+ line,
+ join(
+ [',', line],
+ path.map((p) => print(p), 'args'),
+ ),
+ ]);
+ }
+
+ return doc;
+ }
+
case NodeTypes.RenderMarkup: {
const snippet = path.call(print, 'snippet');
const doc: Doc = [snippet];
diff --git a/src/types.ts b/src/types.ts
index d4474ce..96c5f62 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -34,6 +34,7 @@ export enum NodeTypes {
VariableLookup = 'VariableLookup',
AssignMarkup = 'AssignMarkup',
+ PaginateMarkup = 'PaginateMarkup',
RenderMarkup = 'RenderMarkup',
RenderVariableExpression = 'RenderVariableExpression',
}
@@ -55,6 +56,7 @@ export enum NamedTags {
render = 'render',
include = 'include',
form = 'form',
+ paginate = 'paginate',
}
export const HtmlNodeTypes = [
diff --git a/test/liquid-tag-paginate/fixed.liquid b/test/liquid-tag-paginate/fixed.liquid
new file mode 100644
index 0000000..f016b0b
--- /dev/null
+++ b/test/liquid-tag-paginate/fixed.liquid
@@ -0,0 +1,26 @@
+It takes a collection and a pageSize
+{% paginate collection by pageSize %}{% endpaginate %}
+
+It breaks on by pageSize when printWidth is hit
+printWidth: 1
+{% paginate collection
+ by pageSize
+%}
+ {{ collection }}
+{% endpaginate %}
+
+It is undocumented, but it also accepts named attributes (window_size). Those are comma separated.
+printWidth: 80
+{% paginate collection by pageSize, window_size: 50, attr: (0..2) %}
+ {{ collection }}
+{% endpaginate %}
+
+It is undocumented, but it also accepts named attributes (window_size). We print
+those comma separated and on a new line when it breaks.
+printWidth: 1
+{% paginate collection
+ by pageSize,
+ window_size: 50
+%}
+ {{ collection }}
+{% endpaginate %}
diff --git a/test/liquid-tag-paginate/index.liquid b/test/liquid-tag-paginate/index.liquid
new file mode 100644
index 0000000..0092582
--- /dev/null
+++ b/test/liquid-tag-paginate/index.liquid
@@ -0,0 +1,22 @@
+It takes a collection and a pageSize
+{% paginate collection by pageSize %}{% endpaginate %}
+
+It breaks on by pageSize when printWidth is hit
+printWidth: 1
+{% paginate collection by pageSize %}
+ {{ collection }}
+{% endpaginate %}
+
+It is undocumented, but it also accepts named attributes (window_size). Those are comma separated.
+printWidth: 80
+{% paginate collection by pageSize window_size:50, attr:(0.. 2) %}
+ {{ collection }}
+{% endpaginate %}
+
+It is undocumented, but it also accepts named attributes (window_size). We print
+those comma separated and on a new line when it breaks.
+printWidth: 1
+{% paginate collection by pageSize window_size: 50 %}
+ {{ collection }}
+{% endpaginate %}
+
diff --git a/test/liquid-tag-paginate/index.spec.ts b/test/liquid-tag-paginate/index.spec.ts
new file mode 100644
index 0000000..4587999
--- /dev/null
+++ b/test/liquid-tag-paginate/index.spec.ts
@@ -0,0 +1,6 @@
+import { assertFormattedEqualsFixed } from '../test-helpers';
+import * as path from 'path';
+
+describe(`Unit: ${path.basename(__dirname)}`, () => {
+ assertFormattedEqualsFixed(__dirname);
+});