Skip to content

Add go-to-definition for variable references → assign tags#1154

Draft
jamesmengo wants to merge 4 commits intomainfrom
jm/variable-go-to-definition
Draft

Add go-to-definition for variable references → assign tags#1154
jamesmengo wants to merge 4 commits intomainfrom
jm/variable-go-to-definition

Conversation

@jamesmengo
Copy link
Contributor

What

When a user Cmd+Clicks (or an agent calls textDocument/definition) on a variable like {{ my_var }}, the LSP now jumps to the {% assign my_var = ... %} that defines it.

Why

This is the highest-value goToDefinition gap — no existing LSP feature provides this navigation today. It helps both human developers and AI agents navigate Liquid codebases.

How

New VariableDefinitionProvider registered alongside the existing translation definition providers:

  1. Detects VariableLookup nodes with a non-null name
  2. Visits the AST collecting all AssignMarkup nodes with matching name
  3. Picks the last assign before the cursor offset (handles shadowing)
  4. Returns a LocationLink from the variable reference to the assign tag

Shadowing example

{% assign x = 1 %}
{{ x }}              ← jumps to first assign
{% assign x = 2 %}
{{ x }}              ← jumps to second assign

Tests

9 new tests covering:

  • Basic assign → reference navigation
  • Variable shadowing (each reference resolves to correct assign)
  • Global/contextual variables return null (no false positives)
  • VariableLookup with name = null returns null
  • Variable used before any assign returns null
  • Variables with filters
  • Multiple distinct variable names

All 13 definition tests pass (4 existing + 9 new).

Future work

This covers {% assign %} only. Follow-up tasks planned for:

  • {% capture %}
  • {% for %} / {% tablerow %} (block-scoped)
  • @param (snippet parameters)
  • {% increment %} / {% decrement %}

jamesmengo and others added 4 commits March 16, 2026 16:05
When a user Cmd+Clicks on a variable like {{ my_var }}, the LSP now
jumps to the {% assign my_var = ... %} that defines it.

- New VariableDefinitionProvider handles VariableLookup → AssignMarkup
- Shadowing works: later assigns win for later references
- Global/contextual variables return null (no false positives)
- 9 new tests, all 13 definition tests pass

This covers assign only. Capture, for, tablerow, and @param are
planned as follow-up tasks.

Co-authored-by: Claude <noreply@anthropic.com>
Instead of picking only the last assign before cursor, return all of
them. This handles conditional branches (if/else) where multiple
assigns could be the definition at runtime:

  {% if condition %}
    {% assign x = "from if" %}
  {% else %}
    {% assign x = "from else" %}
  {% endif %}
  {{ x }}  ← peek menu shows both assigns

Single assign still jumps directly. Multiple results trigger the
editor's peek list — standard LSP pattern for ambiguous definitions,
same as TypeScript does for overloaded methods.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: AI <noreply@shopify.com>
Co-authored-by: AI <noreply@shopify.com>
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.

1 participant