Skip to content

Commit dad9e99

Browse files
Sweep a project in one call instead of a file at a time
The sweep asked for each file's diagnostics separately, which meant it, rather than the program, decided how checking was spread over checkers. WorkspaceDiagnosticsForProject checks the program once and keys the result by file, so the program splits the work as a build would and the sweep only formats what comes back. A project now reports when it is done rather than streaming as each of its files finishes; projects still stream as they finish. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 04fe965 commit dad9e99

2 files changed

Lines changed: 60 additions & 7 deletions

File tree

tsc/internal/ls/diagnostics.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,47 @@ func worstCategory(diags []*ast.Diagnostic) diagnostics.Category {
161161
}
162162
return worst
163163
}
164+
165+
// WorkspaceDiagnosticsForProject checks the whole program at once and returns what each file it
166+
// owns should report. Checking every file in one call lets the program split the work across the
167+
// checkers a build would use, and keeps the checker pool's own coordination rather than repeating
168+
// it per file; the diagnostics come back keyed by the file they belong to.
169+
func (l *LanguageService) WorkspaceDiagnosticsForProject(ctx context.Context, files []*ast.SourceFile) map[*ast.SourceFile][]*lsproto.Diagnostic {
170+
reports := make(map[*ast.SourceFile][]*lsproto.Diagnostic, len(files))
171+
if l.UserPreferences().EnableValidation.IsFalse() {
172+
for _, file := range files {
173+
reports[file] = []*lsproto.Diagnostic{}
174+
}
175+
return reports
176+
}
177+
178+
byFile := make(map[*ast.SourceFile][]*ast.Diagnostic, len(files))
179+
for _, diagnostics := range [][]*ast.Diagnostic{
180+
l.program.GetSyntacticDiagnostics(ctx, nil),
181+
l.program.GetSemanticDiagnostics(ctx, nil),
182+
l.program.GetSuggestionDiagnostics(ctx, nil),
183+
} {
184+
for _, diagnostic := range diagnostics {
185+
if file := diagnostic.File(); file != nil {
186+
byFile[file] = append(byFile[file], diagnostic)
187+
}
188+
}
189+
}
190+
if l.program.Options().GetEmitDeclarations() {
191+
for _, diagnostic := range l.program.GetDeclarationDiagnostics(ctx, nil) {
192+
if file := diagnostic.File(); file != nil {
193+
byFile[file] = append(byFile[file], diagnostic)
194+
}
195+
}
196+
}
197+
198+
for _, file := range files {
199+
// A file's supplemental sources report under the file itself, as they do for a pull on it.
200+
diagnostics := byFile[file]
201+
for _, supplemental := range file.SupplementalSourceFiles() {
202+
diagnostics = append(diagnostics, byFile[supplemental]...)
203+
}
204+
reports[file] = l.toLSPDiagnostics(ctx, diagnostics)
205+
}
206+
return reports
207+
}

tsc/internal/lsp/workspacediagnostics.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/microsoft/TypeScript/tsc/internal/collections"
99
"github.com/microsoft/TypeScript/tsc/internal/core"
1010
"github.com/microsoft/TypeScript/tsc/internal/diagnostics"
11-
"github.com/microsoft/TypeScript/tsc/internal/ls"
1211
"github.com/microsoft/TypeScript/tsc/internal/ls/lsconv"
1312
"github.com/microsoft/TypeScript/tsc/internal/ls/lsutil"
1413
"github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto"
@@ -237,15 +236,26 @@ func (r *workspaceDiagnosticsRun) checkConcurrently(snapshot *project.Snapshot,
237236

238237
// checkProject fills in the reports for the files of one project, reporting whether it got through
239238
// them all. A cancelled project must not be emitted: its remaining reports are still zero values.
239+
// checkProject checks a project's files and builds their reports. The program checks them all in
240+
// one call, so the work is split across the checkers a build would use rather than being driven a
241+
// file at a time from here; the trade is that a project reports once it is done rather than
242+
// streaming as each of its files finishes.
240243
func (r *workspaceDiagnosticsRun) checkProject(snapshot *project.Snapshot, pf workspaceDiagnosticsProject) bool {
244+
files := make([]*ast.SourceFile, 0, len(pf.files))
245+
for _, file := range pf.files {
246+
if file != nil {
247+
files = append(files, file)
248+
}
249+
}
250+
reports := pf.languageService.WorkspaceDiagnosticsForProject(r.ctx, files)
251+
if r.ctx.Err() != nil {
252+
return false
253+
}
241254
for j, file := range pf.files {
242255
if file == nil {
243256
continue
244257
}
245-
if r.ctx.Err() != nil {
246-
return false
247-
}
248-
pf.reports[j] = r.reportForFile(snapshot, pf.languageService, file)
258+
pf.reports[j] = r.reportForFile(snapshot, file, reports[file])
249259
}
250260
return true
251261
}
@@ -268,9 +278,8 @@ func (r *workspaceDiagnosticsRun) emitProject(pf workspaceDiagnosticsProject) {
268278
}
269279
}
270280

271-
func (r *workspaceDiagnosticsRun) reportForFile(snapshot *project.Snapshot, languageService *ls.LanguageService, file *ast.SourceFile) workspaceDiagnosticReport {
281+
func (r *workspaceDiagnosticsRun) reportForFile(snapshot *project.Snapshot, file *ast.SourceFile, items []*lsproto.Diagnostic) workspaceDiagnosticReport {
272282
uri := lsconv.FileNameToDocumentURI(file.FileName())
273-
items := languageService.ProvideDiagnosticsForFile(r.ctx, file)
274283
resultID := workspaceDiagnosticsResultID(items)
275284
version := openDocumentVersion(snapshot, file.FileName())
276285

0 commit comments

Comments
 (0)