Repository navigation
fix(js): test selection — imports past .d.ts, test-hot parameters, wrapped calls - #1877
Merged
Merged
Conversation
…tion beside it
The compiler's resolver prefers a declaration file: require('../') in a package whose
package.json names `types` (or an exports "types" condition), and import './x.js' beside
a hand-written x.d.ts, both resolved to the .d.ts. JavaScript never loads a declaration,
so every value imported that way was external and every call through it an untyped
receiver: a test of the package's own API reached nothing in it.
When the first answer is a declaration outside node_modules, the import is resolved again
with declarations hidden from the resolver (types/typings and the .d.ts substitution fall
through to main, the exports code conditions and the .js itself). A module that exists only
as a declaration keeps its first answer.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…ments flow The parameter fan cap refuses argument flow into a function called from more than --dispatch-cap places, and it counted every site, tests included. A suite calls the API it tests over and over, so exactly the functions under test went "hot": a constructor called from 21 test files beside 13 library sites lost its parameters, a callback the library handed it and stored on the instance (this.decode = options['decode']) was never followed, and the library's own call through the field resolved only to the fallback beside it. Two verdicts now. Whether the PROGRAM's arguments flow is decided by the program's own sites; whether a TEST's arguments flow is decided by every site, as before, so nothing that flowed before stops flowing: a plugin a test hands the program, which the program calls back with its own class, still types what the test then calls (a version that dropped every test argument lost exactly that; the case guards it). For the name count, the overcount used for module-level functions, a test's bare call or `new` of the name leaves it, while a test's member call that only shares the name (`lib.add(1, 2)` on an export that is a different function, such as a curried closure) stays in it: dropping those too made a curried library's graph three times larger and its index five times slower, for no change in any answer. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…e was handed
`export default promisable(eachItem, 3)`, `const add = curry2(function add(a, b) {...})`:
the wrapper returns a closure that calls its parameter, and the module exports that
closure. The closure's call of the parameter is read context-insensitively, so it held
every function any of the wrapper's sites passed (past the fan cap: nothing), and a test
calling the export reached the closure and stopped there.
The wrapping site is already a value of its own (("wrap", site), value-flow.dl), used for
what a call through it returns. A call through it now also runs the function that site was
handed at the forwarded position, what the wrapper writes into that parameter, or what a
wrapped argument itself runs: one of a set, beside the edge to the closure the call keeps,
and under the dispatch cap (a parameter every wrapped export is passed to runs one of them,
and naming all of them says nothing). A wrapper whose closure never calls its parameter is
not a wrapper and runs nothing it was handed. Two engine goldens gain these edges (memoize
/ once / lazy results and the param-reassigned shapes), reviewed and blessed with --oracle.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
swapnilpaliwal-sd
force-pushed
the
fix/js-impact-loop
branch
from
October 9, 2026 19:23
310518f to
408d01f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
JavaScript test selection / impact / path, measured against behaviour on 18 pinned pure-JavaScript repositories (12 used for tuning, 6 held out and scored only at the end). Truth: 60 functions per repository broken one at a time (throw on entry, AST-sampled without consulting the graph), the test files that newly fail against a per-test double baseline; plus a precise runtime caller trace (every source function instrumented on its own line) for callers and runtime path pairs.
Three patterns, each seen in at least two repositories:
require('../')in a package whose package.json namestypes(or an exportstypescondition), andimport './x.js'beside a hand-writtenx.d.ts, resolved to the .d.ts: the value was external and every call through it an untyped receiver, so a test of the package's own API reached nothing. The parser now re-resolves with declarations hidden; a declaration-only module keeps its first answer. (4 of 18 repositories; up to 137 imports each.)newof the name leaves the count; a test's member call that only shares the name stays. Two rejected versions, both measured: dropping every test argument broke a held-out plugin pattern (lib.extend(plugin)from a test; path on that repo 0.833 → 0.137 — the case guards that shape), and dropping every test site from the name count made one curried library's graph 3x larger and its index 5x slower for no change in any answer.export default wrapper(impl, n): the closure's call of its parameter is context-insensitive and over the fan cap. Calls through the per-site("wrap", site)value now also run that site's argument (one of a set, under the dispatch cap).Results (micro unless noted; base = apps/integration-0.1.9 at a811ccf)
No repository loses recall, path or callers on either set; precision dips only where recall rose (more files selected). Context answers are unchanged.
Cost (warm index, base → fix): unchanged on all 18 repositories within noise (largest: 96 → 71 s, 394 → 395 MB; the curried library 14 → 12 s, 112 → 117 MB; the CLI library's graph 69 → 76 MB from the edges fix 1 now resolves). An uncapped first version of fix 3 added 53k edges to the curried library; the cap removed them with identical answers.
Checks
64-memoize-wrapper-result,65-wrapper-param-reassigned) gain only the wrapped-call edges, reviewed and blessed with--oraclesrc/test/javascript-tests.ts: 49/49