From 6a096cfd350286446f610f31b87e2b6290ad29c0 Mon Sep 17 00:00:00 2001 From: cevdet <76725496+cevdetch@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:20:39 +0200 Subject: [PATCH] fix(build): match helper imports literally Escape helper export names and module filenames in the usage check, and recognize dollar signs as part of JavaScript identifiers. Add regressions for complete identifiers and literal module filenames. --- scripts/rules-source.ts | 2 +- test/rules.test.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/scripts/rules-source.ts b/scripts/rules-source.ts index a70bcca..d201722 100644 --- a/scripts/rules-source.ts +++ b/scripts/rules-source.ts @@ -385,7 +385,7 @@ export async function checkLibModules(libDir: string = LIB_DIR, logicDir: string const name = m[1]!; // `import { x }`, `import type { X }` and `import { type X }` all count. const used = new RegExp( - `import\\s*(?:type\\s+)?\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*["']\\.\\./\\.\\./lib/${file}["']`, + `import\\s*(?:type\\s+)?\\{[^}]*(? { + const { checkLibModules } = await import("../scripts/rules-source.ts"); + const dir = await mkdtemp(join(tmpdir(), "deadhead-lib-")); + t.after(() => rm(dir, { recursive: true, force: true })); + const lib = join(dir, "lib"); + const logic = join(dir, "logic", "meta"); + await mkdir(lib, { recursive: true }); + await mkdir(logic, { recursive: true }); + await writeFile(join(lib, "x.ts"), "export const $leading = 1;\nexport const trailing$ = 2;\nexport const leading = 3;\nexport const trailing = 4;\n"); + await writeFile(join(logic, "a.ts"), 'import { $leading, trailing$ } from "../../lib/x.ts";\n'); + assert.deepEqual((await checkLibModules(lib, join(dir, "logic"))).map((d) => d.message), [ + "`leading` is exported but no logic module imports it", + "`trailing` is exported but no logic module imports it", + ]); +}); + +test("checkLibModules treats module filenames literally", async (t) => { + const { checkLibModules } = await import("../scripts/rules-source.ts"); + const dir = await mkdtemp(join(tmpdir(), "deadhead-lib-")); + t.after(() => rm(dir, { recursive: true, force: true })); + const lib = join(dir, "lib"); + const logic = join(dir, "logic", "meta"); + await mkdir(lib, { recursive: true }); + await mkdir(logic, { recursive: true }); + await writeFile(join(lib, "x.ts"), "export const value = 1;\n"); + await writeFile(join(logic, "a.ts"), 'import { value } from "../../lib/xXts";\n'); + assert.equal((await checkLibModules(lib, join(dir, "logic"))).length, 1); + await writeFile(join(lib, "x+.ts"), "export const extra = 2;\n"); + await writeFile(join(logic, "a.ts"), 'import { value } from "../../lib/x.ts";\nimport { extra } from "../../lib/x+.ts";\n'); + assert.deepEqual(await checkLibModules(lib, join(dir, "logic")), []); +}); + test("lib/json-ld someNode walks an array too long to spread into arguments", async () => { const { someNode } = await import("../packages/rules/lib/json-ld.ts"); const huge: unknown[] = Array.from({ length: 500_000 }, () => 0);