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); +});