mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 15:26:25 +00:00
fix(javascript): report empty Electron coverage instead of silent zeroes (#8332)
* fix(javascript): report empty Electron coverage instead of silent zeroes `analyze_javascript_application` reported `browser_windows: 0` and `ipc.operations: 0` with only generic partial/unknown flags, so a bundle that imports `electron` but whose Electron facts were not recovered read exactly like an application with no IPC at all. Issue #8201 is that false negative on a real Rolldown bundle. Two additions, both scoped to "Electron is referenced but nothing was recovered": **A machine-readable indicator.** `electron_summary` gains `electron_semantics: "recovered" | "empty"`. It is absent when no analyzed source links an Electron module, so its presence distinguishes "not an Electron application" from "Electron coverage is empty". It follows the existing `protocol_registrations.exactOptional()` precedent, so previously captured evidence stays valid. **A graph limitation.** The limitation list already carried a note when at least one Electron fact was recovered but said nothing when zero were. It now states that zero IPC operations and zero BrowserWindows mean empty coverage rather than absence. `electronModuleReferenced` is the single owner of the module test. It reuses `ELECTRON_MODULE`, which matches exactly the supported entry points (`electron`, `electron/common`, `/main`, `/renderer`, `/utility`), so `electron-helper` does not count, and it ignores type-only links because importing Electron types promises no boundary fact. Five tests cover recovered, empty, not-referenced, type-only, and supported subpath versus unrelated package. The empty, recovered and type-only cases fail on unmodified `main`. * chore: retrigger title and readiness checks after the title was corrected * fix(javascript): report Electron coverage from retained facts Four review findings on the coverage indicator. **A rejected application projection hid the reference.** A file whose expanded relationships exceed the result budget is replaced by a placeholder with no module links before the summary runs, while `graphLimitations` reads the raw analysis. The same response could then warn in prose and omit the field. The summary now takes the retained analysis for coverage only, and both outputs read one `electronSemanticsState`, so they cannot disagree. **A skipped semantic analysis hid the reference too.** `electronModuleReferenced` read only `semantic.ir.moduleLinks`, which is absent when semantic analysis is skipped for a budget and drops `import("electron")` or `module.require("electron")` forms. It now falls back to the retained `javascript.references`, which the static producer records for those inputs, while still excluding `type-import` links when the richer owner is available. `electronBoundaryFactCount` is now the single owner of the fact total, replacing a duplicate inline reduce in the graph builder. **Omission no longer claims absence.** The schema comment said a missing `electron_semantics` means the application does not use Electron. The predicate only establishes that no retained fact names one, and unread sources, failed analysis, and dynamic module names can hide its use, so the contract now says unknown. Two regressions cover a rejected projection and a skipped semantic analysis; both fail on the previous commit. * fix(javascript): count a dynamic Electron import as coverage Review finding: `electronModuleReferenced` returned as soon as module links existed, so a literal dynamic import was ignored. Verified against the two fact owners: import("electron") -> references: [dynamic-import electron] moduleLinks: [] require("electron") -> references: [require electron] moduleLinks: [require electron] An application that only loads Electron through `import("electron")` therefore got neither `electron_semantics: "empty"` nor the empty-coverage warning, and the zero counts stayed unexplained. The two owners now answer their own half instead of one short-circuiting the other. Module links answer a runtime static import or require and keep excluding `type-import`; the retained reference is read for every kind except `static-import`, which is exactly the type-only risk. A dynamic specifier is still never counted by either owner, so this remains an observed-reference claim rather than an absence claim. The regression uses `import("electron")` and fails on the previous commit. * fix(javascript): read only module-loading Electron references Two review findings, both verified against the static reference producer. **A retained runtime static import was skipped.** The excluded kind was meant to protect the type-only distinction, but the static analyzer records a whole `import type { IpcMain } from "electron"` declaration as no reference at all: import type { IpcMain } from "electron" -> references: [] import { app } from "electron" -> references: [static-import electron] require("electron") -> references: [require electron] import("electron") -> references: [dynamic-import electron] new Worker(new URL("electron", m)) -> references: [worker electron] When semantic analysis is skipped for a budget the module links are absent, so that exclusion dropped a real runtime ESM import and left the zero counts unexplained. **A worker URL path is not an Electron module.** `new Worker(new URL("electron", import.meta.url))` records a `worker` reference whose specifier is a path relative to the importing source, so counting it reported an ordinary worker file as `electron_semantics: "empty"`. `ELECTRON_REFERENCE_KINDS` is now an explicit allowlist of the kinds that load a module by package specifier — `static-import`, `dynamic-import` and `require` — so `worker` and `service-worker` URL paths are excluded. Module links are still read first for the type-only distinction. Three regressions cover a skipped-semantics ESM import, a skipped-semantics whole type-only import, and a worker URL path; two of them fail on the previous commit. * fix(javascript): own Electron applicability in the static producer Review finding. A `worker` reference records a URL relative to the importing source, so `new Worker(new URL("electron", import.meta.url))` names a local file and is not an Electron module. The boundary owner already read only module-loading reference kinds, but `finalizeStaticAnalysis` still treated every matching static reference as `analysis_coverage.applicability.electron: "observed"`, which `javascriptStaticSourceCoverageSummary` publishes. One response could therefore say Electron was observed in its coverage view while the Electron summary omitted `electron_semantics`. `ELECTRON_REFERENCE_KINDS` now lives next to `ELECTRON_MODULE` in the domain owner and is consumed by both the static producer and the Electron boundary owner, so the two outputs cannot disagree. A worker URL path is `unknown` for both, while a `require("electron")` is `observed` for both. The regression asserts the domain applicability fact and the boundary summary agree for a worker URL path; it fails when the producer stops filtering by kind. * fix(javascript): require a loaded Electron module for applicability Review finding. A CommonJS `require.resolve("electron")` records a `require` reference with `operation: "resolve"`: it locates the module without loading it, so no Electron boundary fact is expected. Both predicates dropped the operation, so such a source reported `electron_semantics: "empty"` and the missing-coverage warning even though Electron was never loaded. Measured on the static producer: require.resolve("electron") -> references: [require resolve electron] applicability.electron: unknown require("electron") -> references: [require load electron] applicability.electron: observed import("electron") -> references: [dynamic-import load electron] applicability.electron: observed `electronModuleReferenced` and `finalizeStaticAnalysis` now both require `operation === "load"`, so the boundary summary and the coverage view stay aligned and reachability-only references no longer claim missing coverage. The regression asserts a resolve-only source produces neither the field nor the warning, and it fails when the operation check is removed. * fix(javascript): classify Electron loads in the static fact owner Review finding, and the correct home for the previous two rounds of the same complaint. A Webpack/Rspack factory may name a local module `"electron"`, and the static producer then records a loaded `require` reference whose specifier is that bundle module key. Treating the key as a package specifier reported `electron_semantics: "empty"` plus the missing-coverage warning for a bundle that never loads Electron. Three changes, all in the domain owner, so no consumer re-derives the question: - `JavaScriptModuleRangeIndex.hasKey` answers whether a specifier is a recovered bundle module key. A `require` whose specifier is such a key loads a module inside the artifact. - `electronObserved` in `finalizeStaticAnalysis` now requires a loaded module-load reference whose specifier is not a bundle key. The published `analysis_coverage.applicability.electron` therefore owns every exclusion: `type-import`, `require.resolve`, `worker`/`service-worker` URL paths, and bundle-local keys. - `electronModuleReferenced` consumes that owned fact instead of re-deriving it, keeping the boundary summary and the coverage view from disagreeing. Historical evidence without the field still falls back to the retained references. Measured: bundle-local module ID "electron" -> applicability unknown require("electron") -> applicability observed webpack external n(45840) -> applicability observed The regression covers both the local key and the real external in the same factory shape; it fails without the bundle-key exclusion. * fix(javascript): identify a bundle module by its loader, not its key Both review findings were right, and they show why the previous key-set approach was wrong: bundle-key membership is a property of the file, but whether a specifier names a module inside the artifact is a property of the reference. `JavaScriptStaticReference` now records the loader that produced it, and `electronObserved` excludes `factory-loader` references instead of every specifier that happens to match a recovered module key. Measured on Node 24.22.1: local module keyed "electron" -> unknown (factory-loader) external 45840 = require("electron") -> observed (commonjs-loader) factory keyed "electron" that loads the pkg -> observed (commonjs-loader) The third case is the regression: a bundle whose factory is *named* `"electron"` but whose body does `e.exports = require("electron")` is a real Electron load, and the old key-set check suppressed it. The real load keeps its `commonjs-loader` identity even when the same specifier also names a factory, so one response can rely on the loader while the other relies on the key without either lying. `JavaScriptModuleRangeIndex.hasKey` is removed; it answered the wrong question. One regression covers all three shapes and fails when the `factory-loader` exclusion is dropped. * fix(javascript): carry the reference loader across the worker boundary The worker schema is strict, so the new `loader` field broke the worker handshake: `unrecognized_keys` on every `references` entry. That surfaced in CI as both a worker-schema test failure and a package-baseline failure reporting `worker_failure_reason: "io"` for a one-file Electron app. `referenceShape` now accepts the same three loader identities as the domain type, so the fact survives the worker boundary instead of collapsing every analysis to an execution failure. Verified: `JavaScriptAnalysisWorkerSchemas.test.ts` 10 passed, and the full domain/application/javascript suite is 5484 passed | 10 skipped. The remaining `SourceMapCommand` and `DarwinHelperCache` findings are present on unmodified `main` and are not from this change. * fix(javascript): remove the superseded key set and forward a deferred loader Two review findings. **The `keys` set is dead.** `JavaScriptModuleRangeIndex.keys` is left over from the abandoned key-membership approach: the loader-identity refactor removed `hasKey` and the write in `add`, so nothing reads it. Removed. **The deferred require path dropped the loader identity.** `requireCallSpecifier` returns a `loader` for `commonjs-loader`, `factory-loader` and `create-require`, and the direct `addReference` call forwards it, but `finishModuleRequireCalls` did not. A `createRequire` loader saved under a member literally named `require` therefore lost its identity: import { createRequire } from "node:module"; const holder = { require: createRequire(import.meta.url) }; holder.require("x"); before: require "x" with loader undefined after: require "x" with loader "create-require" Three regressions cover it; one fails without the change. I did **not** change the inline-type-only import case. `isTypeOnlyImportDeclaration` deliberately keeps a declaration whose bindings are all inline type-only, because `hasUncertainTypeOnlyModuleSideEffect` reports that decision as `type-only-module-side-effect` uncertainty: whether TypeScript emits the import depends on the consumer's compiler options. Narrowing it to erased broke `javascriptStaticCoverage.test.ts` and `javascriptSemanticAnalysis.require_links.test.ts`, which assert exactly that contract. If Electron applicability should ignore that reference, the decision belongs where applicability is derived, not in the shared declaration predicate — say the word and I will do it there instead. * fix(javascript): keep a commonjs-loader reference out of the bundle module index Review finding. `finalizeLocatedFindings` attaches `module_runtime` to every finding that sits inside a factory module range, whoever produced it. `resolveReference` then sent every reference carrying `module_runtime` through the bundle-module lookup, so a real CommonJS load inside a factory body resolved to the local module key that shared its name: "electron": (e, t, n) => { e.exports = require("electron") } fact: specifier "electron", loader "commonjs-loader", module_key "electron" before: resolved through the bundle module index to `#module:electron` after: resolves as an external package specifier Only a `factory-loader` specifier names a recovered bundle module key, which is what `loader` already records after the loader-identity change. The module lookup is now gated on it, and an absent `loader` keeps the previous behaviour for older findings.
This commit is contained in:
1 parent
85775b635b
commit
99bcd3e4e0
12 files changed
+551
-25
No files matched your search
@@ -8,6 +8,18 @@ import {
|
||||
} from "./javascriptAstValues.js";
|
||||
import { traverseJavaScriptValueAstSteps } from "./javascriptSemanticTraversal.js";
|
||||
|
||||
/**
|
||||
* The reference kinds that load a module by package specifier. A `worker` or
|
||||
* `service-worker` reference records a URL relative to the importing source, so
|
||||
* `new Worker(new URL("electron", import.meta.url))` names a local file and is
|
||||
* not an Electron module.
|
||||
*/
|
||||
export const ELECTRON_REFERENCE_KINDS: ReadonlySet<string> = new Set([
|
||||
"static-import",
|
||||
"dynamic-import",
|
||||
"require",
|
||||
]);
|
||||
|
||||
/** Match Electron's supported module entry points. */
|
||||
export const ELECTRON_MODULE =
|
||||
/^electron(?:\/(?:common|main|renderer|utility))?$/u;
|
||||
|
||||
Reference in new issue
Block a user