@platformos/liquid-html-parser 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,7 +63,6 @@ export declare enum ConcreteNodeTypes {
63
63
  Comparison = "Comparison",
64
64
  Condition = "Condition",
65
65
  AssignMarkup = "AssignMarkup",
66
- AssignPushRhs = "AssignPushRhs",
67
66
  HashAssignMarkup = "HashAssignMarkup",
68
67
  ContentForMarkup = "ContentForMarkup",
69
68
  CycleMarkup = "CycleMarkup",
@@ -267,15 +266,11 @@ export interface ConcreteLiquidTagLiquid extends ConcreteLiquidTagNode<NamedTags
267
266
  export type ConcreteLiquidLiquidTagNode = ConcreteLiquidTagOpen | ConcreteLiquidTagClose | ConcreteLiquidTag | ConcreteLiquidRawTag;
268
267
  export interface ConcreteLiquidTagAssign extends ConcreteLiquidTagNode<NamedTags.assign, ConcreteLiquidTagAssignMarkup> {
269
268
  }
270
- export interface ConcreteLiquidTagAssignPushRhs extends ConcreteBasicNode<ConcreteNodeTypes.AssignPushRhs> {
271
- pushSource: ConcreteLiquidVariable;
272
- pushValue: ConcreteLiquidVariable;
273
- }
274
269
  export interface ConcreteLiquidTagAssignMarkup extends ConcreteBasicNode<ConcreteNodeTypes.AssignMarkup> {
275
270
  name: string;
276
271
  target: ConcreteLiquidVariableLookup;
277
272
  operator: string;
278
- value: ConcreteLiquidVariable | ConcreteLiquidTagAssignPushRhs;
273
+ value: ConcreteLiquidVariable;
279
274
  }
280
275
  export interface ConcreteLiquidTagHashAssign extends ConcreteLiquidTagNode<NamedTags.hash_assign, ConcreteLiquidTagHashAssignMarkup> {
281
276
  }
@@ -71,7 +71,6 @@ var ConcreteNodeTypes;
71
71
  ConcreteNodeTypes["Comparison"] = "Comparison";
72
72
  ConcreteNodeTypes["Condition"] = "Condition";
73
73
  ConcreteNodeTypes["AssignMarkup"] = "AssignMarkup";
74
- ConcreteNodeTypes["AssignPushRhs"] = "AssignPushRhs";
75
74
  ConcreteNodeTypes["HashAssignMarkup"] = "HashAssignMarkup";
76
75
  ConcreteNodeTypes["ContentForMarkup"] = "ContentForMarkup";
77
76
  ConcreteNodeTypes["CycleMarkup"] = "CycleMarkup";
@@ -368,25 +367,6 @@ function toCST(source /* the original file */, grammars, grammar, cstMappings, m
368
367
  },
369
368
  assignOperator: (node) => node.sourceString,
370
369
  liquidAssignValue: 0,
371
- liquidAssignPushExpr: {
372
- type: ConcreteNodeTypes.AssignPushRhs,
373
- // tokens: [pushSource, space*, "<<", space*, pushValue]
374
- pushSource: 0,
375
- pushValue: 4,
376
- locStart,
377
- locEnd,
378
- source,
379
- },
380
- liquidAssignPushSource: {
381
- type: ConcreteNodeTypes.LiquidVariable,
382
- expression: 0,
383
- filters: 1,
384
- // No &delim lookahead at end (unlike liquidAssignVariable), so last token is space*
385
- rawSource: (tokens) => source.slice(locStart(tokens), tokens[tokens.length - 1].source.endIdx).trimEnd(),
386
- locStart,
387
- locEnd,
388
- source,
389
- },
390
370
  liquidAssignVariable: {
391
371
  type: ConcreteNodeTypes.LiquidVariable,
392
372
  expression: 0,
@@ -633,6 +613,8 @@ function toCST(source /* the original file */, grammars, grammar, cstMappings, m
633
613
  liquidTagResponseHeadersMarkup: 0,
634
614
  liquidTagRollback: 0,
635
615
  renderArguments: 1,
616
+ functionRenderArguments: 1,
617
+ functionTagArguments: 0,
636
618
  completionModeRenderArguments: function (_0, namedArguments, _2, _3, _4, _5, variableLookup, _7) {
637
619
  const self = this;
638
620
  // variableLookup.sourceString can be '' when there are no incomplete params
@@ -726,6 +708,9 @@ function toCST(source /* the original file */, grammars, grammar, cstMappings, m
726
708
  tagArguments: 0,
727
709
  graphqlArguments: 0,
728
710
  graphqlRenderArguments: 1,
711
+ // Pass-through: delegates to whichever alternative matched (liquidJsonHashLiteral,
712
+ // liquidJsonArrayLiteral, or liquidExpression). Index 0 = first (and only) child.
713
+ liquidExpressionOrJsonLiteral: 0,
729
714
  positionalArgument: 0,
730
715
  namedArgument: {
731
716
  type: ConcreteNodeTypes.NamedArgument,
@@ -736,6 +721,9 @@ function toCST(source /* the original file */, grammars, grammar, cstMappings, m
736
721
  source,
737
722
  },
738
723
  namedArgumentValue: 0,
724
+ // Handles nested key:value pairs within a named argument value, e.g. `filter: type: "string"`.
725
+ // Only fires when the namedArgumentValue starts with `identifier:`.
726
+ // Produces a NamedArgument so linters can inspect the nested pair.
739
727
  hashPairValue: {
740
728
  type: ConcreteNodeTypes.NamedArgument,
741
729
  name: 0,
@@ -799,7 +787,9 @@ function toCST(source /* the original file */, grammars, grammar, cstMappings, m
799
787
  liquidDoubleQuotedString: {
800
788
  type: ConcreteNodeTypes.String,
801
789
  single: () => false,
802
- value: 1,
790
+ // body is an iteration node (strings now support `\X` escape pairs); use
791
+ // `.sourceString` to get the raw text between quotes.
792
+ value: (tokens) => tokens[1].sourceString,
803
793
  locStart,
804
794
  locEnd,
805
795
  source,
@@ -807,7 +797,7 @@ function toCST(source /* the original file */, grammars, grammar, cstMappings, m
807
797
  liquidSingleQuotedString: {
808
798
  type: ConcreteNodeTypes.String,
809
799
  single: () => true,
810
- value: 1,
800
+ value: (tokens) => tokens[1].sourceString,
811
801
  locStart,
812
802
  locEnd,
813
803
  source,
@@ -35,7 +35,7 @@
35
35
  import { ConcreteAttributeNode, ConcreteHtmlTagClose, ConcreteLiquidTagClose, LiquidCST, LiquidHtmlCST, ConcreteLiquidLiteral } from './stage-1-cst';
36
36
  import { Comparators, NamedTags, NodeTypes, Position } from './types';
37
37
  /** The union type of all possible node types inside a LiquidHTML AST. */
38
- export type LiquidHtmlNode = DocumentNode | YAMLFrontmatter | LiquidNode | HtmlDoctype | HtmlNode | AttributeNode | LiquidVariable | ComplexLiquidExpression | LiquidFilter | LiquidNamedArgument | AssignMarkup | AssignPushRhs | HashAssignMarkup | ContentForMarkup | CycleMarkup | ForMarkup | RenderMarkup | FunctionMarkup | GraphQLMarkup | GraphQLInlineMarkup | RawMarkup | RenderVariableExpression | RenderAliasExpression | LiquidLogicalExpression | LiquidComparison | TextNode | LiquidDocParamNode | LiquidDocExampleNode | LiquidDocPromptNode | LiquidDocDescriptionNode | JsonHashLiteral | JsonArrayLiteral | JsonKeyValuePair | BackgroundMarkup | BackgroundInlineMarkup | CacheMarkup | LogMarkup | SessionMarkup | ExportMarkup | RedirectToMarkup | IncludeFormMarkup | SpamProtectionMarkup;
38
+ export type LiquidHtmlNode = DocumentNode | YAMLFrontmatter | LiquidNode | HtmlDoctype | HtmlNode | AttributeNode | LiquidVariable | ComplexLiquidExpression | LiquidFilter | LiquidNamedArgument | AssignMarkup | HashAssignMarkup | ContentForMarkup | CycleMarkup | ForMarkup | RenderMarkup | FunctionMarkup | GraphQLMarkup | GraphQLInlineMarkup | RawMarkup | RenderVariableExpression | RenderAliasExpression | LiquidLogicalExpression | LiquidComparison | TextNode | LiquidDocParamNode | LiquidDocExampleNode | LiquidDocPromptNode | LiquidDocDescriptionNode | JsonHashLiteral | JsonArrayLiteral | JsonKeyValuePair | BackgroundMarkup | BackgroundInlineMarkup | CacheMarkup | LogMarkup | SessionMarkup | ExportMarkup | RedirectToMarkup | IncludeFormMarkup | SpamProtectionMarkup;
39
39
  /** The root node of all LiquidHTML ASTs. */
40
40
  export interface DocumentNode extends ASTNode<NodeTypes.Document> {
41
41
  children: LiquidHtmlNode[];
@@ -143,15 +143,8 @@ export interface AssignMarkup extends ASTNode<NodeTypes.AssignMarkup> {
143
143
  lookups: LiquidExpression[];
144
144
  /** '=' for assignment, '<<' for array append */
145
145
  operator: '=' | '<<';
146
- /** the value of the variable that is being assigned, or a push expression */
147
- value: LiquidVariable | AssignPushRhs;
148
- }
149
- /** The RHS of an explicit push assign: `assign a = source << value` */
150
- export interface AssignPushRhs extends ASTNode<NodeTypes.AssignPushRhs> {
151
- /** the source array to push into */
152
- pushSource: LiquidVariable;
153
- /** the value being pushed */
154
- pushValue: LiquidVariable;
146
+ /** the value of the variable that is being assigned */
147
+ value: LiquidVariable;
155
148
  }
156
149
  export interface LiquidTagHashAssign extends LiquidTagNode<NamedTags.hash_assign, HashAssignMarkup> {
157
150
  }
@@ -913,24 +913,12 @@ function toUnnamedLiquidBranch(parentNode) {
913
913
  };
914
914
  }
915
915
  function toAssignMarkup(node) {
916
- const value = node.value.type === stage_1_cst_1.ConcreteNodeTypes.AssignPushRhs
917
- ? toAssignPushRhs(node.value)
918
- : toLiquidVariable(node.value);
919
916
  return {
920
917
  type: types_1.NodeTypes.AssignMarkup,
921
918
  name: node.name,
922
919
  lookups: node.target.lookups.map(toExpression),
923
920
  operator: node.operator,
924
- value,
925
- position: position(node),
926
- source: node.source,
927
- };
928
- }
929
- function toAssignPushRhs(node) {
930
- return {
931
- type: types_1.NodeTypes.AssignPushRhs,
932
- pushSource: toLiquidVariable(node.pushSource),
933
- pushValue: toLiquidVariable(node.pushValue),
921
+ value: toLiquidVariable(node.value),
934
922
  position: position(node),
935
923
  source: node.source,
936
924
  };
package/dist/types.d.ts CHANGED
@@ -36,7 +36,6 @@ export declare enum NodeTypes {
36
36
  Comparison = "Comparison",
37
37
  LogicalExpression = "LogicalExpression",
38
38
  AssignMarkup = "AssignMarkup",
39
- AssignPushRhs = "AssignPushRhs",
40
39
  HashAssignMarkup = "HashAssignMarkup",
41
40
  ContentForMarkup = "ContentForMarkup",
42
41
  CycleMarkup = "CycleMarkup",
package/dist/types.js CHANGED
@@ -34,7 +34,6 @@ var NodeTypes;
34
34
  NodeTypes["Comparison"] = "Comparison";
35
35
  NodeTypes["LogicalExpression"] = "LogicalExpression";
36
36
  NodeTypes["AssignMarkup"] = "AssignMarkup";
37
- NodeTypes["AssignPushRhs"] = "AssignPushRhs";
38
37
  NodeTypes["HashAssignMarkup"] = "HashAssignMarkup";
39
38
  NodeTypes["ContentForMarkup"] = "ContentForMarkup";
40
39
  NodeTypes["CycleMarkup"] = "CycleMarkup";
@@ -130,16 +130,14 @@ Liquid <: Helpers {
130
130
 
131
131
  // Assign-specific sub-rules
132
132
  assignTarget<delim> = variableSegment lookup<delim>*
133
+ // Only one operator per assign: either `=` (regular assignment) or `<<` (push).
134
+ // The two forms are mutually exclusive — `{% assign a << "item" %}` pushes onto `a`,
135
+ // and `{% assign a = value %}` assigns to `a`. A compound shape like
136
+ // `{% assign a = b << c %}` is INVALID per the platformOS runtime.
133
137
  assignOperator = "<<" | "="
134
138
 
135
- // The RHS of an assign: either an explicit push expression (source << value) or a regular value
136
- liquidAssignValue<delim> = liquidAssignPushExpr<delim> | liquidAssignVariable<delim>
137
-
138
- // Explicit push: assign a = source << value (equivalent to assign a << value when source is a)
139
- liquidAssignPushExpr<delim> = liquidAssignPushSource<delim> space* "<<" space* liquidAssignVariable<delim>
140
-
141
- // The source expression for a push (no &delim lookahead since "<<" follows)
142
- liquidAssignPushSource<delim> = liquidAssignExpression<delim> liquidFilter<delim>* space*
139
+ // The RHS of an assign: a regular value (expression + optional filters).
140
+ liquidAssignValue<delim> = liquidAssignVariable<delim>
143
141
 
144
142
  // Assign variable: like liquidVariable but expression allows JSON literals
145
143
  liquidAssignVariable<delim> = liquidAssignExpression<delim> liquidFilter<delim>* space* &delim
@@ -148,7 +146,7 @@ Liquid <: Helpers {
148
146
  | liquidJsonArrayLiteral<delim>
149
147
  | liquidComplexExpression<delim>
150
148
 
151
- // JSON literal rules (assign-only, recursive)
149
+ // JSON literal rules (assign, return, and named argument values; recursive)
152
150
  liquidJsonHashLiteral<delim> =
153
151
  "{" space* listOf<liquidJsonKeyValue<delim>, liquidJsonSep> space* "}"
154
152
  liquidJsonKeyValue<delim> =
@@ -415,8 +413,17 @@ Liquid <: Helpers {
415
413
  booleanExpressionCondition<delim> = comparison<delim> | liquidExpression<delim>
416
414
 
417
415
  liquidString<delim> = liquidSingleQuotedString<delim> | liquidDoubleQuotedString<delim>
418
- liquidSingleQuotedString<delim> = "'" anyExceptStar<("'"| delim)> "'"
419
- liquidDoubleQuotedString<delim> = "\"" anyExceptStar<("\""| delim)> "\""
416
+ // Strings support backslash-X escape pairs (any character after a backslash is
417
+ // consumed as part of the string). This matches the platformOS runtime, which parses
418
+ // hash/array literal string values with Ruby's JSON library — so an escaped quote or
419
+ // an escaped backslash is valid inside a double-quoted string.
420
+ liquidSingleQuotedString<delim> = "'" liquidSingleQuotedStringBody<delim> "'"
421
+ liquidDoubleQuotedString<delim> = "\"" liquidDoubleQuotedStringBody<delim> "\""
422
+ liquidSingleQuotedStringBody<delim> = (liquidStringEscape | liquidSingleQuotedStringChar<delim>)*
423
+ liquidDoubleQuotedStringBody<delim> = (liquidStringEscape | liquidDoubleQuotedStringChar<delim>)*
424
+ liquidSingleQuotedStringChar<delim> = ~("'" | delim) any
425
+ liquidDoubleQuotedStringChar<delim> = ~("\"" | delim) any
426
+ liquidStringEscape = "\\" any
420
427
 
421
428
  liquidNumber = liquidFloat | liquidInteger
422
429
  liquidInteger = "-"? digit+
@@ -449,9 +456,18 @@ Liquid <: Helpers {
449
456
  arguments<delim> = nonemptyOrderedListOf<positionalArgument<delim>, namedArgument<delim>, argumentSeparator>
450
457
  argumentSeparator = space* "," space*
451
458
  argumentSeparatorOptionalComma = space* ","? space*
452
- positionalArgument<delim> = liquidExpression<delim> ~(space* ":")
459
+ // liquidExpressionOrJsonLiteral: used in argument value positions where JSON hash/array
460
+ // literals are accepted (named args, filter positional args). Does NOT extend liquidExpression
461
+ // itself so that {{ ["x"] }} drop expressions are still parsed as VariableLookup (empty lookup).
462
+ liquidExpressionOrJsonLiteral<delim> = liquidJsonHashLiteral<delim> | liquidJsonArrayLiteral<delim> | liquidExpression<delim>
463
+ positionalArgument<delim> = liquidExpressionOrJsonLiteral<delim> ~(space* ":")
453
464
  namedArgument<delim> = variableSegment space* ":" space* namedArgumentValue<delim>
454
- namedArgumentValue<delim> = hashPairValue<delim> | liquidExpression<delim>
465
+ namedArgumentValue<delim> = hashPairValue<delim> | liquidExpressionOrJsonLiteral<delim>
466
+ // hashPairValue handles nested key:value pairs within a named argument value, e.g.
467
+ // {% function res = 'path', filter: type: "string" %}
468
+ // where `type: "string"` is the hashPairValue. Only fires when the value starts with
469
+ // `identifier:` — otherwise namedArgumentValue falls through to liquidExpressionOrJsonLiteral.
470
+ // Produces a NamedArgument node so linters can inspect nested argument pairs.
455
471
  hashPairValue<delim> = variableSegment space* ":" space* liquidExpression<delim>
456
472
  tagArguments = listOf<namedArgument<delimTag>, argumentSeparatorOptionalComma>
457
473
  graphqlArguments = listOf<graphqlNamedArgument, argumentSeparatorOptionalComma>
@@ -574,10 +590,11 @@ LiquidStatement <: Liquid {
574
590
 
575
591
  // Override string rules so that '#' inside string content is never mistaken for a
576
592
  // delimTag inline-comment marker. In LiquidStatement, delimTag includes `# ...` which
577
- // would otherwise cause anyExceptStar<delim> to stop inside strings like '##label##'.
578
- // Strings in liquid blocks end at the matching quote or at a newline (no multi-line strings).
579
- liquidSingleQuotedString<delim> := "'" anyExceptStar<("'" | newline | end)> "'"
580
- liquidDoubleQuotedString<delim> := "\"" anyExceptStar<("\"" | newline | end)> "\""
593
+ // would otherwise cause the body rule to stop inside strings like '##label##'.
594
+ // Strings in liquid blocks end at the matching quote or at a newline (no multi-line
595
+ // strings), but still support `\X` escape pairs.
596
+ liquidSingleQuotedStringChar<delim> := ~("'" | newline | end) any
597
+ liquidDoubleQuotedStringChar<delim> := ~("\"" | newline | end) any
581
598
 
582
599
  // trailing whitespace, newline, + optional leading `;` and `# comment` before the next tag
583
600
  statementSep = space* ";"? space* ("#" (~newline any)*)? newline (space | newline)*
@@ -585,6 +602,27 @@ LiquidStatement <: Liquid {
585
602
  // delimTag is used as &delim lookahead in expression rules; it must succeed before `;` and `#`
586
603
  liquidStatementEnd = ";"? space* ("#" (~newline any)*)? (newline | end)
587
604
  delimTag := liquidStatementEnd
605
+
606
+ // Allow newlines inside hash/array literals within {% liquid %} blocks.
607
+ // The base Liquid grammar restricts `space` to horizontal whitespace only, but hash/array
608
+ // literals written across multiple lines need to match newlines between `{`/`[` and entries,
609
+ // between entries (around commas), and between the last entry and the closing `}`/`]`.
610
+ liquidJsonSep := (space | newline)* "," (space | newline)*
611
+ liquidJsonHashLiteral<delim> := "{" (space | newline)* listOf<liquidJsonKeyValue<delim>, liquidJsonSep> (space | newline)* "}"
612
+ liquidJsonArrayLiteral<delim> := "[" (space | newline)* listOf<liquidJsonValue<delim>, liquidJsonSep> (space | newline)* "]"
613
+
614
+ // Only the function tag supports multi-line named arguments in {% liquid %} blocks.
615
+ // render, include, and other tags use the base argumentSeparatorOptionalComma (horizontal-
616
+ // only), which prevents their argument lists from spanning lines. Function uses a dedicated
617
+ // separator that allows newlines ONLY after a comma, preventing the next statement from
618
+ // being swallowed as an argument when no comma is present.
619
+ //
620
+ // Both alternatives have 3 components (space, comma, space) to match each other's arity,
621
+ // ensuring consistent visitor child indexing.
622
+ functionArgumentSeparatorOptionalComma = space* "," (space | newline)* | space* ","? space*
623
+ functionTagArguments = listOf<namedArgument<delimTag>, functionArgumentSeparatorOptionalComma>
624
+ functionRenderArguments = (functionArgumentSeparatorOptionalComma functionTagArguments) (space* ",")? space*
625
+ liquidTagFunctionMarkup := liquidVariableLookup<delimTag> space* "=" space* partialExpression functionRenderArguments liquidFilter<delimTag>* space*
588
626
  }
589
627
 
590
628
  LiquidDoc <: Helpers {
@@ -130,16 +130,14 @@ Liquid <: Helpers {
130
130
 
131
131
  // Assign-specific sub-rules
132
132
  assignTarget<delim> = variableSegment lookup<delim>*
133
+ // Only one operator per assign: either ${"`"}=${"`"} (regular assignment) or ${"`"}<<${"`"} (push).
134
+ // The two forms are mutually exclusive — ${"`"}{% assign a << "item" %}${"`"} pushes onto ${"`"}a${"`"},
135
+ // and ${"`"}{% assign a = value %}${"`"} assigns to ${"`"}a${"`"}. A compound shape like
136
+ // ${"`"}{% assign a = b << c %}${"`"} is INVALID per the platformOS runtime.
133
137
  assignOperator = "<<" | "="
134
138
 
135
- // The RHS of an assign: either an explicit push expression (source << value) or a regular value
136
- liquidAssignValue<delim> = liquidAssignPushExpr<delim> | liquidAssignVariable<delim>
137
-
138
- // Explicit push: assign a = source << value (equivalent to assign a << value when source is a)
139
- liquidAssignPushExpr<delim> = liquidAssignPushSource<delim> space* "<<" space* liquidAssignVariable<delim>
140
-
141
- // The source expression for a push (no &delim lookahead since "<<" follows)
142
- liquidAssignPushSource<delim> = liquidAssignExpression<delim> liquidFilter<delim>* space*
139
+ // The RHS of an assign: a regular value (expression + optional filters).
140
+ liquidAssignValue<delim> = liquidAssignVariable<delim>
143
141
 
144
142
  // Assign variable: like liquidVariable but expression allows JSON literals
145
143
  liquidAssignVariable<delim> = liquidAssignExpression<delim> liquidFilter<delim>* space* &delim
@@ -148,7 +146,7 @@ Liquid <: Helpers {
148
146
  | liquidJsonArrayLiteral<delim>
149
147
  | liquidComplexExpression<delim>
150
148
 
151
- // JSON literal rules (assign-only, recursive)
149
+ // JSON literal rules (assign, return, and named argument values; recursive)
152
150
  liquidJsonHashLiteral<delim> =
153
151
  "{" space* listOf<liquidJsonKeyValue<delim>, liquidJsonSep> space* "}"
154
152
  liquidJsonKeyValue<delim> =
@@ -415,8 +413,17 @@ Liquid <: Helpers {
415
413
  booleanExpressionCondition<delim> = comparison<delim> | liquidExpression<delim>
416
414
 
417
415
  liquidString<delim> = liquidSingleQuotedString<delim> | liquidDoubleQuotedString<delim>
418
- liquidSingleQuotedString<delim> = "'" anyExceptStar<("'"| delim)> "'"
419
- liquidDoubleQuotedString<delim> = "\"" anyExceptStar<("\""| delim)> "\""
416
+ // Strings support backslash-X escape pairs (any character after a backslash is
417
+ // consumed as part of the string). This matches the platformOS runtime, which parses
418
+ // hash/array literal string values with Ruby's JSON library — so an escaped quote or
419
+ // an escaped backslash is valid inside a double-quoted string.
420
+ liquidSingleQuotedString<delim> = "'" liquidSingleQuotedStringBody<delim> "'"
421
+ liquidDoubleQuotedString<delim> = "\"" liquidDoubleQuotedStringBody<delim> "\""
422
+ liquidSingleQuotedStringBody<delim> = (liquidStringEscape | liquidSingleQuotedStringChar<delim>)*
423
+ liquidDoubleQuotedStringBody<delim> = (liquidStringEscape | liquidDoubleQuotedStringChar<delim>)*
424
+ liquidSingleQuotedStringChar<delim> = ~("'" | delim) any
425
+ liquidDoubleQuotedStringChar<delim> = ~("\"" | delim) any
426
+ liquidStringEscape = "\\" any
420
427
 
421
428
  liquidNumber = liquidFloat | liquidInteger
422
429
  liquidInteger = "-"? digit+
@@ -449,9 +456,18 @@ Liquid <: Helpers {
449
456
  arguments<delim> = nonemptyOrderedListOf<positionalArgument<delim>, namedArgument<delim>, argumentSeparator>
450
457
  argumentSeparator = space* "," space*
451
458
  argumentSeparatorOptionalComma = space* ","? space*
452
- positionalArgument<delim> = liquidExpression<delim> ~(space* ":")
459
+ // liquidExpressionOrJsonLiteral: used in argument value positions where JSON hash/array
460
+ // literals are accepted (named args, filter positional args). Does NOT extend liquidExpression
461
+ // itself so that {{ ["x"] }} drop expressions are still parsed as VariableLookup (empty lookup).
462
+ liquidExpressionOrJsonLiteral<delim> = liquidJsonHashLiteral<delim> | liquidJsonArrayLiteral<delim> | liquidExpression<delim>
463
+ positionalArgument<delim> = liquidExpressionOrJsonLiteral<delim> ~(space* ":")
453
464
  namedArgument<delim> = variableSegment space* ":" space* namedArgumentValue<delim>
454
- namedArgumentValue<delim> = hashPairValue<delim> | liquidExpression<delim>
465
+ namedArgumentValue<delim> = hashPairValue<delim> | liquidExpressionOrJsonLiteral<delim>
466
+ // hashPairValue handles nested key:value pairs within a named argument value, e.g.
467
+ // {% function res = 'path', filter: type: "string" %}
468
+ // where ${"`"}type: "string"${"`"} is the hashPairValue. Only fires when the value starts with
469
+ // ${"`"}identifier:${"`"} — otherwise namedArgumentValue falls through to liquidExpressionOrJsonLiteral.
470
+ // Produces a NamedArgument node so linters can inspect nested argument pairs.
455
471
  hashPairValue<delim> = variableSegment space* ":" space* liquidExpression<delim>
456
472
  tagArguments = listOf<namedArgument<delimTag>, argumentSeparatorOptionalComma>
457
473
  graphqlArguments = listOf<graphqlNamedArgument, argumentSeparatorOptionalComma>
@@ -574,10 +590,11 @@ LiquidStatement <: Liquid {
574
590
 
575
591
  // Override string rules so that '#' inside string content is never mistaken for a
576
592
  // delimTag inline-comment marker. In LiquidStatement, delimTag includes ${"`"}# ...${"`"} which
577
- // would otherwise cause anyExceptStar<delim> to stop inside strings like '##label##'.
578
- // Strings in liquid blocks end at the matching quote or at a newline (no multi-line strings).
579
- liquidSingleQuotedString<delim> := "'" anyExceptStar<("'" | newline | end)> "'"
580
- liquidDoubleQuotedString<delim> := "\"" anyExceptStar<("\"" | newline | end)> "\""
593
+ // would otherwise cause the body rule to stop inside strings like '##label##'.
594
+ // Strings in liquid blocks end at the matching quote or at a newline (no multi-line
595
+ // strings), but still support ${"`"}\X${"`"} escape pairs.
596
+ liquidSingleQuotedStringChar<delim> := ~("'" | newline | end) any
597
+ liquidDoubleQuotedStringChar<delim> := ~("\"" | newline | end) any
581
598
 
582
599
  // trailing whitespace, newline, + optional leading ${"`"};${"`"} and ${"`"}# comment${"`"} before the next tag
583
600
  statementSep = space* ";"? space* ("#" (~newline any)*)? newline (space | newline)*
@@ -585,6 +602,27 @@ LiquidStatement <: Liquid {
585
602
  // delimTag is used as &delim lookahead in expression rules; it must succeed before ${"`"};${"`"} and ${"`"}#${"`"}
586
603
  liquidStatementEnd = ";"? space* ("#" (~newline any)*)? (newline | end)
587
604
  delimTag := liquidStatementEnd
605
+
606
+ // Allow newlines inside hash/array literals within {% liquid %} blocks.
607
+ // The base Liquid grammar restricts ${"`"}space${"`"} to horizontal whitespace only, but hash/array
608
+ // literals written across multiple lines need to match newlines between ${"`"}{${"`"}/${"`"}[${"`"} and entries,
609
+ // between entries (around commas), and between the last entry and the closing ${"`"}}${"`"}/${"`"}]${"`"}.
610
+ liquidJsonSep := (space | newline)* "," (space | newline)*
611
+ liquidJsonHashLiteral<delim> := "{" (space | newline)* listOf<liquidJsonKeyValue<delim>, liquidJsonSep> (space | newline)* "}"
612
+ liquidJsonArrayLiteral<delim> := "[" (space | newline)* listOf<liquidJsonValue<delim>, liquidJsonSep> (space | newline)* "]"
613
+
614
+ // Only the function tag supports multi-line named arguments in {% liquid %} blocks.
615
+ // render, include, and other tags use the base argumentSeparatorOptionalComma (horizontal-
616
+ // only), which prevents their argument lists from spanning lines. Function uses a dedicated
617
+ // separator that allows newlines ONLY after a comma, preventing the next statement from
618
+ // being swallowed as an argument when no comma is present.
619
+ //
620
+ // Both alternatives have 3 components (space, comma, space) to match each other's arity,
621
+ // ensuring consistent visitor child indexing.
622
+ functionArgumentSeparatorOptionalComma = space* "," (space | newline)* | space* ","? space*
623
+ functionTagArguments = listOf<namedArgument<delimTag>, functionArgumentSeparatorOptionalComma>
624
+ functionRenderArguments = (functionArgumentSeparatorOptionalComma functionTagArguments) (space* ",")? space*
625
+ liquidTagFunctionMarkup := liquidVariableLookup<delimTag> space* "=" space* partialExpression functionRenderArguments liquidFilter<delimTag>* space*
588
626
  }
589
627
 
590
628
  LiquidDoc <: Helpers {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformos/liquid-html-parser",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Liquid HTML parser for platformOS",
5
5
  "author": "platformOS",
6
6
  "homepage": "https://github.com/Platform-OS/platformos-tools/tree/master/packages/liquid-html-parser#readme",