Skip to content

Commit 35f33d9

Browse files
authored
[ty] publish settings diagnostics (#19335)
1 parent 5d78b31 commit 35f33d9

File tree

6 files changed

+207
-33
lines changed

6 files changed

+207
-33
lines changed

crates/ty_project/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,14 @@ impl Project {
469469
self.set_file_set(db).to(IndexedFiles::lazy());
470470
}
471471
}
472+
473+
/// Check if the project's settings have any issues
474+
pub fn check_settings(&self, db: &dyn Db) -> Vec<Diagnostic> {
475+
self.settings_diagnostics(db)
476+
.iter()
477+
.map(OptionDiagnostic::to_diagnostic)
478+
.collect()
479+
}
472480
}
473481

474482
#[salsa::tracked(returns(deref), heap_size=get_size2::GetSize::get_heap_size)]

crates/ty_server/src/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ mod schedule;
2121

2222
use crate::session::client::Client;
2323
pub(crate) use api::Error;
24+
pub(crate) use api::publish_settings_diagnostics;
2425
pub(crate) use main_loop::{Action, ConnectionSender, Event, MainLoopReceiver, MainLoopSender};
25-
2626
pub(crate) type Result<T> = std::result::Result<T, api::Error>;
2727

2828
pub(crate) struct Server {

crates/ty_server/src/server/api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod traits;
1717
use self::traits::{NotificationHandler, RequestHandler};
1818
use super::{Result, schedule::BackgroundSchedule};
1919
use crate::session::client::Client;
20+
pub(crate) use diagnostics::publish_settings_diagnostics;
2021
use ruff_db::panic::PanicError;
2122

2223
/// Processes a request from the client to the server.

crates/ty_server/src/server/api/diagnostics.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ use rustc_hash::FxHashMap;
88
use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic};
99
use ruff_db::files::FileRange;
1010
use ruff_db::source::{line_index, source_text};
11+
use ruff_db::system::SystemPathBuf;
1112
use ty_project::{Db, ProjectDatabase};
1213

1314
use crate::document::{DocumentKey, FileRangeExt, ToRangeExt};
1415
use crate::session::DocumentSnapshot;
1516
use crate::session::client::Client;
17+
use crate::system::{AnySystemPath, file_to_url};
1618
use crate::{PositionEncoding, Session};
1719

1820
/// Represents the diagnostics for a text document or a notebook document.
@@ -109,6 +111,82 @@ pub(super) fn publish_diagnostics(session: &Session, key: &DocumentKey, client:
109111
}
110112
}
111113

114+
/// Publishes settings diagnostics for all the project at the given path
115+
/// using the [publish diagnostics notification].
116+
///
117+
/// [publish diagnostics notification]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics
118+
pub(crate) fn publish_settings_diagnostics(
119+
session: &mut Session,
120+
client: &Client,
121+
path: SystemPathBuf,
122+
) {
123+
// Don't publish settings diagnostics for workspace that are already doing full diagnostics.
124+
//
125+
// Note we DO NOT respect the fact that clients support pulls because these are
126+
// files they *specifically* won't pull diagnostics from us for, because we don't
127+
// claim to be an LSP for them.
128+
let has_workspace_diagnostics = session
129+
.workspaces()
130+
.for_path(&path)
131+
.map(|workspace| workspace.settings().diagnostic_mode().is_workspace())
132+
.unwrap_or(false);
133+
if has_workspace_diagnostics {
134+
return;
135+
}
136+
137+
let session_encoding = session.position_encoding();
138+
let state = session.project_state_mut(&AnySystemPath::System(path));
139+
let db = &state.db;
140+
let project = db.project();
141+
let settings_diagnostics = project.check_settings(db);
142+
143+
// We need to send diagnostics if we have non-empty ones, or we have ones to clear.
144+
// These will both almost always be empty so this function will almost always be a no-op.
145+
if settings_diagnostics.is_empty() && state.untracked_files_with_pushed_diagnostics.is_empty() {
146+
return;
147+
}
148+
149+
// Group diagnostics by URL
150+
let mut diagnostics_by_url: FxHashMap<Url, Vec<_>> = FxHashMap::default();
151+
for diagnostic in settings_diagnostics {
152+
if let Some(span) = diagnostic.primary_span() {
153+
let file = span.expect_ty_file();
154+
let Some(url) = file_to_url(db, file) else {
155+
tracing::debug!("Failed to convert file to URL at {}", file.path(db));
156+
continue;
157+
};
158+
diagnostics_by_url.entry(url).or_default().push(diagnostic);
159+
}
160+
}
161+
162+
// Record the URLs we're sending non-empty diagnostics for, so we know to clear them
163+
// the next time we publish settings diagnostics!
164+
let old_untracked = std::mem::replace(
165+
&mut state.untracked_files_with_pushed_diagnostics,
166+
diagnostics_by_url.keys().cloned().collect(),
167+
);
168+
169+
// Add empty diagnostics for any files that had diagnostics before but don't now.
170+
// This will clear them (either the file is no longer relevant to us or fixed!)
171+
for url in old_untracked {
172+
diagnostics_by_url.entry(url).or_default();
173+
}
174+
// Send the settings diagnostics!
175+
for (url, file_diagnostics) in diagnostics_by_url {
176+
// Convert diagnostics to LSP format
177+
let lsp_diagnostics = file_diagnostics
178+
.into_iter()
179+
.map(|diagnostic| to_lsp_diagnostic(db, &diagnostic, session_encoding))
180+
.collect::<Vec<_>>();
181+
182+
client.send_notification::<PublishDiagnostics>(PublishDiagnosticsParams {
183+
uri: url,
184+
diagnostics: lsp_diagnostics,
185+
version: None,
186+
});
187+
}
188+
}
189+
112190
pub(super) fn compute_diagnostics(
113191
db: &ProjectDatabase,
114192
snapshot: &DocumentSnapshot,

crates/ty_server/src/server/api/notifications/did_change_watched_files.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::server::Result;
2-
use crate::server::api::diagnostics::publish_diagnostics;
2+
use crate::server::api::diagnostics::{publish_diagnostics, publish_settings_diagnostics};
33
use crate::server::api::traits::{NotificationHandler, SyncNotificationHandler};
44
use crate::session::Session;
55
use crate::session::client::Client;
@@ -88,7 +88,8 @@ impl SyncNotificationHandler for DidChangeWatchedFiles {
8888
for (root, changes) in events_by_db {
8989
tracing::debug!("Applying changes to `{root}`");
9090

91-
let result = session.apply_changes(&AnySystemPath::System(root), changes);
91+
let result = session.apply_changes(&AnySystemPath::System(root.clone()), changes);
92+
publish_settings_diagnostics(session, client, root);
9293

9394
project_changed |= result.project_changed();
9495
}
@@ -107,7 +108,6 @@ impl SyncNotificationHandler for DidChangeWatchedFiles {
107108
publish_diagnostics(session, &key, client);
108109
}
109110
}
110-
111111
// TODO: always publish diagnostics for notebook files (since they don't use pull diagnostics)
112112
}
113113

0 commit comments

Comments
 (0)