From 350df3b9e93a434cf3bc7c492c48eb672c305e17 Mon Sep 17 00:00:00 2001 From: rushikeshmore Date: Sat, 18 Apr 2026 14:08:01 +0530 Subject: [PATCH] Parse style body as Liquid inside inline {% liquid %} tags The inline-liquid raw-tag CST mapping parsed every raw-tag body as plain text. As a result, variable references inside {% liquid ... style ... echo foo ... endstyle %} never produced AST nodes, so checks that walk the tree saw no usage and flagged assigns as unused. The top-level liquidRawTagImpl already distinguishes schema/raw (kept as text) from other raw tags like style (re-parsed as Liquid so children are part of the AST). The inline-liquid mapping now mirrors this: schema and raw stay text; everything else is re-parsed with the LiquidStatement grammar so references inside style and friends are preserved. Added a regression test in UnusedAssign that reproduces the exact snippet from the original report. Full vitest suite (294 files, 1860 tests) remains green. Closes #465 --- .../fix-unused-assign-inline-liquid-style.md | 5 +++ .../liquid-html-parser/src/stage-1-cst.ts | 40 +++++++++++++++---- .../src/checks/unused-assign/index.spec.ts | 19 +++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 .changeset/fix-unused-assign-inline-liquid-style.md diff --git a/.changeset/fix-unused-assign-inline-liquid-style.md b/.changeset/fix-unused-assign-inline-liquid-style.md new file mode 100644 index 000000000..4c63e6c8f --- /dev/null +++ b/.changeset/fix-unused-assign-inline-liquid-style.md @@ -0,0 +1,5 @@ +--- +'@shopify/liquid-html-parser': patch +--- + +Fix false-positive UnusedAssign warnings for variables used inside a `style` block within an inline `{% liquid %}` tag. The inline-liquid raw-tag CST mapping parsed every raw-tag body as plain text, so variable references inside `{% liquid ... style ... echo foo ... endstyle %}` never became AST nodes and checks that walk the tree saw no usage. The mapping now mirrors the top-level `liquidRawTagImpl` behavior: `schema` and `raw` remain text, all other raw tags are parsed as Liquid so references inside `style` and friends are preserved. Fixes Shopify/theme-tools#465. diff --git a/packages/liquid-html-parser/src/stage-1-cst.ts b/packages/liquid-html-parser/src/stage-1-cst.ts index 35f3e9a9b..17b02b2ec 100644 --- a/packages/liquid-html-parser/src/stage-1-cst.ts +++ b/packages/liquid-html-parser/src/stage-1-cst.ts @@ -1212,14 +1212,38 @@ function toCST( name: 0, body: 4, children(nodes) { - return toCST( - source, - grammars, - TextNodeGrammar, - ['HelperMappings'], - nodes[4].sourceString, - offset + nodes[4].source.startIdx, - ); + const nameNode = nodes[0]; + const rawMarkupStringNode = nodes[4]; + switch (nameNode.sourceString) { + // {% schema %} parses its content as a string and should not be visited + case 'schema': + // {% raw %} accepts syntax errors, we shouldn't try to parse that + case 'raw': { + return toCST( + source, + grammars, + TextNodeGrammar, + ['HelperMappings'], + rawMarkupStringNode.sourceString, + offset + rawMarkupStringNode.source.startIdx, + ); + } + + // Match the top-level liquidRawTagImpl behavior so variables + // referenced inside {% liquid ... style ... endstyle %} are + // detected as used by checks that walk the AST. The body is + // still inline-liquid, so re-parse with LiquidStatement. + default: { + return toCST( + source, + grammars, + grammars.LiquidStatement, + ['HelperMappings', 'LiquidMappings', 'LiquidStatement'], + rawMarkupStringNode.sourceString, + offset + rawMarkupStringNode.source.startIdx, + ); + } + } }, whitespaceStart: null, whitespaceEnd: null, diff --git a/packages/theme-check-common/src/checks/unused-assign/index.spec.ts b/packages/theme-check-common/src/checks/unused-assign/index.spec.ts index f5575077a..0ea18789a 100644 --- a/packages/theme-check-common/src/checks/unused-assign/index.spec.ts +++ b/packages/theme-check-common/src/checks/unused-assign/index.spec.ts @@ -114,6 +114,25 @@ describe('Module: UnusedAssign', () => { } }); + it('should not report unused assigns for variables used inside a style block within {% liquid %}', async () => { + const sourceCode = ` + {%- liquid + assign shopName = shop.name + capture example + echo 'Shopify' + endcapture + + style + echo example + echo shopName + endstyle + -%} + `; + + const offenses = await runLiquidCheck(UnusedAssign, sourceCode); + expect(offenses).to.be.empty; + }); + it('should not report unused assigns for things used in a HTML raw-like tag', async () => { const tags = ['style', 'script']; for (const tag of tags) {