Skip to content

Add UnsupportedFilterArguments check#1248

Merged
NathanPJF merged 3 commits into
mainfrom
no-filters-in-rendered-arguments
Jun 28, 2026
Merged

Add UnsupportedFilterArguments check#1248
NathanPJF merged 3 commits into
mainfrom
no-filters-in-rendered-arguments

Conversation

@NathanPJF

@NathanPJF NathanPJF commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What & why

Filters aren't supported on values passed as arguments to render/include tags — but today theme-check says nothing when you write one. The value is silently passed through unchanged, so this:

  {% render 'paragraph', text: 'Hello,' | append: ' World!' %}

renders Hello, (not Hello, World!) with zero feedback making it hard to debug or notice unexpected markup in production.

Examples of what happens in a production Shopify store

Code used
<h1>Test page</h1>

{% render 'paragraph', text: 'Sample text' %}

{% render 'paragraph', text: 'Hello,' | append: ' World!' %}

{% assign id = 'unique-id' %}

{% render 'paragraph', text: 'ID:' | append: id %}

{% render 'paragraph', text: 4 | plus: 2 %}
Screenshot 2026-06-26 at 11 16 49 AM

Real world use-case:

We were creating unique IDs to pass into snippets, but making them unique inline at the render tag. The result was all the id values got rendered as the same value. 😬

Root cause: theme-check's parser is tolerant — when a tag doesn't match the strict grammar, it doesn't error, it just stores the tag as a raw text blob and moves on. The grammar intentionally doesn't allow filters on render arguments, so {% render 'foo', x: 'a' | append: 'b' %} fails the strict parse and falls back to a generic LiquidTag holding the raw string, instead of becoming the structured RenderMarkup node a valid render produces. No error is raised, which is the silent failure. It also means existing render checks (which run on RenderMarkup nodes) never see it — so this check runs on the generic LiquidTag fallback and detects the leftover filter pipe directly.

Approach

A LiquidTag-visitor check that keys off exactly that fallback:

  1. Only render / include tags.
  2. Only when typeof node.markup === 'string' (i.e. strict parse failed).
  3. Find the first | not inside a quoted string literal (so sep: 'a|b' doesn't false-positive).
  4. Report an error pointing at the filter through the end of the markup.

This throws a clean, hard error. The shared grammar is untouched.

Decision Severity is ERROR and the check is recommended (on by default), since the failure is silent and produces incorrect output. We could change this to WARNING, but I think that ERROR is appropriate since we are protecting against unexpected outputs that can affect the site's functionality.

Limitation: the guard is a heuristic on raw markup ("render/include that failed strict parse + contains an unquoted pipe"), because the AST node genuinely doesn't exist for malformed renders. This means no per-argument
precision (e.g. "filter on param2"). Acceptable given an unquoted | inside a render tag is essentially always a filter attempt.

Changes

  • New check: packages/theme-check-common/src/checks/no-filters-in-render-arguments/{index,index.spec}.ts
  • Registered in packages/theme-check-common/src/checks/index.ts
  • Generated configs (all.yml, recommended.yml) regenerated via pnpm build
  • Changeset (minor on @shopify/theme-check-common)

Testing

My test cases:

{% render 'paragraph', text: 'Sample text' %}

{% render 'paragraph', text: 'Hello,' | append: ' World!' %}

{% assign id = 'unique-id' %}

{% render 'paragraph', text: 'ID:' | append: id %}

{% render 'paragraph', text: 4 | plus: 2 %}

6 new unit specs (render filter, include filter, no-filter, 'a|b' no false positive, plain render, nested) — all green; full theme-check-common suite (959 tests) passes.

I ran the local build against my test file. 3 real offenses, 0 false positives.

  pnpm build                                                                                                         
  pnpm theme-check /path/to/theme 2>/dev/null \                                                                      
    | sed -n '/^\[/,$p' \                                                                                            
    | jq -rs '.[0][] | select(.severity == 0) | "\(.uri):\(.start.line + 1)  [\(.check)] \(.message)"'               

Output:

templates/page.blank.liquid:5 [NoFiltersInRenderArguments] Filters cannot be used on arguments passed to the 'render' tag. Apply the filter beforehand (e.g. with {% assign %}) and pass the result instead.
templates/page.blank.liquid:9 [NoFiltersInRenderArguments] Filters cannot be used on arguments passed to the 'render' tag. Apply the filter beforehand (e.g. with {% assign %}) and pass the result instead.
templates/page.blank.liquid:11 [NoFiltersInRenderArguments] Filters cannot be used on arguments passed to the 'render' tag. Apply the filter beforehand (e.g. with {% assign %}) and pass the result instead.

Co-Authored by AI

Before you deploy

Check changes

  • This PR includes a new checks or changes the configuration of a check

Public API changes, new features

  • I included a minor bump changeset
  • My feature is backward compatible

@NathanPJF NathanPJF requested a review from a team as a code owner June 26, 2026 16:05
@NathanPJF NathanPJF force-pushed the no-filters-in-rendered-arguments branch from 822c759 to 7eea0d2 Compare June 26, 2026 16:14
Comment thread packages/theme-check-common/src/checks/no-filters-in-render-arguments/index.ts Outdated
Comment thread packages/theme-check-common/src/checks/no-filters-in-render-arguments/index.ts Outdated
NathanPJF and others added 2 commits June 26, 2026 21:58
… specs

Co-authored-by: Pi AI (anthropic/claude-opus-4-8) <pi-ai@users.noreply.github.com>
Co-authored-by: Pi AI (anthropic/claude-opus-4-8) <pi-ai@users.noreply.github.com>

@NathanPJF NathanPJF left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review @mbarak . I decided to rename the check now to UnsupportedFilterArguments since it's covering more than just render now. That makes it easier to expand in the future if need be.

I also noticed that none of the other checks started with "No", so I chose something that feels closer to the existing patterns.

Going to update the Dev Docs PR now.

@NathanPJF NathanPJF changed the title Add NoFiltersInRenderArguments check Add UnsupportedFilterArguments check Jun 27, 2026
@NathanPJF NathanPJF merged commit b3f4d44 into main Jun 28, 2026
8 checks passed
@NathanPJF NathanPJF deleted the no-filters-in-rendered-arguments branch June 28, 2026 23:33
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.

2 participants