diff --git a/internal/compiler/checkerpool.go b/internal/compiler/checkerpool.go
index 84ea8d824a6..3b0d68633b2 100644
--- a/internal/compiler/checkerpool.go
+++ b/internal/compiler/checkerpool.go
@@ -10,10 +10,14 @@ import (
"github.com/microsoft/typescript-go/internal/core"
)
+// CheckerPool is implemented by the project system to provide checkers with
+// request-scoped lifetime and reclamation. It returns a checker and a release
+// function that must be called when the caller is done with the checker.
+// The returned checker must not be accessed concurrently; each acquisition is exclusive.
+// If file is non-nil, the pool may use it as an affinity hint to return the same
+// checker for the same file across calls.
type CheckerPool interface {
- GetChecker(ctx context.Context) (*checker.Checker, func())
- GetCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func())
- GetCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func())
+ GetChecker(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func())
}
type checkerPool struct {
@@ -46,12 +50,29 @@ func newCheckerPool(program *Program) *checkerPool {
return pool
}
-func (p *checkerPool) GetCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
+// GetChecker implements CheckerPool. When file is non-nil, returns the checker
+// associated with that file; otherwise returns the first checker.
+func (p *checkerPool) GetChecker(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
+ if file != nil {
+ return p.getCheckerForFileExclusive(ctx, file)
+ }
+ p.createCheckers()
+ c := p.checkers[0]
+ p.locks[0].Lock()
+ return c, sync.OnceFunc(func() {
+ p.locks[0].Unlock()
+ })
+}
+
+// getCheckerForFileNonExclusive returns the checker for the given file without locking.
+// This is only safe when the caller guarantees no concurrent access to the same checker,
+// e.g. for read-only operations like obtaining an emit resolver.
+func (p *checkerPool) getCheckerForFileNonExclusive(file *ast.SourceFile) (*checker.Checker, func()) {
p.createCheckers()
return p.fileAssociations[file], noop
}
-func (p *checkerPool) GetCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
+func (p *checkerPool) getCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
p.createCheckers()
c := p.fileAssociations[file]
idx := slices.Index(p.checkers, c)
@@ -61,10 +82,10 @@ func (p *checkerPool) GetCheckerForFileExclusive(ctx context.Context, file *ast.
})
}
-func (p *checkerPool) GetChecker(ctx context.Context) (*checker.Checker, func()) {
+// getCheckerNonExclusive returns the first checker without locking.
+func (p *checkerPool) getCheckerNonExclusive() (*checker.Checker, func()) {
p.createCheckers()
- checker := p.checkers[0]
- return checker, noop
+ return p.checkers[0], noop
}
func (p *checkerPool) createCheckers() {
@@ -101,4 +122,35 @@ func (p *checkerPool) forEachCheckerParallel(cb func(idx int, c *checker.Checker
wg.RunAndWait()
}
+func (p *checkerPool) GetGlobalDiagnostics() []*ast.Diagnostic {
+ p.createCheckers()
+ globalDiagnostics := make([][]*ast.Diagnostic, len(p.checkers))
+ p.forEachCheckerParallel(func(idx int, checker *checker.Checker) {
+ globalDiagnostics[idx] = checker.GetGlobalDiagnostics()
+ })
+ return SortAndDeduplicateDiagnostics(slices.Concat(globalDiagnostics...))
+}
+
+// forEachCheckerGroupDo runs one task per checker in parallel. Each task iterates
+// the provided files, processing only those assigned to its checker. Within each
+// checker's set, files are visited in their original order.
+func (p *checkerPool) forEachCheckerGroupDo(ctx context.Context, files []*ast.SourceFile, singleThreaded bool, cb func(c *checker.Checker, fileIndex int, file *ast.SourceFile)) {
+ p.createCheckers()
+
+ checkerCount := len(p.checkers)
+ 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)
+ }
+ }
+ })
+ }
+ wg.RunAndWait()
+}
+
func noop() {}
diff --git a/internal/compiler/program.go b/internal/compiler/program.go
index 92a3ae384e1..415ea1d8924 100644
--- a/internal/compiler/program.go
+++ b/internal/compiler/program.go
@@ -75,7 +75,12 @@ type packageNamesInfo struct {
type Program struct {
opts ProgramOptions
- checkerPool CheckerPool
+ checkerPool CheckerPool // always set; used as fallback for project system pools
+
+ // compilerCheckerPool is set only when the built-in compiler checker pool is in use
+ // (i.e. CreateCheckerPool was not provided). It enables grouped parallel iteration,
+ // non-exclusive access for emit, and direct global diagnostics collection.
+ compilerCheckerPool *checkerPool
comparePathsOptions tspath.ComparePathsOptions
@@ -313,7 +318,9 @@ func (p *Program) initCheckerPool() {
if p.opts.CreateCheckerPool != nil {
p.checkerPool = p.opts.CreateCheckerPool(p)
} else {
- p.checkerPool = newCheckerPool(p)
+ pool := newCheckerPool(p)
+ p.checkerPool = pool
+ p.compilerCheckerPool = pool
}
}
@@ -407,12 +414,15 @@ func (p *Program) BindSourceFiles() {
// Return the type checker associated with the program.
func (p *Program) GetTypeChecker(ctx context.Context) (*checker.Checker, func()) {
- return p.checkerPool.GetChecker(ctx)
+ if p.compilerCheckerPool != nil {
+ return p.compilerCheckerPool.getCheckerNonExclusive()
+ }
+ return p.checkerPool.GetChecker(ctx, nil)
}
func (p *Program) ForEachCheckerParallel(cb func(idx int, c *checker.Checker)) {
- if pool, ok := p.checkerPool.(*checkerPool); ok {
- pool.forEachCheckerParallel(cb)
+ if p.compilerCheckerPool != nil {
+ p.compilerCheckerPool.forEachCheckerParallel(cb)
}
}
@@ -421,13 +431,19 @@ func (p *Program) ForEachCheckerParallel(cb func(idx int, c *checker.Checker)) {
// types obtained from different checkers, so only non-type data (such as diagnostics or string
// representations of types) should be obtained from checkers returned by this method.
func (p *Program) GetTypeCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
- return p.checkerPool.GetCheckerForFile(ctx, file)
+ if p.compilerCheckerPool != nil {
+ return p.compilerCheckerPool.getCheckerForFileNonExclusive(file)
+ }
+ return p.checkerPool.GetChecker(ctx, file)
}
// Return a checker for the given file, locked to the current thread to prevent data races from multiple threads
// accessing the same checker. The lock will be released when the `done` function is called.
func (p *Program) GetTypeCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
- return p.checkerPool.GetCheckerForFileExclusive(ctx, file)
+ if p.compilerCheckerPool != nil {
+ return p.compilerCheckerPool.getCheckerForFileExclusive(ctx, file)
+ }
+ return p.checkerPool.GetChecker(ctx, file)
}
func (p *Program) GetResolvedModule(file ast.HasFileName, moduleReference string, mode core.ResolutionMode) *module.ResolvedModule {
@@ -477,6 +493,48 @@ func (p *Program) collectDiagnosticsFromFiles(ctx context.Context, sourceFiles [
return diagnostics
}
+// collectCheckerDiagnostics collects diagnostics from a single file or all files,
+// using a callback that receives the checker for each file. When the checker pool
+// supports grouped iteration (compiler pool), files are grouped by checker and
+// processed in parallel with one task per checker, reducing contention and improving
+// cache locality. Otherwise, falls back to per-file concurrent collection.
+func (p *Program) collectCheckerDiagnostics(ctx context.Context, sourceFile *ast.SourceFile, collect func(context.Context, *checker.Checker, *ast.SourceFile) []*ast.Diagnostic) []*ast.Diagnostic {
+ if sourceFile != nil {
+ if p.SkipTypeChecking(sourceFile, false) {
+ return nil
+ }
+ c, done := p.GetTypeCheckerForFileExclusive(ctx, sourceFile)
+ result := collect(ctx, c, sourceFile)
+ done()
+ return SortAndDeduplicateDiagnostics(result)
+ }
+ return SortAndDeduplicateDiagnostics(slices.Concat(p.collectCheckerDiagnosticsFromFiles(ctx, p.files, collect)...))
+}
+
+// collectCheckerDiagnosticsFromFiles collects checker diagnostics for a list of files.
+func (p *Program) collectCheckerDiagnosticsFromFiles(ctx context.Context, sourceFiles []*ast.SourceFile, collect func(context.Context, *checker.Checker, *ast.SourceFile) []*ast.Diagnostic) [][]*ast.Diagnostic {
+ diagnostics := make([][]*ast.Diagnostic, len(sourceFiles))
+ if p.compilerCheckerPool != nil {
+ p.compilerCheckerPool.forEachCheckerGroupDo(ctx, sourceFiles, p.SingleThreaded(), func(c *checker.Checker, fileIndex int, file *ast.SourceFile) {
+ diagnostics[fileIndex] = collect(ctx, c, file)
+ })
+ } else {
+ wg := core.NewWorkGroup(p.SingleThreaded())
+ for i, file := range sourceFiles {
+ if p.SkipTypeChecking(file, false) {
+ continue
+ }
+ wg.Queue(func() {
+ c, done := p.checkerPool.GetChecker(ctx, file)
+ diagnostics[i] = collect(ctx, c, file)
+ done()
+ })
+ }
+ wg.RunAndWait()
+ }
+ return diagnostics
+}
+
func (p *Program) GetSyntacticDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
return p.collectDiagnostics(ctx, sourceFile, false /*concurrent*/, func(_ context.Context, file *ast.SourceFile) []*ast.Diagnostic {
diags := core.Concatenate(file.Diagnostics(), file.JSDiagnostics())
@@ -533,20 +591,20 @@ func (p *Program) GetBindDiagnostics(ctx context.Context, sourceFile *ast.Source
}
func (p *Program) GetSemanticDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
- return p.collectDiagnostics(ctx, sourceFile, true /*concurrent*/, p.getSemanticDiagnosticsForFile)
+ return p.collectCheckerDiagnostics(ctx, sourceFile, p.getSemanticDiagnosticsWithChecker)
}
func (p *Program) GetSemanticDiagnosticsWithoutNoEmitFiltering(ctx context.Context, sourceFiles []*ast.SourceFile) map[*ast.SourceFile][]*ast.Diagnostic {
- diagnostics := p.collectDiagnosticsFromFiles(ctx, sourceFiles, true /*concurrent*/, p.getBindAndCheckDiagnosticsForFile)
+ allDiags := p.collectCheckerDiagnosticsFromFiles(ctx, sourceFiles, p.getBindAndCheckDiagnosticsWithChecker)
result := make(map[*ast.SourceFile][]*ast.Diagnostic, len(sourceFiles))
- for i, diags := range diagnostics {
+ for i, diags := range allDiags {
result[sourceFiles[i]] = SortAndDeduplicateDiagnostics(diags)
}
return result
}
func (p *Program) GetSuggestionDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
- return p.collectDiagnostics(ctx, sourceFile, true /*concurrent*/, p.getSuggestionDiagnosticsForFile)
+ return p.collectCheckerDiagnostics(ctx, sourceFile, p.getSuggestionDiagnosticsWithChecker)
}
func (p *Program) GetProgramDiagnostics() []*ast.Diagnostic {
@@ -1168,32 +1226,18 @@ func (p *Program) GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic {
if len(p.files) == 0 {
return nil
}
-
- pool := p.checkerPool.(*checkerPool)
-
- globalDiagnostics := make([][]*ast.Diagnostic, len(pool.checkers))
- pool.forEachCheckerParallel(func(idx int, checker *checker.Checker) {
- globalDiagnostics[idx] = checker.GetGlobalDiagnostics()
- })
-
- return SortAndDeduplicateDiagnostics(slices.Concat(globalDiagnostics...))
+ if p.compilerCheckerPool != nil {
+ return p.compilerCheckerPool.GetGlobalDiagnostics()
+ }
+ // For external pools (project system), global diagnostics are collected
+ // incrementally as checkers are used, not via a bulk query.
+ return nil
}
func (p *Program) GetDeclarationDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
return p.collectDiagnostics(ctx, sourceFile, true /*concurrent*/, p.getDeclarationDiagnosticsForFile)
}
-func (p *Program) GetOptionsDiagnostics(ctx context.Context) []*ast.Diagnostic {
- return SortAndDeduplicateDiagnostics(core.Concatenate(p.GetGlobalDiagnostics(ctx), p.getOptionsDiagnosticsOfConfigFile()))
-}
-
-func (p *Program) getOptionsDiagnosticsOfConfigFile() []*ast.Diagnostic {
- if p.Options() == nil || p.Options().ConfigFilePath == "" {
- return nil
- }
- return p.GetConfigFileParsingDiagnostics()
-}
-
func FilterNoEmitSemanticDiagnostics(diagnostics []*ast.Diagnostic, options *core.CompilerOptions) []*ast.Diagnostic {
if !options.NoEmit.IsTrue() {
return diagnostics
@@ -1203,31 +1247,25 @@ func FilterNoEmitSemanticDiagnostics(diagnostics []*ast.Diagnostic, options *cor
})
}
-func (p *Program) getSemanticDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
+func (p *Program) getSemanticDiagnosticsWithChecker(ctx context.Context, c *checker.Checker, sourceFile *ast.SourceFile) []*ast.Diagnostic {
return core.Concatenate(
- FilterNoEmitSemanticDiagnostics(p.getBindAndCheckDiagnosticsForFile(ctx, sourceFile), p.Options()),
+ FilterNoEmitSemanticDiagnostics(p.getBindAndCheckDiagnosticsWithChecker(ctx, c, 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 {
+// getBindAndCheckDiagnosticsWithChecker gets semantic diagnostics for a single file using a
+// caller-provided checker, including bind diagnostics, checker diagnostics, and handling
+// of @ts-ignore/@ts-expect-error directives.
+func (p *Program) getBindAndCheckDiagnosticsWithChecker(ctx context.Context, fileChecker *checker.Checker, sourceFile *ast.SourceFile) []*ast.Diagnostic {
compilerOptions := p.Options()
if p.SkipTypeChecking(sourceFile, false) {
return nil
}
- // IIFE to release checker as soon as possible.
- diags := func() []*ast.Diagnostic {
- fileChecker, done := p.checkerPool.GetCheckerForFileExclusive(ctx, sourceFile)
- defer done()
-
- // Getting a checker will force a bind, so this will be populated.
- diags := slices.Clip(sourceFile.BindDiagnostics())
- diags = append(diags, fileChecker.GetDiagnostics(ctx, sourceFile)...)
- return diags
- }()
+ // Checker creation forces binding, so bind diagnostics will be populated.
+ diags := slices.Clip(sourceFile.BindDiagnostics())
+ diags = append(diags, fileChecker.GetDiagnostics(ctx, sourceFile)...)
isPlainJS := ast.IsPlainJSFile(sourceFile, compilerOptions.CheckJs)
if isPlainJS {
@@ -1304,15 +1342,12 @@ func (p *Program) getDeclarationDiagnosticsForFile(ctx context.Context, sourceFi
return diagnostics
}
-func (p *Program) getSuggestionDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
+func (p *Program) getSuggestionDiagnosticsWithChecker(ctx context.Context, fileChecker *checker.Checker, sourceFile *ast.SourceFile) []*ast.Diagnostic {
if p.SkipTypeChecking(sourceFile, false) {
return nil
}
- fileChecker, done := p.checkerPool.GetCheckerForFileExclusive(ctx, sourceFile)
- defer done()
-
- // Getting a checker will force a bind, so this will be populated.
+ // Checker creation forces binding, so bind suggestion diagnostics will be populated.
diags := slices.Clip(sourceFile.BindSuggestionDiagnostics)
diags = append(diags, fileChecker.GetSuggestionDiagnostics(ctx, sourceFile)...)
@@ -1591,7 +1626,6 @@ type ProgramLike interface {
GetConfigFileParsingDiagnostics() []*ast.Diagnostic
GetSyntacticDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic
GetBindDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic
- GetOptionsDiagnostics(ctx context.Context) []*ast.Diagnostic
GetProgramDiagnostics() []*ast.Diagnostic
GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic
GetSemanticDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic
@@ -1640,18 +1674,16 @@ func GetDiagnosticsOfAnyProgram(
allDiagnostics = append(allDiagnostics, program.GetProgramDiagnostics()...)
if len(allDiagnostics) == configFileParsingDiagnosticsLength {
- // Options diagnostics include global diagnostics (even though we collect them separately),
- // and global diagnostics create checkers, which then bind all of the files. Do this binding
- // early so we can track the time.
+ // Do binding early so we can track the time.
getBindDiagnostics(ctx, file)
- allDiagnostics = append(allDiagnostics, program.GetOptionsDiagnostics(ctx)...)
-
if program.Options().ListFilesOnly.IsFalseOrUnknown() {
allDiagnostics = append(allDiagnostics, program.GetGlobalDiagnostics(ctx)...)
if len(allDiagnostics) == configFileParsingDiagnosticsLength {
allDiagnostics = append(allDiagnostics, getSemanticDiagnostics(ctx, file)...)
+ // Ask for the global diagnostics again (they were empty above); we may have found new during checking, e.g. missing globals.
+ allDiagnostics = append(allDiagnostics, program.GetGlobalDiagnostics(ctx)...)
}
if (skipNoEmitCheckForDtsDiagnostics || program.Options().NoEmit.IsTrue()) && program.Options().GetEmitDeclarations() && len(allDiagnostics) == configFileParsingDiagnosticsLength {
diff --git a/internal/execute/incremental/program.go b/internal/execute/incremental/program.go
index fe77b2996ba..76dad5e07c9 100644
--- a/internal/execute/incremental/program.go
+++ b/internal/execute/incremental/program.go
@@ -132,12 +132,6 @@ func (p *Program) GetBindDiagnostics(ctx context.Context, file *ast.SourceFile)
return p.program.GetBindDiagnostics(ctx, file)
}
-// GetOptionsDiagnostics implements compiler.AnyProgram interface.
-func (p *Program) GetOptionsDiagnostics(ctx context.Context) []*ast.Diagnostic {
- p.panicIfNoProgram("GetOptionsDiagnostics")
- return p.program.GetOptionsDiagnostics(ctx)
-}
-
func (p *Program) GetProgramDiagnostics() []*ast.Diagnostic {
p.panicIfNoProgram("GetProgramDiagnostics")
return p.program.GetProgramDiagnostics()
@@ -365,7 +359,6 @@ func (p *Program) ensureHasErrorsForState(ctx context.Context, program *compiler
len(program.GetConfigFileParsingDiagnostics()) > 0 ||
len(program.GetSyntacticDiagnostics(ctx, nil)) > 0 ||
len(program.GetProgramDiagnostics()) > 0 ||
- len(program.GetOptionsDiagnostics(ctx)) > 0 ||
len(program.GetGlobalDiagnostics(ctx)) > 0 {
p.snapshot.hasErrors = core.TSTrue
// Dont need to encode semantic errors state since the syntax and program diagnostics are encoded as present
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index f631de4609c..8c81d94a11a 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -777,6 +777,9 @@ func registerLanguageServiceDocumentRequestHandler[Req lsproto.HasTextDocumentUR
return func() error {
defer s.recover(req)
resp, lsErr := fn(s, ctx, ls, params)
+ // After any language service request, check if new global diagnostics were
+ // discovered during checking and push updated tsconfig diagnostics if so.
+ s.session.EnqueuePublishGlobalDiagnostics()
if lsErr != nil {
return lsErr
}
diff --git a/internal/project/checkerpool.go b/internal/project/checkerpool.go
index ec84b928cd7..f05df1cb3b2 100644
--- a/internal/project/checkerpool.go
+++ b/internal/project/checkerpool.go
@@ -3,6 +3,7 @@ package project
import (
"context"
"fmt"
+ "slices"
"sync"
"github.com/microsoft/typescript-go/internal/ast"
@@ -15,35 +16,37 @@ type CheckerPool struct {
maxCheckers int
program *compiler.Program
- mu sync.Mutex
- cond *sync.Cond
- createCheckersOnce sync.Once
- checkers []*checker.Checker
- locks []sync.Mutex
- inUse map[*checker.Checker]bool
- fileAssociations map[*ast.SourceFile]int
- requestAssociations map[string]int
- log func(msg string)
+ mu sync.Mutex
+ cond *sync.Cond
+ createCheckersOnce sync.Once
+ checkers []*checker.Checker
+ inUse map[*checker.Checker]bool
+ fileAssociations map[*ast.SourceFile]int
+ requestAssociations map[string]int
+ log func(msg string)
+ globalDiagAccumulated []*ast.Diagnostic
+ globalDiagChanged bool
+ globalDiagCheckerCount []int // per-checker count of globals last seen
}
var _ compiler.CheckerPool = (*CheckerPool)(nil)
func newCheckerPool(maxCheckers int, program *compiler.Program, log func(msg string)) *CheckerPool {
pool := &CheckerPool{
- program: program,
- maxCheckers: maxCheckers,
- checkers: make([]*checker.Checker, maxCheckers),
- locks: make([]sync.Mutex, maxCheckers),
- inUse: make(map[*checker.Checker]bool),
- requestAssociations: make(map[string]int),
- log: log,
+ program: program,
+ maxCheckers: maxCheckers,
+ checkers: make([]*checker.Checker, maxCheckers),
+ inUse: make(map[*checker.Checker]bool),
+ requestAssociations: make(map[string]int),
+ log: log,
+ globalDiagCheckerCount: make([]int, maxCheckers),
}
pool.cond = sync.NewCond(&pool.mu)
return pool
}
-func (p *CheckerPool) GetCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
+func (p *CheckerPool) GetChecker(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
p.mu.Lock()
defer p.mu.Unlock()
@@ -54,41 +57,35 @@ func (p *CheckerPool) GetCheckerForFile(ctx context.Context, file *ast.SourceFil
}
}
- if p.fileAssociations == nil {
- p.fileAssociations = make(map[*ast.SourceFile]int)
- }
+ if file != nil {
+ if p.fileAssociations == nil {
+ p.fileAssociations = make(map[*ast.SourceFile]int)
+ }
- if index, ok := p.fileAssociations[file]; ok {
- checker := p.checkers[index]
- if checker != nil {
- if inUse := p.inUse[checker]; !inUse {
- p.inUse[checker] = true
- if requestID != "" {
- p.requestAssociations[requestID] = index
+ if index, ok := p.fileAssociations[file]; ok {
+ checker := p.checkers[index]
+ if checker != nil {
+ if inUse := p.inUse[checker]; !inUse {
+ p.inUse[checker] = true
+ if requestID != "" {
+ p.requestAssociations[requestID] = index
+ }
+ return checker, p.createRelease(requestID, index, checker)
}
- return checker, p.createRelease(requestID, index, checker)
}
}
}
checker, index := p.getCheckerLocked(requestID)
- p.fileAssociations[file] = index
+ if file != nil {
+ if p.fileAssociations == nil {
+ p.fileAssociations = make(map[*ast.SourceFile]int)
+ }
+ p.fileAssociations[file] = index
+ }
return checker, p.createRelease(requestID, index, checker)
}
-// GetCheckerForFileExclusive is the same as GetCheckerForFile but also locks a mutex associated with the checker.
-// Call `done` to free the lock.
-func (p *CheckerPool) GetCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
- return p.GetCheckerForFile(ctx, file)
-}
-
-func (p *CheckerPool) GetChecker(ctx context.Context) (*checker.Checker, func()) {
- p.mu.Lock()
- defer p.mu.Unlock()
- checker, index := p.getCheckerLocked(core.GetRequestID(ctx))
- return checker, p.createRelease(core.GetRequestID(ctx), index, checker)
-}
-
func (p *CheckerPool) getCheckerLocked(requestID string) (*checker.Checker, int) {
if checker, index := p.getImmediatelyAvailableChecker(); checker != nil {
p.inUse[checker] = true
@@ -166,13 +163,49 @@ func (p *CheckerPool) createRelease(requestId string, index int, checker *checke
p.log(fmt.Sprintf("checkerpool: Checker for request %s was canceled, disposing it", requestId))
p.checkers[index] = nil
delete(p.inUse, checker)
+ p.globalDiagCheckerCount[index] = 0
} else {
+ p.mergeGlobalDiagnosticsFromCheckerLocked(index, checker)
p.inUse[checker] = false
}
p.cond.Signal()
}
}
+// mergeGlobalDiagnosticsFromCheckerLocked checks if the given checker has produced new global
+// diagnostics since the last time we looked, and if so merges them into the accumulated set.
+// Must be called with p.mu held.
+func (p *CheckerPool) mergeGlobalDiagnosticsFromCheckerLocked(index int, c *checker.Checker) {
+ globals := c.GetGlobalDiagnostics()
+ if len(globals) == p.globalDiagCheckerCount[index] {
+ return
+ }
+ p.globalDiagCheckerCount[index] = len(globals)
+ before := len(p.globalDiagAccumulated)
+ p.globalDiagAccumulated = compiler.SortAndDeduplicateDiagnostics(append(p.globalDiagAccumulated, globals...))
+ if len(p.globalDiagAccumulated) != before {
+ p.globalDiagChanged = true
+ }
+}
+
+// GetGlobalDiagnostics returns the accumulated global diagnostics collected from
+// all checkers that have been used so far in this pool's lifetime.
+func (p *CheckerPool) GetGlobalDiagnostics() []*ast.Diagnostic {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ return slices.Clone(p.globalDiagAccumulated)
+}
+
+// TakeNewGlobalDiagnostics reports whether new global diagnostics have been
+// accumulated since the last call, and resets the flag.
+func (p *CheckerPool) TakeNewGlobalDiagnostics() bool {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ changed := p.globalDiagChanged
+ p.globalDiagChanged = false
+ return changed
+}
+
func (p *CheckerPool) isFullLocked() bool {
for _, checker := range p.checkers {
if checker == nil {
@@ -193,29 +226,4 @@ func (p *CheckerPool) createCheckerLocked() (*checker.Checker, int) {
panic("called createCheckerLocked when pool is full")
}
-func (p *CheckerPool) isRequestCheckerInUse(requestID string) bool {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- if index, ok := p.requestAssociations[requestID]; ok {
- checker := p.checkers[index]
- if checker != nil {
- return p.inUse[checker]
- }
- }
- return false
-}
-
-func (p *CheckerPool) size() int {
- p.mu.Lock()
- defer p.mu.Unlock()
- size := 0
- for _, checker := range p.checkers {
- if checker != nil {
- size++
- }
- }
- return size
-}
-
func noop() {}
diff --git a/internal/project/project.go b/internal/project/project.go
index 7174bd34bc3..e873c3c79fa 100644
--- a/internal/project/project.go
+++ b/internal/project/project.go
@@ -1,10 +1,12 @@
package project
import (
+ "context"
"fmt"
"strings"
"sync"
+ "github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
@@ -207,6 +209,20 @@ func (p *Project) GetProgram() *compiler.Program {
return p.Program
}
+// GetProjectDiagnostics returns program diagnostics combined with any global
+// diagnostics discovered during checking. These are the diagnostics reported on
+// the tsconfig.json file.
+func (p *Project) GetProjectDiagnostics(ctx context.Context) []*ast.Diagnostic {
+ var globalDiags []*ast.Diagnostic
+ if p.checkerPool != nil {
+ globalDiags = p.checkerPool.GetGlobalDiagnostics()
+ }
+ return compiler.SortAndDeduplicateDiagnostics(core.Concatenate(
+ p.Program.GetProgramDiagnostics(),
+ globalDiags,
+ ))
+}
+
func (p *Project) HasFile(fileName string) bool {
return p.containsFile(p.toPath(fileName))
}
diff --git a/internal/project/project_test.go b/internal/project/project_test.go
index bdf8517d844..b92f27e0a3f 100644
--- a/internal/project/project_test.go
+++ b/internal/project/project_test.go
@@ -2,6 +2,7 @@ package project_test
import (
"context"
+ "strings"
"testing"
"github.com/microsoft/typescript-go/internal/bundled"
@@ -304,6 +305,56 @@ func TestPushDiagnostics(t *testing.T) {
// Should not have any calls since inferred projects don't have tsconfig.json
assert.Equal(t, len(calls), 0, "expected no PublishDiagnostics calls for inferred projects")
})
+
+ t.Run("publishes global diagnostics after checking", func(t *testing.T) {
+ t.Parallel()
+ // Use a target/lib that does not include Disposable, then write code that needs it.
+ // This triggers a deferred "Cannot find global type 'Disposable'" global diagnostic
+ // during checking, which should be accumulated and published on the tsconfig URI.
+ files := map[string]any{
+ "/src/tsconfig.json": `{
+ "compilerOptions": {
+ "target": "es2020"
+ }
+ }`,
+ "/src/index.ts": `export function f() {
+ using x = { [Symbol.dispose]() {} };
+ }`,
+ }
+ session, utils := projecttestutil.Setup(files)
+ session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
+ // Request semantic diagnostics to trigger checking, which triggers the global type resolvers.
+ ls, err := session.GetLanguageService(projecttestutil.WithRequestID(context.Background()), lsproto.DocumentUri("file:///src/index.ts"))
+ assert.NilError(t, err)
+ _, err = ls.ProvideDiagnostics(projecttestutil.WithRequestID(context.Background()), lsproto.DocumentUri("file:///src/index.ts"))
+ assert.NilError(t, err)
+ // Enqueue global diagnostics publishing (normally done by the LSP server after each request).
+ session.EnqueuePublishGlobalDiagnostics()
+ session.WaitForBackgroundTasks()
+
+ calls := utils.Client().PublishDiagnosticsCalls()
+ // Find the last call for tsconfig.json
+ var lastTsconfigCall *struct {
+ Ctx context.Context
+ Params *lsproto.PublishDiagnosticsParams
+ }
+ for i := len(calls) - 1; i >= 0; i-- {
+ if calls[i].Params.Uri == "file:///src/tsconfig.json" {
+ lastTsconfigCall = &calls[i]
+ break
+ }
+ }
+ assert.Assert(t, lastTsconfigCall != nil, "expected PublishDiagnostics call for tsconfig.json")
+ // Should have global diagnostics (e.g., Cannot find global type 'Disposable')
+ hasGlobalDiag := false
+ for _, diag := range lastTsconfigCall.Params.Diagnostics {
+ if strings.Contains(diag.Message, "Cannot find global") {
+ hasGlobalDiag = true
+ break
+ }
+ }
+ assert.Assert(t, hasGlobalDiag, "expected a 'Cannot find global' diagnostic on tsconfig.json, got: %v", lastTsconfigCall.Params.Diagnostics)
+ })
}
func TestDisplayName(t *testing.T) {
diff --git a/internal/project/session.go b/internal/project/session.go
index ec35008fef9..74d8505021b 100644
--- a/internal/project/session.go
+++ b/internal/project/session.go
@@ -140,6 +140,11 @@ type Session struct {
// are using each glob.
watches map[fileSystemWatcherKey]*fileSystemWatcherValue
watchesMu sync.Mutex
+
+ // globalDiagPublishPending is set to true when a global diagnostics publish
+ // task should be enqueued. It is reset when the task runs, coalescing multiple
+ // requests into a single background task.
+ globalDiagPublishPending atomic.Bool
}
func NewSession(init *SessionInit) *Session {
@@ -1084,7 +1089,7 @@ func (s *Session) publishProgramDiagnostics(oldSnapshot *Snapshot, newSnapshot *
if !shouldPublishProgramDiagnostics(addedProject, newSnapshot.ID()) {
return
}
- s.publishProjectDiagnostics(ctx, string(configFilePath), addedProject.Program.GetProgramDiagnostics(), newSnapshot.converters)
+ s.publishProjectDiagnostics(ctx, string(configFilePath), addedProject.GetProjectDiagnostics(ctx), newSnapshot.converters)
},
func(configFilePath tspath.Path, removedProject *Project) {
if removedProject.Kind != KindConfigured {
@@ -1096,7 +1101,7 @@ func (s *Session) publishProgramDiagnostics(oldSnapshot *Snapshot, newSnapshot *
if !shouldPublishProgramDiagnostics(newProject, newSnapshot.ID()) {
return
}
- s.publishProjectDiagnostics(ctx, string(configFilePath), newProject.Program.GetProgramDiagnostics(), newSnapshot.converters)
+ s.publishProjectDiagnostics(ctx, string(configFilePath), newProject.GetProjectDiagnostics(ctx), newSnapshot.converters)
},
)
}
@@ -1122,6 +1127,37 @@ func (s *Session) publishProjectDiagnostics(ctx context.Context, configFilePath
}
}
+// EnqueuePublishGlobalDiagnostics schedules a background check for new accumulated
+// global diagnostics from checker pools, re-publishing tsconfig diagnostics if changed.
+// Multiple calls are coalesced into a single background task.
+func (s *Session) EnqueuePublishGlobalDiagnostics() {
+ if !s.options.PushDiagnosticsEnabled {
+ return
+ }
+ if s.globalDiagPublishPending.CompareAndSwap(false, true) {
+ s.backgroundQueue.Enqueue(s.backgroundCtx, s.publishGlobalDiagnostics)
+ }
+}
+
+func (s *Session) publishGlobalDiagnostics(ctx context.Context) {
+ s.globalDiagPublishPending.Store(false)
+
+ s.snapshotMu.RLock()
+ snapshot := s.snapshot
+ snapshot.ref()
+ s.snapshotMu.RUnlock()
+ defer snapshot.Deref(s)
+
+ for _, project := range snapshot.ProjectCollection.Projects() {
+ if project.Kind != KindConfigured || project.checkerPool == nil {
+ continue
+ }
+ if project.checkerPool.TakeNewGlobalDiagnostics() {
+ s.publishProjectDiagnostics(ctx, string(project.configFilePath), project.GetProjectDiagnostics(ctx), snapshot.converters)
+ }
+ }
+}
+
func (s *Session) triggerATAForUpdatedProjects(newSnapshot *Snapshot) {
for _, project := range newSnapshot.ProjectCollection.Projects() {
if project.ShouldTriggerATA(newSnapshot.ID()) {
diff --git a/testdata/baselines/reference/submodule/fourslash/findAllReferences/findAllRefsForModuleGlobal.baseline.jsonc b/testdata/baselines/reference/submodule/fourslash/findAllReferences/findAllRefsForModuleGlobal.baseline.jsonc
index cdad54c704f..3ac0503391c 100644
--- a/testdata/baselines/reference/submodule/fourslash/findAllReferences/findAllRefsForModuleGlobal.baseline.jsonc
+++ b/testdata/baselines/reference/submodule/fourslash/findAllReferences/findAllRefsForModuleGlobal.baseline.jsonc
@@ -1,5 +1,5 @@
// === findAllReferences ===
// === /b.ts ===
// ///
-// import { x } from "/*FIND ALL REFS*/foo";
+// import { x } from "/*FIND ALL REFS*/[|foo|]";
// declare module "[|foo|]" {}
\ No newline at end of file
diff --git a/testdata/baselines/reference/submodule/fourslash/findAllReferences/findAllRefsForModuleGlobal.baseline.jsonc.diff b/testdata/baselines/reference/submodule/fourslash/findAllReferences/findAllRefsForModuleGlobal.baseline.jsonc.diff
deleted file mode 100644
index 7daa57499fe..00000000000
--- a/testdata/baselines/reference/submodule/fourslash/findAllReferences/findAllRefsForModuleGlobal.baseline.jsonc.diff
+++ /dev/null
@@ -1,9 +0,0 @@
---- old.findAllRefsForModuleGlobal.baseline.jsonc
-+++ new.findAllRefsForModuleGlobal.baseline.jsonc
-@@= skipped -0, +0 lines =@@
- // === findAllReferences ===
- // === /b.ts ===
- // ///
--// import { x } from "/*FIND ALL REFS*/[|foo|]";
-+// import { x } from "/*FIND ALL REFS*/foo";
- // declare module "[|foo|]" {}
\ No newline at end of file
diff --git a/testdata/baselines/reference/tsbuild/configFileErrors/reports-syntax-errors-in-config-file.js b/testdata/baselines/reference/tsbuild/configFileErrors/reports-syntax-errors-in-config-file.js
index 58783aac79e..2df99f87a48 100644
--- a/testdata/baselines/reference/tsbuild/configFileErrors/reports-syntax-errors-in-config-file.js
+++ b/testdata/baselines/reference/tsbuild/configFileErrors/reports-syntax-errors-in-config-file.js
@@ -63,7 +63,7 @@ export declare function bar(): void;
export function bar() { }
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *new*
-{"version":"FakeTSVersion","errors":true,"root":[[2,3]],"fileNames":["lib.es2025.full.d.ts","./a.ts","./b.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b8af959ef8294c415b0415508643e446-export function foo() { }","signature":"7ffb4ea6089b1a385965a214ba412941-export declare function foo(): void;\n","impliedNodeFormat":1},{"version":"492f7ec5be310332dc7e2ef503772d24-export function bar() { }","signature":"2f1e9992435d5724d3e1da8bdbc17eae-export declare function bar(): void;\n","impliedNodeFormat":1}],"options":{"composite":true},"semanticDiagnosticsPerFile":[1,2,3],"latestChangedDtsFile":"./b.d.ts"}
+{"version":"FakeTSVersion","errors":true,"root":[[2,3]],"fileNames":["lib.es2025.full.d.ts","./a.ts","./b.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b8af959ef8294c415b0415508643e446-export function foo() { }","signature":"7ffb4ea6089b1a385965a214ba412941-export declare function foo(): void;\n","impliedNodeFormat":1},{"version":"492f7ec5be310332dc7e2ef503772d24-export function bar() { }","signature":"2f1e9992435d5724d3e1da8bdbc17eae-export declare function bar(): void;\n","impliedNodeFormat":1}],"options":{"composite":true},"latestChangedDtsFile":"./b.d.ts"}
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *new*
{
"version": "FakeTSVersion",
@@ -124,20 +124,15 @@ export function bar() { }
"options": {
"composite": true
},
- "semanticDiagnosticsPerFile": [
- "lib.es2025.full.d.ts",
- "./a.ts",
- "./b.ts"
- ],
"latestChangedDtsFile": "./b.d.ts",
- "size": 1357
+ "size": 1320
}
tsconfig.json::
SemanticDiagnostics::
-*not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*not cached* /home/src/workspaces/project/a.ts
-*not cached* /home/src/workspaces/project/b.ts
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/a.ts
+*refresh* /home/src/workspaces/project/b.ts
Signatures::
(stored at emit) /home/src/workspaces/project/a.ts
(stored at emit) /home/src/workspaces/project/b.ts
@@ -169,9 +164,6 @@ Found 1 error in tsconfig.json[90m:7[0m
tsconfig.json::
SemanticDiagnostics::
-*not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*not cached* /home/src/workspaces/project/a.ts
-*not cached* /home/src/workspaces/project/b.ts
Signatures::
@@ -199,7 +191,7 @@ export function foo() { }
export function fooBar() { }
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified*
-{"version":"FakeTSVersion","errors":true,"root":[[2,3]],"fileNames":["lib.es2025.full.d.ts","./a.ts","./b.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"12981c250647eb82bb45c5fb79732976-export function foo() { }export function fooBar() { }","signature":"f3ff291f5185ac75eeeb6de19fc28a01-export declare function foo(): void;\nexport declare function fooBar(): void;\n","impliedNodeFormat":1},{"version":"492f7ec5be310332dc7e2ef503772d24-export function bar() { }","signature":"2f1e9992435d5724d3e1da8bdbc17eae-export declare function bar(): void;\n","impliedNodeFormat":1}],"options":{"composite":true,"declaration":true},"semanticDiagnosticsPerFile":[1,2,3],"latestChangedDtsFile":"./a.d.ts"}
+{"version":"FakeTSVersion","errors":true,"root":[[2,3]],"fileNames":["lib.es2025.full.d.ts","./a.ts","./b.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"12981c250647eb82bb45c5fb79732976-export function foo() { }export function fooBar() { }","signature":"f3ff291f5185ac75eeeb6de19fc28a01-export declare function foo(): void;\nexport declare function fooBar(): void;\n","impliedNodeFormat":1},{"version":"492f7ec5be310332dc7e2ef503772d24-export function bar() { }","signature":"2f1e9992435d5724d3e1da8bdbc17eae-export declare function bar(): void;\n","impliedNodeFormat":1}],"options":{"composite":true,"declaration":true},"latestChangedDtsFile":"./a.d.ts"}
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified*
{
"version": "FakeTSVersion",
@@ -261,20 +253,13 @@ export function fooBar() { }
"composite": true,
"declaration": true
},
- "semanticDiagnosticsPerFile": [
- "lib.es2025.full.d.ts",
- "./a.ts",
- "./b.ts"
- ],
"latestChangedDtsFile": "./a.d.ts",
- "size": 1445
+ "size": 1408
}
tsconfig.json::
SemanticDiagnostics::
-*not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*not cached* /home/src/workspaces/project/a.ts
-*not cached* /home/src/workspaces/project/b.ts
+*refresh* /home/src/workspaces/project/a.ts
Signatures::
(computed .d.ts) /home/src/workspaces/project/a.ts
@@ -295,9 +280,6 @@ Found 1 error in tsconfig.json[90m:7[0m
tsconfig.json::
SemanticDiagnostics::
-*not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*not cached* /home/src/workspaces/project/a.ts
-*not cached* /home/src/workspaces/project/b.ts
Signatures::
@@ -384,7 +366,4 @@ Output::
tsconfig.json::
SemanticDiagnostics::
-*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*refresh* /home/src/workspaces/project/a.ts
-*refresh* /home/src/workspaces/project/b.ts
Signatures::
diff --git a/testdata/baselines/reference/tsbuildWatch/configFileErrors/reports-syntax-errors-in-config-file.js b/testdata/baselines/reference/tsbuildWatch/configFileErrors/reports-syntax-errors-in-config-file.js
index a34e6b9b083..e85a51f69f0 100644
--- a/testdata/baselines/reference/tsbuildWatch/configFileErrors/reports-syntax-errors-in-config-file.js
+++ b/testdata/baselines/reference/tsbuildWatch/configFileErrors/reports-syntax-errors-in-config-file.js
@@ -64,7 +64,7 @@ export declare function bar(): void;
export function bar() { }
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *new*
-{"version":"FakeTSVersion","errors":true,"root":[[2,3]],"fileNames":["lib.es2025.full.d.ts","./a.ts","./b.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b8af959ef8294c415b0415508643e446-export function foo() { }","signature":"7ffb4ea6089b1a385965a214ba412941-export declare function foo(): void;\n","impliedNodeFormat":1},{"version":"492f7ec5be310332dc7e2ef503772d24-export function bar() { }","signature":"2f1e9992435d5724d3e1da8bdbc17eae-export declare function bar(): void;\n","impliedNodeFormat":1}],"options":{"composite":true},"semanticDiagnosticsPerFile":[1,2,3],"latestChangedDtsFile":"./b.d.ts"}
+{"version":"FakeTSVersion","errors":true,"root":[[2,3]],"fileNames":["lib.es2025.full.d.ts","./a.ts","./b.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"b8af959ef8294c415b0415508643e446-export function foo() { }","signature":"7ffb4ea6089b1a385965a214ba412941-export declare function foo(): void;\n","impliedNodeFormat":1},{"version":"492f7ec5be310332dc7e2ef503772d24-export function bar() { }","signature":"2f1e9992435d5724d3e1da8bdbc17eae-export declare function bar(): void;\n","impliedNodeFormat":1}],"options":{"composite":true},"latestChangedDtsFile":"./b.d.ts"}
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *new*
{
"version": "FakeTSVersion",
@@ -125,20 +125,15 @@ export function bar() { }
"options": {
"composite": true
},
- "semanticDiagnosticsPerFile": [
- "lib.es2025.full.d.ts",
- "./a.ts",
- "./b.ts"
- ],
"latestChangedDtsFile": "./b.d.ts",
- "size": 1357
+ "size": 1320
}
tsconfig.json::
SemanticDiagnostics::
-*not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*not cached* /home/src/workspaces/project/a.ts
-*not cached* /home/src/workspaces/project/b.ts
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/a.ts
+*refresh* /home/src/workspaces/project/b.ts
Signatures::
(stored at emit) /home/src/workspaces/project/a.ts
(stored at emit) /home/src/workspaces/project/b.ts
@@ -170,9 +165,6 @@ Output::
tsconfig.json::
SemanticDiagnostics::
-*not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*not cached* /home/src/workspaces/project/a.ts
-*not cached* /home/src/workspaces/project/b.ts
Signatures::
@@ -200,7 +192,7 @@ export function foo() { }
export function fooBar() { }
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified*
-{"version":"FakeTSVersion","errors":true,"root":[[2,3]],"fileNames":["lib.es2025.full.d.ts","./a.ts","./b.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"12981c250647eb82bb45c5fb79732976-export function foo() { }export function fooBar() { }","signature":"f3ff291f5185ac75eeeb6de19fc28a01-export declare function foo(): void;\nexport declare function fooBar(): void;\n","impliedNodeFormat":1},{"version":"492f7ec5be310332dc7e2ef503772d24-export function bar() { }","signature":"2f1e9992435d5724d3e1da8bdbc17eae-export declare function bar(): void;\n","impliedNodeFormat":1}],"options":{"composite":true,"declaration":true},"semanticDiagnosticsPerFile":[1,2,3],"latestChangedDtsFile":"./a.d.ts"}
+{"version":"FakeTSVersion","errors":true,"root":[[2,3]],"fileNames":["lib.es2025.full.d.ts","./a.ts","./b.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"12981c250647eb82bb45c5fb79732976-export function foo() { }export function fooBar() { }","signature":"f3ff291f5185ac75eeeb6de19fc28a01-export declare function foo(): void;\nexport declare function fooBar(): void;\n","impliedNodeFormat":1},{"version":"492f7ec5be310332dc7e2ef503772d24-export function bar() { }","signature":"2f1e9992435d5724d3e1da8bdbc17eae-export declare function bar(): void;\n","impliedNodeFormat":1}],"options":{"composite":true,"declaration":true},"latestChangedDtsFile":"./a.d.ts"}
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified*
{
"version": "FakeTSVersion",
@@ -262,20 +254,13 @@ export function fooBar() { }
"composite": true,
"declaration": true
},
- "semanticDiagnosticsPerFile": [
- "lib.es2025.full.d.ts",
- "./a.ts",
- "./b.ts"
- ],
"latestChangedDtsFile": "./a.d.ts",
- "size": 1445
+ "size": 1408
}
tsconfig.json::
SemanticDiagnostics::
-*not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*not cached* /home/src/workspaces/project/a.ts
-*not cached* /home/src/workspaces/project/b.ts
+*refresh* /home/src/workspaces/project/a.ts
Signatures::
(computed .d.ts) /home/src/workspaces/project/a.ts
@@ -297,9 +282,6 @@ Output::
tsconfig.json::
SemanticDiagnostics::
-*not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*not cached* /home/src/workspaces/project/a.ts
-*not cached* /home/src/workspaces/project/b.ts
Signatures::
@@ -389,7 +371,4 @@ Output::
tsconfig.json::
SemanticDiagnostics::
-*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*refresh* /home/src/workspaces/project/a.ts
-*refresh* /home/src/workspaces/project/b.ts
Signatures::
diff --git a/testdata/baselines/reference/tsc/composite/converting-to-modules.js b/testdata/baselines/reference/tsc/composite/converting-to-modules.js
index 75e50d1ddae..d815a96ec7a 100644
--- a/testdata/baselines/reference/tsc/composite/converting-to-modules.js
+++ b/testdata/baselines/reference/tsc/composite/converting-to-modules.js
@@ -53,7 +53,7 @@ declare const x = 10;
const x = 10;
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *new*
-{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2025.full.d.ts","./src/main.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"4447ab8c90027f28bdaff9f2056779ce-const x = 10;","signature":"4be7af7f970696121f4f582a5d074177-declare const x = 10;\n","affectsGlobalScope":true,"impliedNodeFormat":1}],"options":{"composite":true},"semanticDiagnosticsPerFile":[1,2],"latestChangedDtsFile":"./src/main.d.ts"}
+{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2025.full.d.ts","./src/main.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"4447ab8c90027f28bdaff9f2056779ce-const x = 10;","signature":"4be7af7f970696121f4f582a5d074177-declare const x = 10;\n","affectsGlobalScope":true,"impliedNodeFormat":1}],"options":{"composite":true},"latestChangedDtsFile":"./src/main.d.ts"}
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *new*
{
"version": "FakeTSVersion",
@@ -100,18 +100,14 @@ const x = 10;
"options": {
"composite": true
},
- "semanticDiagnosticsPerFile": [
- "lib.es2025.full.d.ts",
- "./src/main.ts"
- ],
"latestChangedDtsFile": "./src/main.d.ts",
- "size": 1174
+ "size": 1139
}
tsconfig.json::
SemanticDiagnostics::
-*not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*not cached* /home/src/workspaces/project/src/main.ts
+*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
+*refresh* /home/src/workspaces/project/src/main.ts
Signatures::
(stored at emit) /home/src/workspaces/project/src/main.ts
@@ -183,6 +179,4 @@ Output::
tsconfig.json::
SemanticDiagnostics::
-*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts
-*refresh* /home/src/workspaces/project/src/main.ts
Signatures::
diff --git a/testdata/baselines/reference/tsc/incremental/js-file-with-import-in-jsdoc-in-composite-project.js b/testdata/baselines/reference/tsc/incremental/js-file-with-import-in-jsdoc-in-composite-project.js
index 590ad3c64aa..00438f16b8c 100644
--- a/testdata/baselines/reference/tsc/incremental/js-file-with-import-in-jsdoc-in-composite-project.js
+++ b/testdata/baselines/reference/tsc/incremental/js-file-with-import-in-jsdoc-in-composite-project.js
@@ -28,8 +28,12 @@ test("", async function () {
{"compilerOptions": {"allowJs": true, "composite": true}}
tsgo --noEmit
-ExitStatus:: Success
+ExitStatus:: DiagnosticsPresent_OutputsSkipped
Output::
+[91merror[0m[90m TS2318: [0mCannot find global type 'Promise'.
+
+Found 1 error.
+
//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
///
interface Boolean {}
@@ -54,7 +58,7 @@ interface Symbol {
}
declare const console: { log(msg: any): void; };
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *new*
-{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2025.full.d.ts","./index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"a2c0c261f400e90f1ff304dbe3da7a53-test(\"\", async function () {\n ;(/** @type {typeof import(\"a\")} */ ({}))\n})\n\ntest(\"\", async function () {\n ;(/** @type {typeof import(\"a\")} */ a)\n})\n\ntest(\"\", async function () {\n (/** @type {typeof import(\"a\")} */ ({}))\n ;(/** @type {typeof import(\"a\")} */ ({}))\n})\n\ntest(\"\", async function () {\n (/** @type {typeof import(\"a\")} */ a)\n ;(/** @type {typeof import(\"a\")} */ a)\n})\n\ntest(\"\", async function () {\n (/** @type {typeof import(\"a\")} */ ({}))\n ;(/** @type {typeof import(\"a\")} */ ({}))\n})","affectsGlobalScope":true,"impliedNodeFormat":1}],"options":{"allowJs":true,"composite":true},"affectedFilesPendingEmit":[[2,17]],"emitSignatures":[2]}
+{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2025.full.d.ts","./index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"a2c0c261f400e90f1ff304dbe3da7a53-test(\"\", async function () {\n ;(/** @type {typeof import(\"a\")} */ ({}))\n})\n\ntest(\"\", async function () {\n ;(/** @type {typeof import(\"a\")} */ a)\n})\n\ntest(\"\", async function () {\n (/** @type {typeof import(\"a\")} */ ({}))\n ;(/** @type {typeof import(\"a\")} */ ({}))\n})\n\ntest(\"\", async function () {\n (/** @type {typeof import(\"a\")} */ a)\n ;(/** @type {typeof import(\"a\")} */ a)\n})\n\ntest(\"\", async function () {\n (/** @type {typeof import(\"a\")} */ ({}))\n ;(/** @type {typeof import(\"a\")} */ ({}))\n})","affectsGlobalScope":true,"impliedNodeFormat":1}],"options":{"allowJs":true,"composite":true},"affectedFilesPendingEmit":[2],"emitSignatures":[2]}
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *new*
{
"version": "FakeTSVersion",
@@ -104,11 +108,8 @@ declare const console: { log(msg: any): void; };
"affectedFilesPendingEmit": [
[
"./index.js",
- "Js|DtsEmit",
- [
- 2,
- 17
- ]
+ "Js|Dts",
+ 2
]
],
"emitSignatures": [
@@ -117,7 +118,7 @@ declare const console: { log(msg: any): void; };
"original": 2
}
],
- "size": 1633
+ "size": 1628
}
tsconfig.json::