From d51fa7eba2e369fd6212cdc59ebaef71e2024f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Fri, 10 Jul 2026 23:03:17 -0400 Subject: [PATCH] fix(lint): flag digit-leading element ids (#2222) --- packages/lint/src/rules/core.test.ts | 31 ++++++++++++++++++++++++++++ packages/lint/src/rules/core.ts | 19 +++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/packages/lint/src/rules/core.test.ts b/packages/lint/src/rules/core.test.ts index 15341e0f7..f3c001435 100644 --- a/packages/lint/src/rules/core.test.ts +++ b/packages/lint/src/rules/core.test.ts @@ -77,6 +77,37 @@ ${headContent} } describe("core rules", () => { + it("warns when an id starts with a digit and is unsafe in a hash selector", async () => { + const html = ` + +
+
+
+ +`; + + const result = await lintHyperframeHtml(html); + const finding = result.findings.find((item) => item.code === "id_requires_css_escape"); + + expect(finding?.severity).toBe("warning"); + expect(finding?.elementId).toBe("123-frame"); + expect(finding?.fixHint).toContain("CSS.escape"); + }); + + it("accepts ids that start with a letter", async () => { + const html = ` + +
+
+
+ +`; + + const result = await lintHyperframeHtml(html); + + expect(result.findings.find((item) => item.code === "id_requires_css_escape")).toBeUndefined(); + }); + it("reports error when root is missing data-composition-id", async () => { const html = ` diff --git a/packages/lint/src/rules/core.ts b/packages/lint/src/rules/core.ts index 016fa2584..d297d4a76 100644 --- a/packages/lint/src/rules/core.ts +++ b/packages/lint/src/rules/core.ts @@ -181,6 +181,25 @@ function findVisibleMarkupCommentLeak(source: string): string | null { } export const coreRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> = [ + // id_requires_css_escape + ({ tags }) => { + const findings: HyperframeLintFinding[] = []; + for (const tag of tags) { + const id = readAttr(tag.raw, "id"); + if (!id || !/^\d/.test(id)) continue; + findings.push({ + code: "id_requires_css_escape", + severity: "warning", + message: `id="${id}" starts with a digit, so the common selector \`#${id}\` throws a SyntaxError in querySelector().`, + elementId: id, + fixHint: + "Rename the id to start with a letter (recommended), or build selectors with `#${CSS.escape(id)}` at runtime.", + snippet: truncateSnippet(tag.raw), + }); + } + return findings; + }, + // root_missing_composition_id + root_missing_dimensions ({ rootTag }) => { const findings: HyperframeLintFinding[] = [];