-
Notifications
You must be signed in to change notification settings - Fork 752
Revert "Make Program diagnostic API clearer (#2197)" #2250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -371,15 +371,23 @@ func (p *Program) BindSourceFiles() { | |||||||||||||||
| wg.RunAndWait() | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) CheckSourceFiles(ctx context.Context, files []*ast.SourceFile) { | ||||||||||||||||
| p.checkerPool.ForEachCheckerParallel(ctx, func(_ int, checker *checker.Checker) { | ||||||||||||||||
| for file := range p.checkerPool.Files(checker) { | ||||||||||||||||
| if files == nil || slices.Contains(files, file) { | ||||||||||||||||
| checker.CheckSourceFile(ctx, file) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| }) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Return the type checker associated with the program. | ||||||||||||||||
| func (p *Program) GetTypeChecker(ctx context.Context) (*checker.Checker, func()) { | ||||||||||||||||
| return p.checkerPool.GetChecker(ctx) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) ForEachCheckerParallel(cb func(idx int, c *checker.Checker)) { | ||||||||||||||||
| if pool, ok := p.checkerPool.(*checkerPool); ok { | ||||||||||||||||
| pool.ForEachCheckerParallel(cb) | ||||||||||||||||
| } | ||||||||||||||||
| func (p *Program) ForEachCheckerParallel(ctx context.Context, cb func(idx int, c *checker.Checker)) { | ||||||||||||||||
| p.checkerPool.ForEachCheckerParallel(ctx, cb) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Return a checker for the given file. We may have multiple checkers in concurrent scenarios and this | ||||||||||||||||
|
|
@@ -417,59 +425,39 @@ func (p *Program) GetResolvedModules() map[tspath.Path]module.ModeAwareCache[*mo | |||||||||||||||
| return p.resolvedModules | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // collectDiagnostics collects diagnostics from a single file or all files. | ||||||||||||||||
| // If sourceFile is non-nil, returns diagnostics for just that file. | ||||||||||||||||
| // If sourceFile is nil, returns diagnostics for all files in the program. | ||||||||||||||||
| func (p *Program) collectDiagnostics(ctx context.Context, sourceFile *ast.SourceFile, collect func(context.Context, *ast.SourceFile) []*ast.Diagnostic) []*ast.Diagnostic { | ||||||||||||||||
| var result []*ast.Diagnostic | ||||||||||||||||
| if sourceFile != nil { | ||||||||||||||||
| result = collect(ctx, sourceFile) | ||||||||||||||||
| } else { | ||||||||||||||||
| for _, file := range p.files { | ||||||||||||||||
| result = append(result, collect(ctx, file)...) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| return SortAndDeduplicateDiagnostics(result) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) GetSyntacticDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| return p.collectDiagnostics(ctx, sourceFile, func(_ context.Context, file *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| return core.Concatenate(file.Diagnostics(), file.JSDiagnostics()) | ||||||||||||||||
| }) | ||||||||||||||||
| return p.getDiagnosticsHelper(ctx, sourceFile, false /*ensureBound*/, false /*ensureChecked*/, p.getSyntacticDiagnosticsForFile) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) GetBindDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| if sourceFile != nil { | ||||||||||||||||
| binder.BindSourceFile(sourceFile) | ||||||||||||||||
| } else { | ||||||||||||||||
| p.BindSourceFiles() | ||||||||||||||||
| } | ||||||||||||||||
| return p.collectDiagnostics(ctx, sourceFile, func(_ context.Context, file *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| return file.BindDiagnostics() | ||||||||||||||||
| }) | ||||||||||||||||
| return p.getDiagnosticsHelper(ctx, sourceFile, true /*ensureBound*/, false /*ensureChecked*/, p.getBindDiagnosticsForFile) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) GetSemanticDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| return p.collectDiagnostics(ctx, sourceFile, p.getSemanticDiagnosticsForFile) | ||||||||||||||||
| return p.getDiagnosticsHelper(ctx, sourceFile, true /*ensureBound*/, true /*ensureChecked*/, p.getSemanticDiagnosticsForFile) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) GetSemanticDiagnosticsWithoutNoEmitFiltering(ctx context.Context, sourceFiles []*ast.SourceFile) map[*ast.SourceFile][]*ast.Diagnostic { | ||||||||||||||||
| func (p *Program) GetSemanticDiagnosticsNoFilter(ctx context.Context, sourceFiles []*ast.SourceFile) map[*ast.SourceFile][]*ast.Diagnostic { | ||||||||||||||||
| p.BindSourceFiles() | ||||||||||||||||
| p.CheckSourceFiles(ctx, sourceFiles) | ||||||||||||||||
| if ctx.Err() != nil { | ||||||||||||||||
| return nil | ||||||||||||||||
| } | ||||||||||||||||
| result := make(map[*ast.SourceFile][]*ast.Diagnostic, len(sourceFiles)) | ||||||||||||||||
| for _, file := range sourceFiles { | ||||||||||||||||
| result[file] = SortAndDeduplicateDiagnostics(p.getBindAndCheckDiagnosticsForFile(ctx, file)) | ||||||||||||||||
| result[file] = SortAndDeduplicateDiagnostics(p.getSemanticDiagnosticsForFileNotFilter(ctx, file)) | ||||||||||||||||
| } | ||||||||||||||||
| return result | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) GetSuggestionDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| return p.collectDiagnostics(ctx, sourceFile, p.getSuggestionDiagnosticsForFile) | ||||||||||||||||
| return p.getDiagnosticsHelper(ctx, sourceFile, true /*ensureBound*/, true /*ensureChecked*/, p.getSuggestionDiagnosticsForFile) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) GetProgramDiagnostics() []*ast.Diagnostic { | ||||||||||||||||
| return SortAndDeduplicateDiagnostics(core.Concatenate( | ||||||||||||||||
| return SortAndDeduplicateDiagnostics(slices.Concat( | ||||||||||||||||
| p.programDiagnostics, | ||||||||||||||||
| p.includeProcessor.getDiagnostics(p).GetGlobalDiagnostics(), | ||||||||||||||||
| )) | ||||||||||||||||
| p.includeProcessor.getDiagnostics(p).GetGlobalDiagnostics())) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) GetIncludeProcessorDiagnostics(sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
|
|
@@ -998,26 +986,40 @@ func (p *Program) GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic { | |||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| globalDiagnostics := make([][]*ast.Diagnostic, p.checkerPool.Count()) | ||||||||||||||||
| p.ForEachCheckerParallel(func(idx int, checker *checker.Checker) { | ||||||||||||||||
| p.checkerPool.ForEachCheckerParallel(ctx, func(idx int, checker *checker.Checker) { | ||||||||||||||||
| globalDiagnostics[idx] = checker.GetGlobalDiagnostics() | ||||||||||||||||
| }) | ||||||||||||||||
|
|
||||||||||||||||
| return SortAndDeduplicateDiagnostics(slices.Concat(globalDiagnostics...)) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) GetDeclarationDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| return p.collectDiagnostics(ctx, sourceFile, p.getDeclarationDiagnosticsForFile) | ||||||||||||||||
| return p.getDiagnosticsHelper(ctx, sourceFile, true /*ensureBound*/, true /*ensureChecked*/, p.getDeclarationDiagnosticsForFile) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) GetOptionsDiagnostics(ctx context.Context) []*ast.Diagnostic { | ||||||||||||||||
| return SortAndDeduplicateDiagnostics(core.Concatenate(p.GetGlobalDiagnostics(ctx), p.getOptionsDiagnosticsOfConfigFile())) | ||||||||||||||||
| return SortAndDeduplicateDiagnostics(append(p.GetGlobalDiagnostics(ctx), p.getOptionsDiagnosticsOfConfigFile()...)) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) getOptionsDiagnosticsOfConfigFile() []*ast.Diagnostic { | ||||||||||||||||
| // todo update p.configParsingDiagnostics when updateAndGetProgramDiagnostics is implemented | ||||||||||||||||
| if p.Options() == nil || p.Options().ConfigFilePath == "" { | ||||||||||||||||
| return nil | ||||||||||||||||
| } | ||||||||||||||||
| return p.GetConfigFileParsingDiagnostics() | ||||||||||||||||
| return p.GetConfigFileParsingDiagnostics() // TODO: actually call getDiagnosticsHelper on config path | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) getSyntacticDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| return core.Concatenate(sourceFile.Diagnostics(), sourceFile.JSDiagnostics()) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) getBindDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| // TODO: restore this; tsgo's main depends on this function binding all files for timing. | ||||||||||||||||
| // if checker.SkipTypeChecking(sourceFile, p.compilerOptions) { | ||||||||||||||||
| // return nil | ||||||||||||||||
| // } | ||||||||||||||||
|
|
||||||||||||||||
| return sourceFile.BindDiagnostics() | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func FilterNoEmitSemanticDiagnostics(diagnostics []*ast.Diagnostic, options *core.CompilerOptions) []*ast.Diagnostic { | ||||||||||||||||
|
|
@@ -1030,26 +1032,40 @@ func FilterNoEmitSemanticDiagnostics(diagnostics []*ast.Diagnostic, options *cor | |||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (p *Program) getSemanticDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| return core.Concatenate( | ||||||||||||||||
| FilterNoEmitSemanticDiagnostics(p.getBindAndCheckDiagnosticsForFile(ctx, sourceFile), p.Options()), | ||||||||||||||||
| return slices.Concat( | ||||||||||||||||
| FilterNoEmitSemanticDiagnostics(p.getSemanticDiagnosticsForFileNotFilter(ctx, sourceFile), p.Options()), | ||||||||||||||||
| p.GetIncludeProcessorDiagnostics(sourceFile), | ||||||||||||||||
| ) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // getBindAndCheckDiagnosticsForFile gets semantic diagnostics for a single file, | ||||||||||||||||
| // including bind diagnostics, checker diagnostics, and handling of @ts-ignore/@ts-expect-error directives. | ||||||||||||||||
| func (p *Program) getBindAndCheckDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| func (p *Program) getSemanticDiagnosticsForFileNotFilter(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { | ||||||||||||||||
| compilerOptions := p.Options() | ||||||||||||||||
| if checker.SkipTypeChecking(sourceFile, compilerOptions, p, false) { | ||||||||||||||||
| return nil | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| fileChecker, done := p.checkerPool.GetCheckerForFile(ctx, sourceFile) | ||||||||||||||||
| defer done() | ||||||||||||||||
|
|
||||||||||||||||
| // Getting a checker will force a bind, so this will be populated. | ||||||||||||||||
| var fileChecker *checker.Checker | ||||||||||||||||
| var done func() | ||||||||||||||||
| if sourceFile != nil { | ||||||||||||||||
| fileChecker, done = p.checkerPool.GetCheckerForFile(ctx, sourceFile) | ||||||||||||||||
| defer done() | ||||||||||||||||
| } | ||||||||||||||||
| diags := slices.Clip(sourceFile.BindDiagnostics()) | ||||||||||||||||
| diags = append(diags, fileChecker.GetDiagnostics(ctx, sourceFile)...) | ||||||||||||||||
| // Ask for diags from all checkers; checking one file may add diagnostics to other files. | ||||||||||||||||
| // These are deduplicated later. | ||||||||||||||||
| checkerDiags := make([][]*ast.Diagnostic, p.checkerPool.Count()) | ||||||||||||||||
| p.checkerPool.ForEachCheckerParallel(ctx, func(idx int, checker *checker.Checker) { | ||||||||||||||||
| if sourceFile == nil || checker == fileChecker { | ||||||||||||||||
| checkerDiags[idx] = checker.GetDiagnostics(ctx, sourceFile) | ||||||||||||||||
| } | ||||||||||||||||
| }) | ||||||||||||||||
| if ctx.Err() != nil { | ||||||||||||||||
| return nil | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| diags = append(diags, slices.Concat(checkerDiags...)...) | ||||||||||||||||
|
|
||||||||||||||||
| // !!! This should be rewritten to work like getBindAndCheckDiagnosticsForFileNoCache. | ||||||||||||||||
|
|
||||||||||||||||
| isPlainJS := ast.IsPlainJSFile(sourceFile, compilerOptions.CheckJs) | ||||||||||||||||
| if isPlainJS { | ||||||||||||||||
|
|
@@ -1131,12 +1147,28 @@ func (p *Program) getSuggestionDiagnosticsForFile(ctx context.Context, sourceFil | |||||||||||||||
| return nil | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| fileChecker, done := p.checkerPool.GetCheckerForFile(ctx, sourceFile) | ||||||||||||||||
| defer done() | ||||||||||||||||
| var fileChecker *checker.Checker | ||||||||||||||||
| var done func() | ||||||||||||||||
| if sourceFile != nil { | ||||||||||||||||
| fileChecker, done = p.checkerPool.GetCheckerForFile(ctx, sourceFile) | ||||||||||||||||
| defer done() | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Getting a checker will force a bind, so this will be populated. | ||||||||||||||||
| diags := slices.Clip(sourceFile.BindSuggestionDiagnostics) | ||||||||||||||||
|
||||||||||||||||
| diags := slices.Clip(sourceFile.BindSuggestionDiagnostics) | |
| var diags []*ast.Diagnostic | |
| if sourceFile != nil { | |
| diags = slices.Clip(sourceFile.BindSuggestionDiagnostics) | |
| } else { | |
| diags = []*ast.Diagnostic{} | |
| } |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling ForEachCheckerParallel with context.Background() will cause a panic in the project CheckerPool implementation. The project CheckerPool's ForEachCheckerParallel method (line 106-108 in internal/project/checkerpool.go) panics when the context has no request ID.
The counting methods SymbolCount(), TypeCount(), and InstantiationCount() don't receive a context parameter, so they use context.Background(). This will work fine with the compiler's checkerPool but will panic when used with a project CheckerPool.
Consider either:
- Adding a context parameter to these counting methods
- Creating a variant of
ForEachCheckerParallelthat doesn't require a request ID for read-only operations like counting - Using the compiler's checkerPool directly instead of the interface when the implementation is known
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling ForEachCheckerParallel with context.Background() will cause a panic in the project CheckerPool implementation. The project CheckerPool's ForEachCheckerParallel method (line 106-108 in internal/project/checkerpool.go) panics when the context has no request ID.
The TypeCount() method doesn't receive a context parameter, so it uses context.Background(). This will work fine with the compiler's checkerPool but will panic when used with a project CheckerPool.
Consider either:
- Adding a context parameter to this method
- Creating a variant of
ForEachCheckerParallelthat doesn't require a request ID for read-only operations like counting - Using the compiler's checkerPool directly instead of the interface when the implementation is known
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling ForEachCheckerParallel with context.Background() will cause a panic in the project CheckerPool implementation. The project CheckerPool's ForEachCheckerParallel method (line 106-108 in internal/project/checkerpool.go) panics when the context has no request ID.
The InstantiationCount() method doesn't receive a context parameter, so it uses context.Background(). This will work fine with the compiler's checkerPool but will panic when used with a project CheckerPool.
Consider either:
- Adding a context parameter to this method
- Creating a variant of
ForEachCheckerParallelthat doesn't require a request ID for read-only operations like counting - Using the compiler's checkerPool directly instead of the interface when the implementation is known
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function will panic if
sourceFileisnil. The code on lines 1047-1052 attempts to handle the nil case by conditionally getting a file checker, but then line 1053 unconditionally accessessourceFile.BindDiagnostics(), which will cause a nil pointer dereference.Either remove the nil handling code (lines 1047-1052) if this function should never be called with nil, or add a nil check before line 1053 to handle the nil case properly.