From b9a862fbe2a993078c446ebe1f6f51aac2945767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claud=C3=A9ric=20Demers?= Date: Tue, 20 Oct 2026 09:02:50 -0400 Subject: [PATCH] Rebuild a theme graph that failed to build ThemeGraphManager caches each graph as a promise. On a file change, processQueue awaited the cached promise before replacing it. When the build had failed, that await threw, so the graph was never rebuilt and every request got the same error until the language server restarted. It now still waits for a build in progress, but rebuilds whether that build succeeded or failed. Co-Authored-By: Claude Opus 5.5 --- .changeset/graph-rebuild-after-error.md | 5 +++ .../src/server/ThemeGraphManager.spec.ts | 45 +++++++++++++++++++ .../src/server/ThemeGraphManager.ts | 4 +- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .changeset/graph-rebuild-after-error.md create mode 100644 packages/theme-language-server-common/src/server/ThemeGraphManager.spec.ts diff --git a/.changeset/graph-rebuild-after-error.md b/.changeset/graph-rebuild-after-error.md new file mode 100644 index 000000000..3c386807e --- /dev/null +++ b/.changeset/graph-rebuild-after-error.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme-language-server-common': patch +--- + +Rebuild a theme graph that failed to build when a file changes. Before, the failed graph stayed until the language server restarted. diff --git a/packages/theme-language-server-common/src/server/ThemeGraphManager.spec.ts b/packages/theme-language-server-common/src/server/ThemeGraphManager.spec.ts new file mode 100644 index 000000000..0b94d41e1 --- /dev/null +++ b/packages/theme-language-server-common/src/server/ThemeGraphManager.spec.ts @@ -0,0 +1,45 @@ +import { AbstractFileSystem, path } from '@shopify/theme-check-common'; +import { MockFileSystem } from '@shopify/theme-check-common/src/test'; +import { describe, expect, it, vi } from 'vitest'; +import { DocumentManager } from '../documents'; +import { mockConnection } from '../test/MockConnection'; +import { ThemeGraphManager } from './ThemeGraphManager'; + +describe('Module: ThemeGraphManager', () => { + it('rebuilds a graph that failed to build when a file changes', async () => { + const rootUri = path.normalize('file:/'); + const mockFs = new MockFileSystem( + { + 'assets/theme.js': '', + 'sections/main.liquid': "{% render 'card' %}", + 'snippets/card.liquid': '', + }, + rootUri, + ); + let broken = true; + const fs: AbstractFileSystem = { + stat: (uri) => mockFs.stat(uri), + readFile: (uri) => mockFs.readFile(uri), + readDirectory: (uri) => + broken ? Promise.reject(new Error('EIO')) : mockFs.readDirectory(uri), + }; + const connection = mockConnection(rootUri); + const manager = new ThemeGraphManager( + connection, + new DocumentManager(fs), + fs, + async () => rootUri, + ); + const card = path.join(rootUri, 'snippets', 'card.liquid'); + + await expect(manager.getReferences(card)).rejects.toThrow('EIO'); + + broken = false; + vi.useFakeTimers(); + manager.change(card); + await vi.runAllTimersAsync(); + vi.useRealTimers(); + + expect(await manager.getReferences(card)).toHaveLength(1); + }); +}); diff --git a/packages/theme-language-server-common/src/server/ThemeGraphManager.ts b/packages/theme-language-server-common/src/server/ThemeGraphManager.ts index a83bcbced..63eb6881d 100644 --- a/packages/theme-language-server-common/src/server/ThemeGraphManager.ts +++ b/packages/theme-language-server-common/src/server/ThemeGraphManager.ts @@ -208,8 +208,10 @@ export class ThemeGraphManager { const rootUri = await this.findThemeRootURI(anyUri); if (!rootUri) return; - const graph = await this.graphs.get(rootUri); + const graph = this.graphs.get(rootUri); if (!graph) return; + // Let a build in progress finish first. A graph that failed to build is rebuilt too. + await graph.catch(() => {}); this.graphs.delete(rootUri); await this.getThemeGraphForURI(rootUri);