1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 07:14:47 -05:00

refactor(lsp): implement "deno.cacheOnSave" server-side (#20632)

This commit is contained in:
Nayeem Rahman 2023-09-24 17:59:42 +01:00 committed by GitHub
parent cb9ab9c3ac
commit 33f84321b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 190 additions and 11 deletions

View file

@ -315,6 +315,11 @@ pub struct WorkspaceSettings {
#[serde(default, deserialize_with = "empty_string_none")]
pub cache: Option<String>,
/// Cache local modules and their dependencies on `textDocument/didSave`
/// notifications corresponding to them.
#[serde(default)]
pub cache_on_save: bool,
/// Override the default stores used to validate certificates. This overrides
/// the environment variable `DENO_TLS_CA_STORE` if present.
pub certificate_stores: Option<Vec<String>>,
@ -379,6 +384,7 @@ impl Default for WorkspaceSettings {
disable_paths: vec![],
enable_paths: None,
cache: None,
cache_on_save: false,
certificate_stores: None,
config: None,
import_map: None,
@ -1192,6 +1198,7 @@ mod tests {
disable_paths: vec![],
enable_paths: None,
cache: None,
cache_on_save: false,
certificate_stores: None,
config: None,
import_map: None,

View file

@ -48,6 +48,7 @@ use std::sync::Arc;
use std::thread;
use tokio::sync::mpsc;
use tokio::sync::Mutex;
use tokio::sync::RwLock;
use tokio::time::Duration;
use tokio_util::sync::CancellationToken;
use tower_lsp::lsp_types as lsp;
@ -95,14 +96,16 @@ type DiagnosticsBySource = HashMap<DiagnosticSource, VersionedDiagnostics>;
#[derive(Debug)]
struct DiagnosticsPublisher {
client: Client,
state: Arc<RwLock<DiagnosticsState>>,
diagnostics_by_specifier:
Mutex<HashMap<ModuleSpecifier, DiagnosticsBySource>>,
}
impl DiagnosticsPublisher {
pub fn new(client: Client) -> Self {
pub fn new(client: Client, state: Arc<RwLock<DiagnosticsState>>) -> Self {
Self {
client,
state,
diagnostics_by_specifier: Default::default(),
}
}
@ -116,6 +119,7 @@ impl DiagnosticsPublisher {
) -> usize {
let mut diagnostics_by_specifier =
self.diagnostics_by_specifier.lock().await;
let mut state = self.state.write().await;
let mut seen_specifiers = HashSet::with_capacity(diagnostics.len());
let mut messages_sent = 0;
@ -142,6 +146,7 @@ impl DiagnosticsPublisher {
.cloned()
.collect::<Vec<_>>();
state.update(&record.specifier, version, &all_specifier_diagnostics);
self
.client
.when_outside_lsp_lock()
@ -172,6 +177,7 @@ impl DiagnosticsPublisher {
specifiers_to_remove.push(specifier.clone());
if let Some(removed_value) = maybe_removed_value {
// clear out any diagnostics for this specifier
state.update(specifier, removed_value.version, &[]);
self
.client
.when_outside_lsp_lock()
@ -290,6 +296,60 @@ struct ChannelUpdateMessage {
batch_index: Option<usize>,
}
#[derive(Clone, Debug)]
struct SpecifierState {
has_no_cache_diagnostic: bool,
}
#[derive(Clone, Debug, Default)]
pub struct DiagnosticsState {
specifiers: HashMap<ModuleSpecifier, (Option<i32>, SpecifierState)>,
}
impl DiagnosticsState {
fn update(
&mut self,
specifier: &ModuleSpecifier,
version: Option<i32>,
diagnostics: &[lsp::Diagnostic],
) {
let current_version = self.specifiers.get(specifier).and_then(|s| s.0);
let is_new = match (version, current_version) {
(Some(arg), Some(existing)) => arg >= existing,
_ => true,
};
if is_new {
self.specifiers.insert(
specifier.clone(),
(
version,
SpecifierState {
has_no_cache_diagnostic: diagnostics.iter().any(|d| {
d.code
== Some(lsp::NumberOrString::String("no-cache".to_string()))
|| d.code
== Some(lsp::NumberOrString::String(
"no-cache-npm".to_string(),
))
}),
},
),
);
}
}
pub fn clear(&mut self, specifier: &ModuleSpecifier) {
self.specifiers.remove(specifier);
}
pub fn has_no_cache_diagnostic(&self, specifier: &ModuleSpecifier) -> bool {
self
.specifiers
.get(specifier)
.map_or(false, |s| s.1.has_no_cache_diagnostic)
}
}
#[derive(Debug)]
pub struct DiagnosticsServer {
channel: Option<mpsc::UnboundedSender<ChannelMessage>>,
@ -298,6 +358,7 @@ pub struct DiagnosticsServer {
performance: Arc<Performance>,
ts_server: Arc<TsServer>,
batch_counter: DiagnosticBatchCounter,
state: Arc<RwLock<DiagnosticsState>>,
}
impl DiagnosticsServer {
@ -313,9 +374,14 @@ impl DiagnosticsServer {
performance,
ts_server,
batch_counter: Default::default(),
state: Default::default(),
}
}
pub fn state(&self) -> Arc<RwLock<DiagnosticsState>> {
self.state.clone()
}
pub fn get_ts_diagnostics(
&self,
specifier: &ModuleSpecifier,
@ -340,6 +406,7 @@ impl DiagnosticsServer {
let (tx, mut rx) = mpsc::unbounded_channel::<ChannelMessage>();
self.channel = Some(tx);
let client = self.client.clone();
let state = self.state.clone();
let performance = self.performance.clone();
let ts_diagnostics_store = self.ts_diagnostics.clone();
let ts_server = self.ts_server.clone();
@ -353,7 +420,7 @@ impl DiagnosticsServer {
let mut lint_handle: Option<JoinHandle<()>> = None;
let mut deps_handle: Option<JoinHandle<()>> = None;
let diagnostics_publisher =
Arc::new(DiagnosticsPublisher::new(client.clone()));
Arc::new(DiagnosticsPublisher::new(client.clone(), state.clone()));
loop {
match rx.recv().await {

View file

@ -108,6 +108,7 @@ use crate::npm::NpmResolution;
use crate::tools::fmt::format_file;
use crate::tools::fmt::format_parsed_source;
use crate::util::fs::remove_dir_all_if_exists;
use crate::util::path::is_importable_ext;
use crate::util::path::specifier_to_file_path;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
@ -1446,6 +1447,12 @@ impl Inner {
async fn did_close(&mut self, params: DidCloseTextDocumentParams) {
let mark = self.performance.mark("did_close", Some(&params));
self
.diagnostics_server
.state()
.write()
.await
.clear(&params.text_document.uri);
if params.text_document.uri.scheme() == "deno" {
// we can ignore virtual text documents closing, as they don't need to
// be tracked in memory, as they are static assets that won't change
@ -3267,9 +3274,38 @@ impl tower_lsp::LanguageServer for LanguageServer {
self.0.write().await.did_change(params).await
}
async fn did_save(&self, _params: DidSaveTextDocumentParams) {
// We don't need to do anything on save at the moment, but if this isn't
// implemented, lspower complains about it not being implemented.
async fn did_save(&self, params: DidSaveTextDocumentParams) {
let uri = &params.text_document.uri;
let Ok(path) = specifier_to_file_path(uri) else {
return;
};
if !is_importable_ext(&path) {
return;
}
let diagnostics_state = {
let inner = self.0.read().await;
if !inner.config.workspace_settings().cache_on_save
|| !inner.config.specifier_enabled(uri)
{
return;
}
inner.diagnostics_server.state()
};
if !diagnostics_state.read().await.has_no_cache_diagnostic(uri) {
return;
}
if let Err(err) = self
.cache_request(Some(
serde_json::to_value(lsp_custom::CacheParams {
referrer: TextDocumentIdentifier { uri: uri.clone() },
uris: vec![TextDocumentIdentifier { uri: uri.clone() }],
})
.unwrap(),
))
.await
{
lsp_warn!("Failed to cache \"{}\" on save: {}", uri.to_string(), err);
}
}
async fn did_close(&self, params: DidCloseTextDocumentParams) {

View file

@ -292,6 +292,7 @@ pub fn get_repl_workspace_settings() -> WorkspaceSettings {
config: None,
certificate_stores: None,
cache: None,
cache_on_save: false,
import_map: None,
code_lens: Default::default(),
internal_debug: false,

View file

@ -4455,6 +4455,74 @@ fn lsp_code_actions_deno_cache_npm() {
client.shutdown();
}
#[test]
fn lsp_cache_on_save() {
let context = TestContextBuilder::new()
.use_http_server()
.use_temp_cwd()
.build();
let temp_dir = context.temp_dir();
temp_dir.write(
"file.ts",
r#"
import { printHello } from "http://localhost:4545/subdir/print_hello.ts";
printHello();
"#,
);
let mut client = context.new_lsp_command().build();
client.initialize_default();
client.write_notification(
"workspace/didChangeConfiguration",
json!({
"settings": {}
}),
);
let settings = json!({
"deno": {
"enable": true,
"cacheOnSave": true,
},
});
// one for the workspace
client.handle_configuration_request(&settings);
// one for the specifier
client.handle_configuration_request(&settings);
let diagnostics = client.did_open(json!({
"textDocument": {
"uri": temp_dir.uri().join("file.ts").unwrap(),
"languageId": "typescript",
"version": 1,
"text": temp_dir.read_to_string("file.ts"),
}
}));
assert_eq!(
diagnostics.messages_with_source("deno"),
serde_json::from_value(json!({
"uri": temp_dir.uri().join("file.ts").unwrap(),
"diagnostics": [{
"range": {
"start": { "line": 1, "character": 33 },
"end": { "line": 1, "character": 78 }
},
"severity": 1,
"code": "no-cache",
"source": "deno",
"message": "Uncached or missing remote URL: http://localhost:4545/subdir/print_hello.ts",
"data": { "specifier": "http://localhost:4545/subdir/print_hello.ts" }
}],
"version": 1
}))
.unwrap()
);
client.did_save(json!({
"textDocument": { "uri": temp_dir.uri().join("file.ts").unwrap() },
}));
assert_eq!(client.read_diagnostics().all(), vec![]);
client.shutdown();
}
#[test]
fn lsp_code_actions_imports() {
let context = TestContextBuilder::new().use_temp_cwd().build();

View file

@ -748,6 +748,10 @@ impl LspClient {
self.write_response(id, result);
}
pub fn did_save(&mut self, params: Value) {
self.write_notification("textDocument/didSave", params);
}
pub fn did_change_watched_files(&mut self, params: Value) {
self.write_notification("workspace/didChangeWatchedFiles", params);
}
@ -760,11 +764,7 @@ impl LspClient {
/// Reads the latest diagnostics. It's assumed that
pub fn read_diagnostics(&mut self) -> CollectedDiagnostics {
// ask the server what the latest diagnostic batch index is
let latest_diagnostic_batch_index =
self.get_latest_diagnostic_batch_index();
// now wait for three (deno, lint, and typescript diagnostics) batch
// wait for three (deno, lint, and typescript diagnostics) batch
// notification messages for that index
let mut read = 0;
let mut total_messages_len = 0;
@ -773,7 +773,7 @@ impl LspClient {
self.read_notification::<DiagnosticBatchNotificationParams>();
assert_eq!(method, "deno/internalTestDiagnosticBatch");
let response = response.unwrap();
if response.batch_index == latest_diagnostic_batch_index {
if response.batch_index == self.get_latest_diagnostic_batch_index() {
read += 1;
total_messages_len += response.messages_len;
}