Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions tsc/internal/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -826,10 +826,16 @@ func (p *Program) GetSemanticDiagnostics(ctx context.Context, sourceFile *ast.So
// GetSemanticDiagnosticsForIncremental includes newly discovered globals in each
// file's cached diagnostics and leaves noEmit filtering to the builder.
func (p *Program) GetSemanticDiagnosticsForIncremental(ctx context.Context, sourceFiles []*ast.SourceFile) map[*ast.SourceFile][]*ast.Diagnostic {
allDiags := p.collectCheckerDiagnosticsFromFiles(ctx, sourceFiles, func(ctx context.Context, c *checker.Checker, file *ast.SourceFile) []*ast.Diagnostic {
collect := func(ctx context.Context, c *checker.Checker, file *ast.SourceFile) []*ast.Diagnostic {
return p.getBindAndCheckDiagnosticsWithChecker(ctx, c, file, true /*includeDeferredGlobals*/)
})
}
result := make(map[*ast.SourceFile][]*ast.Diagnostic, len(sourceFiles))
if len(sourceFiles) == 1 {
file := sourceFiles[0]
result[file] = p.collectCheckerDiagnostics(ctx, file, collect)
return result
}
allDiags := p.collectCheckerDiagnosticsFromFiles(ctx, sourceFiles, collect)
for i, diags := range allDiags {
result[sourceFiles[i]] = filterAndSortDiagnostics(diags)
}
Expand Down
25 changes: 20 additions & 5 deletions tsc/internal/execute/incremental/affectedfileshandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ func (c dtsMayChange) addFileToAffectedFilesPendingEmit(filePath tspath.PathKey,
}

type updatedSignature struct {
mu sync.Mutex
signature string
kind SignatureUpdateKind
mu sync.Mutex
signature string
kind SignatureUpdateKind
semanticDiagnostics *DiagnosticsOrBuildInfoDiagnosticsWithFileName
}

type affectedFilesHandler struct {
ctx context.Context
program *Program
collectSemanticDiagnostics bool
hasAllFilesExcludingDefaultLibraryFile atomic.Bool
updatedSignatures collections.SyncMap[tspath.PathKey, *updatedSignature]
dtsMayChange []dtsMayChange
Expand Down Expand Up @@ -101,6 +103,13 @@ func (h *affectedFilesHandler) updateShapeSignature(file *ast.SourceFile, useFil
// JSON files have no declaration output from which to compute a shape
// signature, so use the file version to conservatively invalidate dependents.
if !file.IsDeclarationFile && !ast.IsJsonSourceFile(file) && !useFileVersionAsSignature {
if h.collectSemanticDiagnostics && !h.program.snapshot.options.NoCheck.IsTrue() {
// Signature generation can discover globals before checking does. Keep the
// checking diagnostics first, without retaining incidental emit-only errors.
update.semanticDiagnostics = &DiagnosticsOrBuildInfoDiagnosticsWithFileName{
diagnostics: h.program.program.GetSemanticDiagnosticsForIncremental(h.ctx, core.SingleElementSlice(file))[file],
Comment on lines +106 to +110
}
}
update.signature = h.computeDtsSignature(file)
}
// Default is to use file version as signature
Expand Down Expand Up @@ -347,6 +356,12 @@ func (h *affectedFilesHandler) updateSnapshot() {
h.program.snapshot.semanticDiagnosticsPerFile.Delete(file)
return true
})
h.updatedSignatures.Range(func(filePath tspath.PathKey, update *updatedSignature) bool {
if update.semanticDiagnostics != nil && h.filesToRemoveDiagnostics.Has(filePath) {
h.program.snapshot.semanticDiagnosticsPerFile.Store(filePath, update.semanticDiagnostics)
}
return true
})
for _, change := range h.dtsMayChange {
for filePath, emitKind := range change {
h.program.snapshot.addFileToAffectedFilesPendingEmit(filePath, emitKind)
Expand All @@ -356,12 +371,12 @@ func (h *affectedFilesHandler) updateSnapshot() {
h.program.snapshot.buildInfoEmitPending.Store(true)
}

func collectAllAffectedFiles(ctx context.Context, program *Program) {
func collectAllAffectedFiles(ctx context.Context, program *Program, collectSemanticDiagnostics bool) {
if program.snapshot.changedFilesSet.Size() == 0 {
return
}

handler := affectedFilesHandler{ctx: ctx, program: program}
handler := affectedFilesHandler{ctx: ctx, program: program, collectSemanticDiagnostics: collectSemanticDiagnostics}
wg := core.NewWorkGroup(handler.program.program.SingleThreaded())
var result collections.SyncSet[*ast.SourceFile]
program.snapshot.changedFilesSet.Range(func(file tspath.PathKey) bool {
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/execute/incremental/emitfileshandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (h *emitFilesHandler) emitBuildInfo(options compiler.EmitOptions, result *c

func (h *emitFilesHandler) emitFilesIncremental(options compiler.EmitOptions) []*compiler.EmitResult {
// Get all affected files
collectAllAffectedFiles(h.ctx, h.program)
collectAllAffectedFiles(h.ctx, h.program, false /*collectSemanticDiagnostics*/)
if h.ctx.Err() != nil {
return nil
}
Expand Down
8 changes: 5 additions & 3 deletions tsc/internal/execute/incremental/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,15 @@ func (p *Program) Emit(ctx context.Context, options compiler.EmitOptions) *compi
func (p *Program) collectSemanticDiagnosticsOfAffectedFiles(ctx context.Context, file *ast.SourceFile) {
if p.snapshot.canUseIncrementalState() {
// Get all affected files
collectAllAffectedFiles(ctx, p)
collectAllAffectedFiles(ctx, p, true /*collectSemanticDiagnostics*/)
if ctx.Err() != nil {
return
}

if p.snapshot.semanticDiagnosticsPerFile.Size() == len(p.program.GetSourceFiles()) {
// If we have all the files,
if p.snapshot.checkPending && !p.snapshot.options.NoCheck.IsTrue() {
p.snapshot.checkPending = false
p.snapshot.buildInfoEmitPending.Store(true)
}
return
}
}
Expand Down
69 changes: 65 additions & 4 deletions tsc/internal/execute/tsctests/tsc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2326,12 +2326,10 @@ func TestTscIncremental(t *testing.T) {
edit: func(sys *TestSys) {
sys.appendFile("/home/src/workspaces/project/repro.ts", "\n// comment-only edit\n")
},
expectedDiff: "Like Strada, signature generation produces the missing-global diagnostic before semantic checking, so it is excluded from the file's semantic diagnostics.",
},
{
caption: "no change",
edit: noChange.edit,
expectedDiff: "Like Strada, the cached semantic diagnostics do not include the missing-global diagnostic produced during signature generation.",
caption: "no change",
edit: noChange.edit,
},
{
caption: "delete build info to restore the semantic diagnostic",
Expand Down Expand Up @@ -2380,6 +2378,31 @@ func TestTscIncremental(t *testing.T) {
noChange,
},
},
{
subScenario: "global diagnostics survive build signature generation",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": `{"compilerOptions": {"noEmit": true, "incremental": true}}`,
"/home/src/workspaces/project/repro.ts": `export function* values() { yield 1; }`,
},
commandLineArgs: []string{"--build"},
edits: []*tscEdit{
noChange,
{
caption: "add a comment",
edit: func(sys *TestSys) {
sys.appendFile("/home/src/workspaces/project/repro.ts", "\n// comment-only edit\n")
},
},
{
caption: "no change",
edit: noChange.edit,
},
newTscEdit("remove the generator", func(sys *TestSys) {
sys.writeFileNoError("/home/src/workspaces/project/repro.ts", `export const value = 1;`)
}),
noChange,
},
},
{
subScenario: "reverse mapped declaration consumption",
files: FileMap{
Expand Down Expand Up @@ -2521,6 +2544,44 @@ func TestTscIncremental(t *testing.T) {
},
}

for _, checkers := range []int{1, 2} {
for _, build := range []bool{false, true} {
args := []string{}
if build {
args = []string{"--build"}
}
test := &tscInput{
subScenario: fmt.Sprintf("global diagnostics with declaration emit checkers %d build %t", checkers, build),
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": fmt.Sprintf(`{"compilerOptions": {"declaration": true, "incremental": true, "checkers": %d}}`, checkers),
"/home/src/workspaces/project/generator.ts": `export function* values() { yield 1; }`,
"/home/src/workspaces/project/index.ts": `import { values } from "./generator"; export const get = values;`,
},
commandLineArgs: args,
edits: []*tscEdit{
newTscEdit("edit both modules without changing their types", func(sys *TestSys) {
sys.appendFile("/home/src/workspaces/project/generator.ts", "\n// generator comment\n")
sys.appendFile("/home/src/workspaces/project/index.ts", "\n// consumer comment\n")
}),
noChange,
newTscEdit("remove the generator", func(sys *TestSys) {
sys.writeFileNoError("/home/src/workspaces/project/generator.ts", `export function values() { return 1; }`)
}),
noChange,
newTscEdit("restore the generator", func(sys *TestSys) {
sys.writeFileNoError("/home/src/workspaces/project/generator.ts", `export function* values() { yield 2; }`)
}),
noChange,
},
}
watchTest := *test
watchTest.subScenario += " watch"
watchTest.commandLineArgs = append(args, "--watch")
watchTest.edits = core.Filter(test.edits, func(edit *tscEdit) bool { return edit != noChange })
testCases = append(testCases, test, &watchTest)
}
}

for _, test := range testCases {
test.run(t, "incremental")
}
Expand Down
Loading
Loading