diff --git a/packages/core/src/compiler/compositionScoping.test.ts b/packages/core/src/compiler/compositionScoping.test.ts index 7ba39a5fb..80a63c059 100644 --- a/packages/core/src/compiler/compositionScoping.test.ts +++ b/packages/core/src/compiler/compositionScoping.test.ts @@ -1054,4 +1054,13 @@ describe("wrapInlineScriptWithErrorBoundary — { + const malformedCss = `body { margin: 0; overflow: hidden; } +:root { --accent: #5ef17c; } +.stage { position: absolute; inset: 0; } +.broken { transform: xPercent: -10; }`; + const result = scopeCssToComposition(malformedCss, "scene-bad"); + expect(result).toBe(""); + }); }); diff --git a/packages/core/src/compiler/compositionScoping.ts b/packages/core/src/compiler/compositionScoping.ts index c1fc99620..2ae8d9ee7 100644 --- a/packages/core/src/compiler/compositionScoping.ts +++ b/packages/core/src/compiler/compositionScoping.ts @@ -230,7 +230,12 @@ export function scopeCssToComposition( const scope = scopeSelectorOverride || `[data-composition-id="${escapeCssAttributeValue(trimmedCompositionId)}"]`; - const root = postcss.parse(css); + let root: postcss.Root; + try { + root = postcss.parse(css); + } catch { + return ""; + } root.walkRules((rule) => { if (isInsideGlobalAtRule(rule)) return; diff --git a/packages/lint/src/rules/core.test.ts b/packages/lint/src/rules/core.test.ts index b0b870d80..1bc0a21e8 100644 --- a/packages/lint/src/rules/core.test.ts +++ b/packages/lint/src/rules/core.test.ts @@ -777,4 +777,20 @@ describe("core rules", () => { expect(result.findings.find((f) => f.code === "timeline_id_mismatch")).toBeDefined(); }); }); + + describe("css_parse_error — malformed CSS is reported instead of silently swallowed", () => { + it("reports a css_parse_error finding for unparseable CSS", async () => { + const html = `
+ + + + + `; + const result = await lintHyperframeHtml(html); + const finding = result.findings.find((f) => f.code === "css_parse_error"); + expect(finding).toBeDefined(); + expect(finding?.severity).toBe("error"); + expect(finding?.message).toContain("Missed semicolon"); + }); + }); }); diff --git a/packages/lint/src/rules/core.ts b/packages/lint/src/rules/core.ts index 3461a5942..8d784da2f 100644 --- a/packages/lint/src/rules/core.ts +++ b/packages/lint/src/rules/core.ts @@ -312,7 +312,12 @@ export const coreRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> = [ let root: postcss.Root; try { root = postcss.parse(style.content); - } catch { + } catch (error) { + findings.push({ + code: "css_parse_error", + severity: "error", + message: `CSS parse error: ${error instanceof Error ? error.message : "unknown"}`, + }); continue; } root.walkRules((rule) => {