Skip to content

fix(stryker-js-typescript-checker): resolve every exports array fallback target - #254

Merged
kiro-systemf[bot] merged 3 commits into
mainfrom
stryker/exports-missing-file
Oct 9, 2026
Merged

kiro-systemf[bot] merged 3 commits into
mainfrom
stryker/exports-missing-file

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

When a tsconfig extends a package subpath whose exports value is an array, the checker took only the first entry that looked like a valid target and never checked that the file exists. With "./strict": ["./missing.json", "./strict-base.json"], tsc extends strict-base.json. Our resolver picked missing.json, found nothing there and dropped the base config, so editing the real base file didn't change the program digest.

Change

  • resolvePackageExports (resolve-package-exports.workflow.ts) now returns every resolvable target of an array, in order (PackageExportsResolved.targets). The caller in ts-compiler.handle.ts turns each one into a candidate path, and the existing "first file that exists" pick chooses among them.
  • A null ends the whole list at any depth, as it does in tsc. Targets listed before it stay candidates, and nothing after it is tried, even when the null sits inside a nested array. A leading null still excludes the subpath.
  • Targets that escape the package (./../…, invalid segments) are still skipped and never returned.

Evidence

1. Same answer as tsc --showConfig (7.0.2) on throwaway fixtures. Each fixture has extends: "pkg/base", and only present.json exists:

exports["./base"] tsc extends resolver targets first existing
["./missing.json", "./present.json"] present.json (target: es2021) ["./missing.json","./present.json"] present.json
["./present.json", "./missing.json"] present.json ["./present.json","./missing.json"] present.json
["./present.json", null] present.json ["./present.json"] present.json
["./missing.json", null, "./present.json"] nothing (compilerOptions: {}) ["./missing.json"] none
[["./missing.json", null], "./present.json"] nothing (compilerOptions: {}) ["./missing.json"] none

2. Tests that fail without the fix.

  • ∀entries_ArrayOfResolvableTargets_≡EveryTargetInFallbackOrder and the other array laws: with this branch's test file on main's source (9dde0b9), all 16 laws fail, because main has no targets.
  • ∀entries_NullTerminatedListAtAnyDepth_≡LeadingTargetsOnly nests the null-terminated list 0–3 levels deep. It fails on this branch's earlier head 782d20d (shrunk to [["./.json", null], "./.json"]) and passes at the current head.
  • Program-digest scenario "editing a base tsconfig an exports array reaches after a missing target moves the digest" (tests/program-digest.integration.test.ts) fails on main 9dde0b9, where the digest doesn't move, and passes here.

3. Escaping paths are still refused. ∀entries_ArrayWithEscapingTargets_≡OnlyResolvableTargetsInOrderAndNeverEscaping mixes escaping and in-package targets and requires that no escaping target is ever returned.

Package suite: 31 files pass. Lint, typecheck and format pass.

Review

ce-code-review, focused and report-only. The correctness peer checked about 70 exports shapes against tsc 7.0.2 and found one P1, a null inside a nested array; this branch fixes it. It also flagged one scenario that passed on the base commit too; that scenario is removed. One pre-existing P2 is not changed here: when the exports value is [null], the checker still falls back to the old <pkg>/<subpath>.json lookup (ts-compiler.handle.ts:519-526), which tsc does not do.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…ack target

An exports fallback array now yields every resolvable target in fallback order, so exportsCandidatesOf offers each in order and the first entry whose file exists wins. Escaping targets stay refused; a null entry and an inactive condition keep their existing terminal semantics.

Verdict-Semantics: unchanged
…in an exports array

A null in an exports fallback array ends the list, but the targets listed
before it are still candidates. tsc 7.0.2 resolves ["./present.json", null]
to present.json; the previous commit returned the subpath as excluded and
dropped the base config. Two program-digest scenarios pin both array cases
through the real checker runtime.
… any depth

tsc 7.0.2 stops the whole fallback list at a null, even one inside a nested
array: [["./missing.json", null], "./present.json"] extends nothing. The
resolver ended only the inner list and returned present.json as a later
candidate. A nested array that hits a null now reports its targets as
terminated, and every enclosing array stops there.

The property law now nests the null-terminated list up to three levels and
fails on the previous head (shrunk to [["./.json", null], "./.json"]). The
program-digest scenario for a trailing null passed on the base commit too, so
it is removed; the real compiler rejects the nested-null shape with TS6053, so
that case cannot be reached through a digest and stays with the law.

@kiro-systemf kiro-systemf Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: tsc 7.0.2 fixtures, laws fail on main 9dde0b9, digest scenario fails on main, CI 10/10 on 422489c, hunt grep clean.

@kiro-systemf
kiro-systemf Bot merged commit 27f493b into main Oct 9, 2026
10 checks passed
@kiro-systemf
kiro-systemf Bot deleted the stryker/exports-missing-file branch October 9, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant