From 7354c7ae664bc8881b7bd830ce08607aa90f798a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Sat, 3 Oct 2026 14:42:24 +0300 Subject: [PATCH] fix(theme-graph): report Liquid parse failures --- .../calm-rivers-report-graph-parse-errors.md | 5 ++++ packages/theme-graph/src/graph/build.spec.ts | 28 +++++++++++++++++++ packages/theme-graph/src/graph/build.ts | 1 + packages/theme-graph/src/graph/serialize.ts | 1 + packages/theme-graph/src/graph/traverse.ts | 5 +++- packages/theme-graph/src/types.ts | 7 +++++ 6 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 .changeset/calm-rivers-report-graph-parse-errors.md diff --git a/.changeset/calm-rivers-report-graph-parse-errors.md b/.changeset/calm-rivers-report-graph-parse-errors.md new file mode 100644 index 000000000..b6486f613 --- /dev/null +++ b/.changeset/calm-rivers-report-graph-parse-errors.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme-graph': patch +--- + +Include Liquid parse errors in the theme graph and its serialized output. diff --git a/packages/theme-graph/src/graph/build.spec.ts b/packages/theme-graph/src/graph/build.spec.ts index 821089947..4b065cc43 100644 --- a/packages/theme-graph/src/graph/build.spec.ts +++ b/packages/theme-graph/src/graph/build.spec.ts @@ -1,7 +1,9 @@ import { path as pathUtils, SourceCodeType } from '@shopify/theme-check-common'; import { assert, beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { buildThemeGraph } from '../index'; +import { toSourceCode } from '../toSourceCode'; import { Dependencies, JsonModuleKind, LiquidModuleKind, ModuleType, ThemeGraph } from '../types'; +import { serializeThemeGraph } from './serialize'; import { getDependencies, skeleton, themeAppExtension } from './test-helpers'; describe('Module: index', () => { @@ -20,6 +22,32 @@ describe('Module: index', () => { expect(graph).toBeDefined(); }); + it('reports Liquid files that fail to parse', async () => { + const uri = p('sections/custom-section.liquid'); + const brokenSource = await toSourceCode( + uri, + `{{ 'used-by-broken.css' | asset_url | stylesheet_tag }}\n{% if true %}
x
`, + ); + assert(brokenSource.type === SourceCodeType.LiquidHtml); + assert(brokenSource.ast instanceof Error); + + const graph = await buildThemeGraph( + rootUri, + { + ...dependencies, + getSourceCode: async (sourceUri) => + sourceUri === uri ? brokenSource : dependencies.getSourceCode(sourceUri), + }, + [uri], + ); + + expect(graph.parseErrors).toEqual([ + expect.objectContaining({ uri, message: expect.any(String) }), + ]); + expect(graph.modules[p('assets/used-by-broken.css')]).toBeUndefined(); + expect(serializeThemeGraph(graph).parseErrors).toEqual(graph.parseErrors); + }); + describe('with a valid theme graph', () => { let graph: ThemeGraph; diff --git a/packages/theme-graph/src/graph/build.ts b/packages/theme-graph/src/graph/build.ts index 594b07557..44d5b60e0 100644 --- a/packages/theme-graph/src/graph/build.ts +++ b/packages/theme-graph/src/graph/build.ts @@ -43,6 +43,7 @@ export async function buildThemeGraph( const graph: ThemeGraph = { entryPoints: [], modules: {}, + parseErrors: [], rootUri, }; diff --git a/packages/theme-graph/src/graph/serialize.ts b/packages/theme-graph/src/graph/serialize.ts index a3e4e505d..58d329e7f 100644 --- a/packages/theme-graph/src/graph/serialize.ts +++ b/packages/theme-graph/src/graph/serialize.ts @@ -16,5 +16,6 @@ export function serializeThemeGraph(graph: ThemeGraph): SerializableGraph { rootUri: graph.rootUri, nodes, edges, + parseErrors: graph.parseErrors ?? [], }; } diff --git a/packages/theme-graph/src/graph/traverse.ts b/packages/theme-graph/src/graph/traverse.ts index 2751b5b13..f7b97b053 100644 --- a/packages/theme-graph/src/graph/traverse.ts +++ b/packages/theme-graph/src/graph/traverse.ts @@ -92,7 +92,10 @@ async function traverseLiquidModule( ) { const sourceCode = await deps.getSourceCode(module.uri); - if (sourceCode.ast instanceof Error) return; // can't visit what you can't parse + if (sourceCode.ast instanceof Error) { + themeGraph.parseErrors?.push({ uri: module.uri, message: sourceCode.ast.message }); + return; // can't visit what you can't parse + } const visitor: Visitor< SourceCodeType.LiquidHtml, diff --git a/packages/theme-graph/src/types.ts b/packages/theme-graph/src/types.ts index 5aba6f3f9..8961c19ef 100644 --- a/packages/theme-graph/src/types.ts +++ b/packages/theme-graph/src/types.ts @@ -48,6 +48,12 @@ export interface ThemeGraph { rootUri: UriString; entryPoints: ThemeModule[]; modules: Record; + parseErrors?: GraphParseError[]; +} + +export interface GraphParseError { + uri: UriString; + message: string; } export type ThemeModule = @@ -70,6 +76,7 @@ export interface SerializableGraph { rootUri: UriString; nodes: SerializableNode[]; edges: SerializableEdge[]; + parseErrors?: GraphParseError[]; } export interface SerializableEdge {