From 8510e73741680b1c0e3aca7a6c996d841ecec9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Fri, 16 Oct 2026 06:06:01 +0300 Subject: [PATCH 1/2] fix(theme-check-node): normalise LiquidDoc paths on Windows --- .changeset/tidy-snakes-listen.md | 5 +++ packages/theme-check-node/src/index.spec.ts | 46 +++++++++++++++++++-- packages/theme-check-node/src/index.ts | 2 +- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 .changeset/tidy-snakes-listen.md diff --git a/.changeset/tidy-snakes-listen.md b/.changeset/tidy-snakes-listen.md new file mode 100644 index 000000000..23415219e --- /dev/null +++ b/.changeset/tidy-snakes-listen.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme-check-node': patch +--- + +Fix cross-file LiquidDoc checks on Windows by normalising snippet paths before lookup. diff --git a/packages/theme-check-node/src/index.spec.ts b/packages/theme-check-node/src/index.spec.ts index 04883b333..21573b4fd 100644 --- a/packages/theme-check-node/src/index.spec.ts +++ b/packages/theme-check-node/src/index.spec.ts @@ -1,7 +1,8 @@ -import { afterEach, assert, beforeEach, describe, expect, it } from 'vitest'; -import { Config, SourceCodeType, getTheme, getThemeFilesPathPattern } from './index'; +import { afterEach, assert, beforeEach, describe, expect, it, vi } from 'vitest'; +import path from 'node:path'; +import { Config, SourceCodeType, getTheme, getThemeFilesPathPattern, themeCheckRun } from './index'; import { Workspace, makeTempWorkspace } from './test/test-helpers'; -import { pathToFileURL } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; describe('Unit: getTheme', () => { let workspace: Workspace; @@ -49,3 +50,42 @@ describe('Unit: getThemeFilesPathPattern', () => { expect(normalizedGlob).to.equal(__dirname.replace(/\\/g, '/') + '/**/*.{liquid,json}'); }); }); + +describe('Integration: themeCheckRun', () => { + it('reports LiquidDoc errors across files when paths use Windows separators', async () => { + const workspace = await makeTempWorkspace({ + locales: { + 'en.default.json': '{}', + }, + sections: { + 'main.liquid': "{% render 'card' %}", + }, + snippets: { + 'card.liquid': ` + {% doc %} + @param {string} title - The card title + {% enddoc %} + `, + }, + }); + const originalRelative = path.relative; + + const relativeSpy = vi.spyOn(path, 'relative').mockImplementation((from, to) => { + const relative = originalRelative(from, to); + if (typeof from === 'string' && from.startsWith('file:')) { + return relative.replace(/\//g, '\\'); + } + return relative; + }); + + try { + const { offenses } = await themeCheckRun(fileURLToPath(workspace.rootUri)); + expect( + offenses.filter(({ check }) => check === 'MissingRenderSnippetArguments'), + ).toHaveLength(1); + } finally { + relativeSpy.mockRestore(); + await workspace.clean(); + } + }); +}); diff --git a/packages/theme-check-node/src/index.ts b/packages/theme-check-node/src/index.ts index 22cb13827..0a876f901 100644 --- a/packages/theme-check-node/src/index.ts +++ b/packages/theme-check-node/src/index.ts @@ -125,7 +125,7 @@ export async function themeCheckRun( ])); const docDefinitions = new Map( theme.map((file) => [ - path.relative(URI.file(root).toString(), file.uri), + pathUtils.relative(file.uri, URI.file(root).toString()), memo(async (): Promise => { const ast = file.ast; if (!isLiquidHtmlNode(ast)) { From cbc19c09970c24d5a69543cf1eca43c48d7fb9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Wed, 28 Oct 2026 05:23:03 +0300 Subject: [PATCH 2/2] Fix LiquidDoc root URI normalisation Generated-by: Codex --- packages/theme-check-node/src/index.spec.ts | 26 +++++++++++-------- packages/theme-check-node/src/index.ts | 2 +- .../theme-check-node/src/test/test-helpers.ts | 7 +++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/theme-check-node/src/index.spec.ts b/packages/theme-check-node/src/index.spec.ts index 21573b4fd..c776faf2c 100644 --- a/packages/theme-check-node/src/index.spec.ts +++ b/packages/theme-check-node/src/index.spec.ts @@ -1,4 +1,5 @@ import { afterEach, assert, beforeEach, describe, expect, it, vi } from 'vitest'; +import os from 'node:os'; import path from 'node:path'; import { Config, SourceCodeType, getTheme, getThemeFilesPathPattern, themeCheckRun } from './index'; import { Workspace, makeTempWorkspace } from './test/test-helpers'; @@ -52,22 +53,25 @@ describe('Unit: getThemeFilesPathPattern', () => { }); describe('Integration: themeCheckRun', () => { - it('reports LiquidDoc errors across files when paths use Windows separators', async () => { - const workspace = await makeTempWorkspace({ - locales: { - 'en.default.json': '{}', - }, - sections: { - 'main.liquid': "{% render 'card' %}", - }, - snippets: { - 'card.liquid': ` + it('reports cross-file LiquidDoc errors with Windows separators and spaces in the root path', async () => { + const workspace = await makeTempWorkspace( + { + locales: { + 'en.default.json': '{}', + }, + sections: { + 'main.liquid': "{% render 'card' %}", + }, + snippets: { + 'card.liquid': ` {% doc %} @param {string} title - The card title {% enddoc %} `, + }, }, - }); + path.join(os.tmpdir(), 'theme check.XXXXX'), + ); const originalRelative = path.relative; const relativeSpy = vi.spyOn(path, 'relative').mockImplementation((from, to) => { diff --git a/packages/theme-check-node/src/index.ts b/packages/theme-check-node/src/index.ts index 0a876f901..5dc08c1fb 100644 --- a/packages/theme-check-node/src/index.ts +++ b/packages/theme-check-node/src/index.ts @@ -125,7 +125,7 @@ export async function themeCheckRun( ])); const docDefinitions = new Map( theme.map((file) => [ - pathUtils.relative(file.uri, URI.file(root).toString()), + pathUtils.relative(file.uri, pathUtils.normalize(URI.file(root))), memo(async (): Promise => { const ast = file.ast; if (!isLiquidHtmlNode(ast)) { diff --git a/packages/theme-check-node/src/test/test-helpers.ts b/packages/theme-check-node/src/test/test-helpers.ts index c55af1ec3..7efe8a7e3 100644 --- a/packages/theme-check-node/src/test/test-helpers.ts +++ b/packages/theme-check-node/src/test/test-helpers.ts @@ -74,8 +74,11 @@ export interface Workspace { clean(): Promise; } -export async function makeTempWorkspace(structure: Tree): Promise { - const root = await mktemp.createDir(path.join(__dirname, '..', '.XXXXX')); +export async function makeTempWorkspace( + structure: Tree, + rootTemplate = path.join(__dirname, '..', '.XXXXX'), +): Promise { + const root = await mktemp.createDir(rootTemplate); if (!root) throw new Error('Could not create temp dir for temp workspace'); await createFiles(structure, [root]);