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 {