Skip to content

Remove GetOptionsDiagnostics, global diags in editor, restore checker affinity#3285

Merged
jakebailey merged 24 commits intomainfrom
jabaile/restore-per-checker-looping
Mar 31, 2026
Merged

Remove GetOptionsDiagnostics, global diags in editor, restore checker affinity#3285
jakebailey merged 24 commits intomainfrom
jabaile/restore-per-checker-looping

Conversation

@jakebailey
Copy link
Copy Markdown
Member

This builds on #2322. Probably could replace it and just go for this.

From #2322:

  • Remove GetOptionsDiagnostics
  • Expose global diags to the editor.

Then:

  • Restore the looping over checkers to walk files in a consistent order and keep them in the same goroutine during many file checking.
  • Refactor the CheckerPool interface again, now down to just one single function.
    • As a side effect, fixes a bug where the project pool was not giving requests a consistent checker.

@jakebailey jakebailey changed the title Remove GetOptionsDiagnostics, global diags in checker, restore checker affinity Remove GetOptionsDiagnostics, global diags in editor, restore checker affinity Mar 29, 2026
@jakebailey jakebailey marked this pull request as ready for review March 30, 2026 22:02
Copilot AI review requested due to automatic review settings March 30, 2026 22:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the compiler and language server diagnostics pipeline by removing “options diagnostics”, surfacing global diagnostics in the editor via tsconfig publishes, and restoring more consistent checker affinity/iteration behavior.

Changes:

  • Remove GetOptionsDiagnostics and adjust diagnostics collection to re-query global diagnostics after semantic checking.
  • Accumulate global diagnostics from project-system checker pools and publish updated tsconfig diagnostics from the LSP after requests.
  • Refactor checker pooling to support grouped per-checker file iteration for diagnostics, and update baselines accordingly.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/compiler/checkerpool.go Simplifies CheckerPool interface; adds grouped per-checker iteration and global diagnostics aggregation helpers.
internal/compiler/program.go Reworks checker acquisition paths (compiler vs project pools), removes options diags path, and refactors semantic/suggestion diagnostics collection to reuse a provided checker.
internal/execute/incremental/program.go Removes incremental wrapper support for GetOptionsDiagnostics and stops using it for error-state tracking.
internal/lsp/server.go Enqueues a global-diagnostics publish check after each language service request.
internal/project/checkerpool.go Updates project-system checker pool API and adds incremental global diagnostics accumulation + change detection.
internal/project/project.go Adds GetProjectDiagnostics to combine program diagnostics with accumulated global diagnostics for tsconfig publishing.
internal/project/project_test.go Adds coverage to ensure global diagnostics discovered during checking are published on tsconfig.json.
internal/project/session.go Publishes combined project diagnostics; introduces coalesced background task for global diagnostics publishing.
testdata/baselines/reference/tsc/incremental/js-file-with-import-in-jsdoc-in-composite-project.js Updates baseline to reflect newly surfaced global diagnostic + changed exit status / tsbuildinfo shape.
testdata/baselines/reference/tsc/composite/converting-to-modules.js Updates tsbuildinfo + semantic diagnostics caching baseline expectations.
testdata/baselines/reference/tsbuildWatch/configFileErrors/reports-syntax-errors-in-config-file.js Updates watch baseline expectations for semantic diagnostics caching output and tsbuildinfo fields.
testdata/baselines/reference/tsbuild/configFileErrors/reports-syntax-errors-in-config-file.js Updates build baseline expectations for semantic diagnostics caching output and tsbuildinfo fields.
testdata/baselines/reference/submodule/fourslash/findAllReferences/findAllRefsForModuleGlobal.baseline.jsonc Baseline updated (marker placement) to match expected find-all-refs output.
testdata/baselines/reference/submodule/fourslash/findAllReferences/findAllRefsForModuleGlobal.baseline.jsonc.diff Removes no-longer-needed diff baseline file.

}

func (s *Session) publishGlobalDiagnostics(ctx context.Context) {
s.globalDiagPublishPending.Store(false)
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

globalDiagPublishPending is cleared at the start of publishGlobalDiagnostics, but background.Queue runs tasks concurrently. This means EnqueuePublishGlobalDiagnostics can enqueue another publish while one is still running, defeating the stated coalescing behavior and potentially causing overlapping PublishDiagnostics calls. Consider only clearing the flag when the task finishes, or introduce a separate “in-flight” vs “rerun requested” mechanism to ensure at most one publish runs at a time (while still not missing updates).

Suggested change
s.globalDiagPublishPending.Store(false)
defer s.globalDiagPublishPending.Store(false)

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +149
wg := core.NewWorkGroup(singleThreaded)
for checkerIdx := range checkerCount {
wg.Queue(func() {
p.locks[checkerIdx].Lock()
defer p.locks[checkerIdx].Unlock()
for i, file := range files {
if checker := p.checkers[checkerIdx]; checker == p.fileAssociations[file] {
cb(checker, i, file)
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

forEachCheckerGroupDo scans the entire files slice once per checker (for checkerIdx ... { for i, file := range files { ... } }), making this O(checkers×files). On large projects this can outweigh the contention benefits. Consider pre-bucketing file indices by checker (single pass over files) and then iterating only each checker's bucket inside the per-checker goroutine, preserving original order within each bucket.

Suggested change
wg := core.NewWorkGroup(singleThreaded)
for checkerIdx := range checkerCount {
wg.Queue(func() {
p.locks[checkerIdx].Lock()
defer p.locks[checkerIdx].Unlock()
for i, file := range files {
if checker := p.checkers[checkerIdx]; checker == p.fileAssociations[file] {
cb(checker, i, file)
}
if checkerCount == 0 || len(files) == 0 {
return
}
// Build a mapping from checker pointer to its index so we can bucket files
// by checker in a single pass over the files slice.
checkerIndex := make(map[*checker.Checker]int, checkerCount)
for idx, c := range p.checkers {
checkerIndex[c] = idx
}
// Buckets of file indices per checker, preserving original order within each bucket.
buckets := make([][]int, checkerCount)
for i, file := range files {
if c := p.fileAssociations[file]; c != nil {
if idx, ok := checkerIndex[c]; ok {
buckets[idx] = append(buckets[idx], i)
}
}
}
wg := core.NewWorkGroup(singleThreaded)
for checkerIdx := 0; checkerIdx < checkerCount; checkerIdx++ {
idx := checkerIdx
wg.Queue(func() {
p.locks[idx].Lock()
defer p.locks[idx].Unlock()
checker := p.checkers[idx]
for _, fileIndex := range buckets[idx] {
file := files[fileIndex]
cb(checker, fileIndex, file)

Copilot uses AI. Check for mistakes.
@andrewbranch
Copy link
Copy Markdown
Member

This looks good to me, except I don't really know how to evaluate the --incremental baseline changes. Have you validated that they look right/acceptable?

@jakebailey
Copy link
Copy Markdown
Member Author

jakebailey commented Mar 31, 2026

Yeah, so it's specifically how GetDiagnosticsOfAnyProgram is checking the length of diagnostics slices to understand what to do. Because GetOptionsDiagnostics was just global diags + config file parsing diags, it was duplicating those diagnostics, which actually guaranteed that if len(allDiagnostics) == configFileParsingDiagnosticsLength { would fail at the second check.

The tsbuildinfo is encoding sort of the "opposite" of reality, in that it's noting which files it didn't check (IIRC).

@jakebailey jakebailey added this pull request to the merge queue Mar 31, 2026
Merged via the queue into main with commit caf3ecd Mar 31, 2026
21 checks passed
@jakebailey jakebailey deleted the jabaile/restore-per-checker-looping branch March 31, 2026 20:25
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.

4 participants