Return all files from getFilesAffectedBy when a changed file affects global scope#4665
Merged
jakebailey merged 3 commits intoJul 23, 2026
Conversation
…global scope change Three dependencyUpdate scenarios covering reverse-dependency semantic invalidation when changed files affect the global scope: - a batched update where one dependency gets a breaking type change while another dependency's update also changes the global scope; the break surfaces in a consumer that only references the broken dependency through an intermediate module whose .d.ts signature does not change - a control arm with the same breaking change but no global scope co-change - a lone global scope content change whose new type is violated by a file that uses the global without importing the changed module The first and third cases currently fail on main with an unexpected incremental-vs-clean diff: the incremental build reports no errors while a clean build reports TS2366/TS2322, and the written buildinfo carries no semanticDiagnosticsPerFile entry or pending marker for the affected files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…global scope Ports Strada's getFilesAffectedByUpdatedShapeWhenModuleEmit behavior (builderState.ts): when a changed file affects the global scope, the affected set is every file other than the default library files. getFilesAffectedBy previously set hasAllFilesExcludingDefaultLibraryFile and primed the all-files cache on a global-scope change, but still returned only the changed file plus its reverse closure bounded by unchanged .d.ts signatures. handleDtsMayChangeOfAffectedFile then takes an early return for every affected file when that flag is set, skipping the reverse-referencedBy invalidation that removes dependents' cached semantic diagnostics. Strada's equivalent early return in builder.ts is safe only because its affected set is all files; with the narrow set, dependents outside the signature-bounded closure kept their cached-clean entries and the incremental build reported no errors where a clean build reports them. Because the flag is handler-global, one global-scope file in a changed batch also suppressed dependent invalidation for every other changed file in that batch. With the all-files set, the early return clears cached diagnostics and updates signatures for every file, matching Strada. Baseline churn is uniformly that class: after a global-scope change, sibling files are refreshed (recomputed .d.ts signatures), their cached diagnostics dropped, and they are queued for full emit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Aligns incremental/build invalidation with TypeScript by rechecking all non-default-library files after a global-scope shape change.
Changes:
- Returns the cached all-files affected set for global changes.
- Adds dependency-update regression and control scenarios.
- Updates affected incremental, build, and watch baselines.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
internal/execute/incremental/affectedfileshandler.go |
Returns all affected non-library files. |
internal/execute/tsctests/tscbuild_test.go |
Adds three regression scenarios. |
testdata/baselines/reference/tsc/noEmitOnError/when-declarationMap-changes.js |
Updates invalidation baseline. |
testdata/baselines/reference/tsc/noEmit/syntax-errors-with-incremental-as-modules.js |
Updates incremental syntax-error baseline. |
testdata/baselines/reference/tsc/noEmit/semantic-errors-with-incremental-as-modules.js |
Updates semantic-error baseline. |
testdata/baselines/reference/tsc/noEmit/dts-errors-without-dts-enabled-with-incremental-as-modules.js |
Updates declaration-error baseline. |
testdata/baselines/reference/tsc/noEmit/dts-errors-with-incremental-as-modules.js |
Updates declaration-emit baseline. |
testdata/baselines/reference/tsc/incremental/when-global-file-is-added,-the-signatures-are-updated.js |
Records refreshed global signatures. |
testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors-with-incremental-as-modules.js |
Updates watch syntax-error baseline. |
testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors-with-incremental-as-modules.js |
Updates watch semantic-error baseline. |
testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled-with-incremental-as-modules.js |
Updates watch declaration-error baseline. |
testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-with-incremental-as-modules.js |
Updates watch declaration-emit baseline. |
testdata/baselines/reference/tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-when-no-files-are-emitted-with-incremental.js |
Records additional refreshed file. |
testdata/baselines/reference/tsbuild/noEmit/syntax-errors-with-incremental-as-modules.js |
Updates build syntax-error baseline. |
testdata/baselines/reference/tsbuild/noEmit/semantic-errors-with-incremental-as-modules.js |
Updates build semantic-error baseline. |
testdata/baselines/reference/tsbuild/noEmit/dts-errors-without-dts-enabled-with-incremental-as-modules.js |
Updates build declaration-error baseline. |
testdata/baselines/reference/tsbuild/noEmit/dts-errors-with-incremental-as-modules.js |
Updates build declaration-emit baseline. |
testdata/baselines/reference/tsbuild/dependencyUpdate/rebuilds-transitive-dependents-when-dependency-update-batch-includes-a-global-scope-change.js |
Covers mixed dependency updates. |
testdata/baselines/reference/tsbuild/dependencyUpdate/rebuilds-transitive-dependents-when-dependency-update-batch-has-no-global-scope-change.js |
Adds the control scenario. |
testdata/baselines/reference/tsbuild/dependencyUpdate/rebuilds-files-using-globals-when-global-scope-dependency-is-updated.js |
Covers non-importing global consumers. |
Contributor
Author
|
@microsoft-github-policy-service agree |
jakebailey
reviewed
Jul 21, 2026
Review feedback on microsoft#4665: the Strada original (getFilesAffectedByUpdatedShapeWhenModuleEmit in builderState.ts) carries no comment at this check, and the ported code reads fine without it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jakebailey
approved these changes
Jul 23, 2026
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.
Fixes #4664
This is a 6.0/7.0 behavior difference in
--build/--incrementalinvalidation (in-scope per CONTRIBUTING's bridge-release policy).
What
When a changed file
affectsGlobalScope,getFilesAffectedBynow returnsthe all-files-excluding-default-library set — the set it already computed and
cached — instead of falling through to the reverse-reference walk. This is
Strada's behavior:
getFilesAffectedByUpdatedShapeWhenModuleEmit(
src/compiler/builderState.ts) returnsgetAllFilesExcludingDefaultLibraryFile(...)for a global-scope shapechange.
Why
getFilesAffectedBysethasAllFilesExcludingDefaultLibraryFileand primedthe all-files cache on a global-scope change, but still returned only the
changed file plus its reverse closure bounded by unchanged .d.ts signatures.
handleDtsMayChangeOfAffectedFiletakes an early return for every affectedfile when that flag is set, skipping the reverse-
referencedByinvalidationthat removes dependents' cached semantic diagnostics. Strada's equivalent
early return (
src/compiler/builder.ts,handleDtsMayChangeOfAffectedFile)is safe only because its affected set is all files; tsgo kept the early
return but dropped the all-files set.
Consequences on main (see linked issue for a CLI repro and mechanism detail):
the global without importing the changed module — the incremental build
stays green while a clean build errors;
batch (the shape of a package-manager dependency bump) suppresses dependent
invalidation for every other changed file in that batch;
(absence of a
semanticDiagnosticsPerFileentry), so the false green isdurable across subsequent builds.
Test coverage
Three new
tsbuild/dependencyUpdatescenarios ininternal/execute/tsctests/tscbuild_test.go:rebuilds transitive dependents when dependency update batch includes a global scope change— dep-A breaking change + dep-Bdeclare globalchange in one batch; the break surfaces in a consumer connected to dep-A
only through a module whose .d.ts signature does not change; includes a
no-change follow-up edit showing durability. Failed on unmodified main via
the harness's incremental-vs-clean diff (incremental missed TS2366).
rebuilds transitive dependents when dependency update batch has no global scope change— control arm; behaved correctly before and after.rebuilds files using globals when global scope dependency is updated—lone global-scope content change violating a non-importing user. Failed on
unmodified main (incremental missed TS2322).
Baseline impact
15 existing baselines change, uniformly the intended class: after a
global-scope file changes, the other files are now refreshed — recomputed
.d.ts signatures (
*refresh*/(computed .d.ts)in program baselines),cached diagnostics dropped, and files queued for full emit
(
affectedFilesPendingEmitgains the sibling files; same-content rewritesappear in output listings):
tsc/incremental/when-global-file-is-added,-the-signatures-are-updated—the scenario's titular signatures are now actually updated for sibling
files.
tsc|tsbuild|tsbuildWatch/noEmit/*-with-incremental-as-modules(12 files)— these scenarios edit
a.tsinto a non-module (global) file mid-scenario;b.tsis now refreshed and queued for emit when that happens.tsc/noEmitOnError/when-declarationMap-changes,tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-...— same class(the watch scenario's actual concern, not looping, is unchanged).
Performance note: a global-scope change now invalidates and re-checks all
non-library files, like Strada. The all-files set is computed once per
build (cached on the snapshot), and per-file handling stays on the existing
early-return path (
removeSemanticDiagnosticsOf+ signature update), so thecost profile matches Strada's for the same transition; builds with no
global-scope change in the batch are unaffected.
Validation
go test ./internal/execute/...— green.go test ./...— green (macOS; TypeScript-submodule-gated tests skipped —submodule not cloned locally).
typescript@6.0.3errorson the incremental rebuild, unmodified main exits 0 silently, this branch
errors identically to 6.0 and to a clean build.
Disclosure
Per CONTRIBUTING's AI-assistance policy: this patch was authored with an AI
coding agent (Claude Code). I have read and understood the change, chose this
specific issue myself, and will shepherd it through review personally.