Skip to content

fix(parser): allow dot-lookup property names to start with a digit#1196

Closed
SinhSinhAn wants to merge 1 commit intoShopify:mainfrom
SinhSinhAn:fix/autocomplete-digit-property-names
Closed

fix(parser): allow dot-lookup property names to start with a digit#1196
SinhSinhAn wants to merge 1 commit intoShopify:mainfrom
SinhSinhAn:fix/autocomplete-digit-property-names

Conversation

@SinhSinhAn
Copy link
Copy Markdown
Contributor

What is this PR?

Fixes #858. The language server was silently dropping autocomplete after a property whose name started with a digit:

{{ address.2country.█ }}   <- no completions offered for `name` or `code`
{{ address.country.█ }}    <- completions offered

What was the issue?

The parser grammar (liquid-html.ohm) defined:

dotLookup         = space* "." space* identifier
identifier        = variableSegment "?"?
variableSegment   = (letter | "_") (~endOfTagName identifierCharacter)*

Every dot-lookup segment was funnelled through variableSegment, which only accepts letter or _ as the first character. Any property whose name started with a digit (e.g. 2country) was treated as unparseable text.

Downstream effect on completion: createLiquidCompletionParams appends the cursor placeholder and calls toLiquidHtmlAST(..., { mode: 'completion' }). When that parse throws or falls through to the base-case fallback, completionContext is set to undefined, and ObjectAttributeCompletionProvider returns [] at its first guard (if (!params.completionContext) return []).

The fix

Introduce a new propertyName rule that mirrors identifier but allows leading digits, and use it in dotLookup only. variableSegment stays restricted so variable declarations, filter names, for/capture targets, and named arguments continue to reject leading digits.

dotLookup    = space* "." space* propertyName
propertyName = (letter | "_" | digit) (~endOfTagName identifierCharacter)* "?"?

Matching overrides are added to the three WithPlaceholder* grammars so that the completion-mode placeholder is also accepted in property-name position:

propertyName := (letter | "_" | digit | "█") (identifierCharacter | "█")* "?"?

Why this is the right scope

Shopify actually supports property names that start with a digit — the issue's reproduction is a legitimate custom-object definition accepted by the platform. The grammar was incorrectly rejecting valid Liquid. Narrowing the fix to dotLookup (rather than loosening identifier or variableSegment) preserves existing constraints where they matter:

  • Variable declarations ({% assign 2x = 1 %}) still fail — correct, Liquid forbids this
  • Filter names ({{ x | 2upcase }}) still fail — correct
  • for / capture loop variables still reject leading digits — correct
  • Named arguments still reject leading digits — correct
  • Only .propertyName in a dot chain accepts leading digits — the only place it should

Verification

Minimal reproduction:

toLiquidHtmlCST('{{ address.2country.name }}');
// → VariableLookup { name: "address", lookups: ["2country", "name"] }

toLiquidHtmlCST('{{ address.2country.█ }}', { mode: 'completion' });
// → VariableLookup { name: "address", lookups: ["2country", "█"] }

The completion provider now gets a valid completionContext with the cursor positioned inside a dot lookup on 2country, so it walks the object graph normally and offers the nested properties.

Tests

Added test cases in stage-1-cst.spec.ts:

  • address.2country parses with lookups ["2country"]
  • address.2country.name parses with lookups ["2country", "name"]
  • Completion mode: {{ address.2country.█ }} parses with lookups ["2country", "█"] so completion providers can resolve the parent object and offer its properties

Regression coverage

All 1,592 existing tests still pass across liquid-html-parser (162), theme-check-common (856), prettier-plugin-liquid (141), and theme-language-server-common (433). Zero regressions.

Closes #858

Shopify objects and theme customization can define property names
that start with a digit (e.g. `address.2country`). The parser grammar
required every segment of a dot chain to match `identifier`, which
itself requires `variableSegment = (letter | "_") ...`. Property
names starting with a digit were treated as garbage text, and the
language server's completion context was silently dropped when the
cursor sat just after such a property.

Introduce a dedicated `propertyName` rule that accepts leading digits
and use it in `dotLookup` only. `variableSegment` stays restricted so
variable declarations, filter names, and `for`/`capture`/named-argument
targets still reject leading digits. Overrides added to the three
`WithPlaceholder*` grammars so completion mode accepts the placeholder
character in digit-starting property positions too.

Closes Shopify#858
@SinhSinhAn SinhSinhAn requested a review from a team as a code owner April 24, 2026 20:35
@graygilmore
Copy link
Copy Markdown
Contributor

Thanks for the contribution! In the near future with all themes moving to Liquid's new strict parser this will not be valid Liquid. The lookup will need to be {{ address['2country'] }}. With that in mind I'm going to close this and the related issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No autocomplete offered for nested properties when key starts with number

2 participants