Skip to content

Commit 1f54be1

Browse files
committedNov 25, 2024
Add language support for incomplete filter parameters
We want to add autocomplete behavior for the case where a developer is typing out a liquid statement but isn't finished yet: ```liquid {{ product | image_url: █ ``` The scenario above works without any extra code but if we were to do: ```liquid {{ product | image_url: width: 100, h█ ``` Our parser would blow up because this isn't valid liquid anymore. It interprets the `h` as a positional argument which cannot come after a named argument. To support this we need to override how the grammar for liquid filters is defined in the Placeholder scenario to support this. The changes will treat the `h` as a `LiquidVariableOutput` and include proper hierarchy.

File tree

4 files changed

+47
-3
lines changed

4 files changed

+47
-3
lines changed
 

‎.changeset/pretty-maps-fix.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@shopify/liquid-html-parser': patch
3+
---
4+
5+
Add support for incomplete filter statements
6+
7+
Adds the ability to continue to parse liquid code that would otherwise be
8+
considered invalid:
9+
10+
```liquid
11+
{{ product | image_url: width: 100, c█ }}
12+
```
13+
14+
The above liquid statement will now successfully parse in all placeholder modes
15+
so that we can offer completion options for filter parameters.

‎packages/liquid-html-parser/grammar/liquid-html.ohm

+11-3
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ Liquid <: Helpers {
264264
positionalArgument<delim> = liquidExpression<delim> ~(space* ":")
265265
namedArgument<delim> = variableSegment space* ":" space* liquidExpression<delim>
266266
tagArguments = listOf<namedArgument<delimTag>, argumentSeparatorOptionalComma>
267+
filterArguments<delim> =
268+
| complexArguments<delim>
269+
| simpleArgument<delim>
270+
complexArguments<delim> = arguments<delim> (space* "," space* simpleArgument<delim>)?
271+
simpleArgument<delim> = liquidVariableLookup<delim>
267272

268273
variableSegment = (letter | "_") (~endOfTagName identifierCharacter)*
269274
variableSegmentAsLookup = variableSegment
@@ -495,18 +500,21 @@ StrictLiquidHTML <: LiquidHTML {
495500
}
496501

497502
WithPlaceholderLiquid <: Liquid {
503+
liquidFilter<delim> := space* "|" space* identifier (space* ":" space* filterArguments<delim> (space* ",")?)?
498504
liquidTagName := (letter | "█") (alnum | "_")*
499-
variableSegment := (letter | "_" | "█") identifierCharacter*
505+
variableSegment := (letter | "_" | "█") (identifierCharacter | "█")*
500506
}
501507

502508
WithPlaceholderLiquidStatement <: LiquidStatement {
509+
liquidFilter<delim> := space* "|" space* identifier (space* ":" space* filterArguments<delim> (space* ",")?)?
503510
liquidTagName := (letter | "█") (alnum | "_")*
504-
variableSegment := (letter | "_" | "█") identifierCharacter*
511+
variableSegment := (letter | "_" | "█") (identifierCharacter | "█")*
505512
}
506513

507514
WithPlaceholderLiquidHTML <: LiquidHTML {
515+
liquidFilter<delim> := space* "|" space* identifier (space* ":" space* filterArguments<delim> (space* ",")?)?
508516
liquidTagName := (letter | "█") (alnum | "_")*
509-
variableSegment := (letter | "_" | "█") identifierCharacter*
517+
variableSegment := (letter | "_" | "█") (identifierCharacter | "█")*
510518
leadingTagNameTextNode := (letter | "█") (alnum | "-" | ":" | "█")*
511519
trailingTagNameTextNode := (alnum | "-" | ":" | "█")+
512520
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,16 @@ describe('Unit: Stage 1 (CST)', () => {
13831383
expectPath(cst, '0.markup.filters.0.args.1.value.type').to.eql('VariableLookup');
13841384
expectPath(cst, '0.markup.filters.0.args.1.value.name').to.eql('█');
13851385
});
1386+
1387+
it('should parse incomplete parameters for filters', () => {
1388+
const toCST = (source: string) => toLiquidHtmlCST(source, { mode: 'completion' });
1389+
1390+
cst = toCST(`{{ a[1].foo | image_url: 200, width: 100, h█ }}`);
1391+
1392+
expectPath(cst, '0.markup.filters.0.args.0.type').to.equal('Number');
1393+
expectPath(cst, '0.markup.filters.0.args.1.type').to.equal('NamedArgument');
1394+
expectPath(cst, '0.markup.filters.0.args.2.type').to.equal('VariableLookup');
1395+
});
13861396
});
13871397

13881398
function makeExpectPath(message: string) {

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

+11
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,18 @@ function toCST<T>(
860860
}
861861
},
862862
},
863+
filterArguments: 0,
863864
arguments: 0,
865+
complexArguments: function (completeParams, _space1, _comma, _space2, incompleteParam) {
866+
const self = this as any;
867+
868+
return completeParams
869+
.toAST(self.args.mapping)
870+
.concat(
871+
incompleteParam.sourceString === '' ? [] : incompleteParam.toAST(self.args.mapping),
872+
);
873+
},
874+
simpleArgument: 0,
864875
tagArguments: 0,
865876
positionalArgument: 0,
866877
namedArgument: {

0 commit comments

Comments
 (0)
Please sign in to comment.