Skip to content
Closed
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
34 changes: 34 additions & 0 deletions internal/project/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,40 @@ func TestPushDiagnostics(t *testing.T) {
assert.Equal(t, len(lastTsconfigCall.Params.Diagnostics), 0, "expected no diagnostics after removing baseUrl option")
})

t.Run("updates diagnostics when an open config file is saved", func(t *testing.T) {
t.Parallel()
files := map[string]any{
"/src/tsconfig.json": `{"compilerOptions": {"target": "es2020"}}`,
"/src/index.ts": "export const x = 1;",
}
session, utils := projecttestutil.Setup(files)
session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript)
_, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts"))
assert.NilError(t, err)
session.WaitForBackgroundTasks()

// Edit the config file in the editor and save it. No other request follows, so the
// save is what has to get the new diagnostics published.
newContent := `{"compilerOptions": {"target": "es5"}}`
session.DidOpenFile(context.Background(), "file:///src/tsconfig.json", 1, files["/src/tsconfig.json"].(string), lsproto.LanguageKindJSON)
session.DidChangeFile(context.Background(), "file:///src/tsconfig.json", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
{WholeDocument: &lsproto.TextDocumentContentChangeWholeDocument{Text: newContent}},
})
assert.NilError(t, utils.FS().WriteFile("/src/tsconfig.json", newContent))
session.DidSaveFile(context.Background(), "file:///src/tsconfig.json")
session.WaitForBackgroundTasks()

calls := utils.Client().PublishDiagnosticsCalls()
tsconfigCalls := filterDiagnosticsByURI(calls, "file:///src/tsconfig.json", 0)
assert.Assert(t, len(tsconfigCalls) > 0, "expected PublishDiagnostics call for tsconfig.json")
lastTsconfigCall := tsconfigCalls[len(tsconfigCalls)-1]

expectedMessage := "Option 'target=ES5' has been removed."
assert.Assert(t, slices.ContainsFunc(lastTsconfigCall.Params.Diagnostics, func(diag *lsproto.Diagnostic) bool {
return strings.Contains(diag.Message.AsString(), expectedMessage)
}), "expected removed target diagnostic on tsconfig.json, got: %v", lastTsconfigCall.Params.Diagnostics)
})

t.Run("does not publish for inferred projects", func(t *testing.T) {
t.Parallel()
files := map[string]any{
Expand Down
24 changes: 23 additions & 1 deletion internal/project/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
UpdateReasonRequestedLoadProjectTree
UpdateReasonRequestedLanguageServiceWithAutoImports
UpdateReasonIdleCleanDiskCache
UpdateReasonDidSaveConfigFile
)

// watchRequestTimeout is the maximum time to wait for the client to respond to
Expand Down Expand Up @@ -360,11 +361,32 @@ func (s *Session) DidChangeFile(ctx context.Context, uri lsproto.DocumentUri, ve
func (s *Session) DidSaveFile(ctx context.Context, uri lsproto.DocumentUri) {
s.scheduleIdleCacheClean()
s.pendingFileChangesMu.Lock()
defer s.pendingFileChangesMu.Unlock()
s.pendingFileChanges = append(s.pendingFileChanges, FileChange{
Kind: FileChangeKindSave,
URI: uri,
})
s.pendingFileChangesMu.Unlock()

if s.isConfigFile(uri) {
// Diagnostics for config files are only pushed as part of a snapshot update.
// Editing a config file usually isn't followed by any request that would flush
// the pending changes, so schedule an update to get its diagnostics republished.
s.ScheduleSnapshotUpdate(UpdateReasonDidSaveConfigFile)
}
}

// isConfigFile returns true if the given file is a config file known to the current
// snapshot, or is named like one.
func (s *Session) isConfigFile(uri lsproto.DocumentUri) bool {
fileName := uri.FileName()
baseName := tspath.GetBaseFileName(fileName)
snapshot := s.Snapshot()
if baseName == "tsconfig.json" || baseName == "jsconfig.json" ||
(snapshot.ConfigFileRegistry.customConfigFileName != "" && baseName == snapshot.ConfigFileRegistry.customConfigFileName) {
return true
}
// Configs pulled in by `extends` or project references can be named anything.
return snapshot.ConfigFileRegistry.GetConfig(snapshot.toPath(fileName)) != nil
}

func (s *Session) DidChangeWatchedFiles(ctx context.Context, changes []*lsproto.FileEvent) {
Expand Down
2 changes: 2 additions & 0 deletions internal/project/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ func (s *Snapshot) Clone(ctx context.Context, change SnapshotChange, overlays ma
logger.Logf("Reason: DidOpenFile - %s", change.fileChanges.Opened)
case UpdateReasonDidCloseFile:
logger.Logf("Reason: DidCloseFile - %v", change.fileChanges.Closed)
case UpdateReasonDidSaveConfigFile:
logger.Logf("Reason: DidSaveConfigFile - %v", change.fileChanges.Changed)
case UpdateReasonDidChangeCompilerOptionsForInferredProjects:
logger.Logf("Reason: DidChangeCompilerOptionsForInferredProjects")
case UpdateReasonRequestedLanguageServicePendingChanges:
Expand Down