diff --git a/src/domain/webBundleAnalyzer.test.ts b/src/domain/webBundleAnalyzer.test.ts index 87e1e852bd..f0ba4e2cf3 100644 --- a/src/domain/webBundleAnalyzer.test.ts +++ b/src/domain/webBundleAnalyzer.test.ts @@ -2123,6 +2123,32 @@ it("keeps known capture omissions in compact route summaries and queryable gap f expect(row).toMatchObject({ source_id: null, provenance: null }); }); +it.each( + (["scripts", "script_sources"] as const).flatMap((section) => + [0, 1, null].map((count) => ({ section, count })), + ), +)( + "preserves actual capture omission coverage for $section count $count", + ({ section, count }) => { + const captured = inspection("export {};"); + captured.completeness.excluded.push({ + section, + reason: "resource_budget_exhausted", + count, + }); + const result = analyzeWebBundleFixture(captured); + expect(result.completeness.status).toBe( + count === 0 ? "complete" : "partial", + ); + expect(result.unknowns).toHaveLength(count === 0 ? 0 : 1); + expect( + result.route_inventory?.unknowns.filter( + ({ kind }) => kind === "capture-omission", + ), + ).toHaveLength(count === 0 ? 0 : 1); + }, +); + it("retains captured Next script identity while deriving decoded route patterns", () => { const url = `${origin}/base/_next/static/chunks/app/%28shop%29/blog/%5Bid%5D/page-abc.js`; const captured = createWebPageInspection([ diff --git a/src/domain/webBundleAnalyzerResult.ts b/src/domain/webBundleAnalyzerResult.ts index c1656e282c..b27f8cf0d0 100644 --- a/src/domain/webBundleAnalyzerResult.ts +++ b/src/domain/webBundleAnalyzerResult.ts @@ -385,7 +385,8 @@ const captureBudgetOmission = ( const exclusions = inspection.completeness.excluded.filter( (exclusion) => exclusion.section === section && - exclusion.reason === "resource_budget_exhausted", + exclusion.reason === "resource_budget_exhausted" && + exclusion.count !== 0, ); if (exclusions.length === 0) return undefined; if (exclusions.some(({ count }) => count === null)) return null; diff --git a/src/domain/webModuleImports.test.ts b/src/domain/webModuleImports.test.ts index 2ef7eba144..0a601f0f87 100644 --- a/src/domain/webModuleImports.test.ts +++ b/src/domain/webModuleImports.test.ts @@ -63,6 +63,34 @@ describe("native module syntax observations", () => { column: 7, }); }); + it.each([ + ['"./dep.js" as const', "./dep.js"], + ['"./dep.js" satisfies string', "./dep.js"], + ['"./dep.js"!', "./dep.js"], + ["`./dep.js` as const", "./dep.js"], + ["`./${name}.js` as const", null], + ] as const)( + "retains exact import identity through erased syntax: %s", + (expression, specifier) => { + const source = `import(${expression});`; + const result = collectWebModuleImports(source, "main.ts"); + expect(result.state).toBe("parsed"); + expect(result.diagnostics).toEqual([]); + expect(result.imports).toEqual([ + { + kind: "dynamic-import", + specifier, + expression, + start: { offset: 7, line: 1, column: 7 }, + end: { + offset: 7 + expression.length, + line: 1, + column: 7 + expression.length, + }, + }, + ]); + }, + ); it("reports unparseable input without asserting an empty complete graph", () => { const result = collectWebModuleImports( 'import "./ok.js"; function {', diff --git a/src/domain/webModuleImports.ts b/src/domain/webModuleImports.ts index a8162030ee..9a142f2fb4 100644 --- a/src/domain/webModuleImports.ts +++ b/src/domain/webModuleImports.ts @@ -1,5 +1,5 @@ -import { isStringLiteral, isTemplateLiteral } from "@babel/types"; import type { Node } from "@babel/types"; +import { readExactJavaScriptLiteral } from "./javascript/javascriptAstValues.js"; import { traverseJavaScriptAst } from "./javascript/javascriptSemanticTraversal.js"; import { parseJavaScriptSource } from "./javascript/javascriptSourceParser.js"; import type { WebModuleImport } from "./webModuleTrace.js"; @@ -36,11 +36,9 @@ export const collectWebModuleImports = ( expression.loc === undefined ) return; - const specifier = isStringLiteral(expression) - ? expression.value - : isTemplateLiteral(expression) && expression.expressions.length === 0 - ? (expression.quasis[0]?.value.cooked ?? null) - : null; + const literal = readExactJavaScriptLiteral(expression); + const specifier = + literal.found && typeof literal.value === "string" ? literal.value : null; imports.push({ kind, specifier,