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)) {