diff options
-rw-r--r-- | cli/lsp/diagnostics.rs | 7 | ||||
-rw-r--r-- | cli/lsp/documents.rs | 61 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 10 | ||||
-rw-r--r-- | cli/tests/integration_tests_lsp.rs | 139 |
4 files changed, 214 insertions, 3 deletions
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index c069e4666..18cff6ea7 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -120,8 +120,11 @@ impl DiagnosticsServer { .collect() } - pub(crate) async fn invalidate(&self, specifier: &ModuleSpecifier) { - self.collection.lock().await.versions.remove(specifier); + pub(crate) async fn invalidate(&self, specifiers: Vec<ModuleSpecifier>) { + let mut collection = self.collection.lock().await; + for specifier in specifiers { + collection.versions.remove(&specifier); + } } pub(crate) fn start( diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 048e4bedb..5fbfcdf52 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -12,6 +12,7 @@ use deno_core::error::Context; use deno_core::ModuleSpecifier; use lspower::lsp::TextDocumentContentChangeEvent; use std::collections::HashMap; +use std::collections::HashSet; use std::ops::Range; use std::str::FromStr; @@ -138,10 +139,42 @@ impl DocumentData { #[derive(Debug, Clone, Default)] pub struct DocumentCache { + dependents_graph: HashMap<ModuleSpecifier, HashSet<ModuleSpecifier>>, docs: HashMap<ModuleSpecifier, DocumentData>, } impl DocumentCache { + /// Calculate a graph of dependents and set it on the structure. + fn calculate_dependents(&mut self) { + let mut dependents_graph: HashMap< + ModuleSpecifier, + HashSet<ModuleSpecifier>, + > = HashMap::new(); + for (specifier, data) in &self.docs { + if let Some(dependencies) = &data.dependencies { + for dependency in dependencies.values() { + if let Some(analysis::ResolvedDependency::Resolved(dep_specifier)) = + &dependency.maybe_code + { + dependents_graph + .entry(dep_specifier.clone()) + .or_default() + .insert(specifier.clone()); + } + if let Some(analysis::ResolvedDependency::Resolved(dep_specifier)) = + &dependency.maybe_type + { + dependents_graph + .entry(dep_specifier.clone()) + .or_default() + .insert(specifier.clone()); + } + } + } + } + self.dependents_graph = dependents_graph; + } + pub fn change( &mut self, specifier: &ModuleSpecifier, @@ -166,6 +199,7 @@ impl DocumentCache { pub fn close(&mut self, specifier: &ModuleSpecifier) { self.docs.remove(specifier); + self.calculate_dependents(); } pub fn contains_key(&self, specifier: &ModuleSpecifier) -> bool { @@ -183,6 +217,17 @@ impl DocumentCache { } } + // For a given specifier, get all open documents which directly or indirectly + // depend upon the specifier. + pub fn dependents( + &self, + specifier: &ModuleSpecifier, + ) -> Vec<ModuleSpecifier> { + let mut dependents = HashSet::new(); + self.recurse_dependents(specifier, &mut dependents); + dependents.into_iter().collect() + } + pub fn dependencies( &self, specifier: &ModuleSpecifier, @@ -260,6 +305,21 @@ impl DocumentCache { .collect() } + fn recurse_dependents( + &self, + specifier: &ModuleSpecifier, + dependents: &mut HashSet<ModuleSpecifier>, + ) { + if let Some(deps) = self.dependents_graph.get(specifier) { + for dep in deps { + if !dependents.contains(dep) { + dependents.insert(dep.clone()); + self.recurse_dependents(dep, dependents); + } + } + } + } + pub fn set_dependencies( &mut self, specifier: &ModuleSpecifier, @@ -267,6 +327,7 @@ impl DocumentCache { ) -> Result<(), AnyError> { if let Some(doc) = self.docs.get_mut(specifier) { doc.dependencies = maybe_dependencies; + self.calculate_dependents(); Ok(()) } else { Err(custom_error( diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 20d5c1ee7..cd8298573 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -627,6 +627,10 @@ impl Inner { if self.documents.is_diagnosable(&specifier) { self.analyze_dependencies(&specifier, ¶ms.text_document.text); + self + .diagnostics_server + .invalidate(self.documents.dependents(&specifier)) + .await; if let Err(err) = self.diagnostics_server.update() { error!("{}", err); } @@ -645,6 +649,10 @@ impl Inner { Ok(Some(source)) => { if self.documents.is_diagnosable(&specifier) { self.analyze_dependencies(&specifier, &source); + self + .diagnostics_server + .invalidate(self.documents.dependents(&specifier)) + .await; if let Err(err) = self.diagnostics_server.update() { error!("{}", err); } @@ -2511,7 +2519,7 @@ impl Inner { if let Some(source) = self.documents.content(&referrer).unwrap() { self.analyze_dependencies(&referrer, &source); } - self.diagnostics_server.invalidate(&referrer).await; + self.diagnostics_server.invalidate(vec![referrer]).await; } self.diagnostics_server.update().map_err(|err| { diff --git a/cli/tests/integration_tests_lsp.rs b/cli/tests/integration_tests_lsp.rs index 2fe984bca..cb9eae5a8 100644 --- a/cli/tests/integration_tests_lsp.rs +++ b/cli/tests/integration_tests_lsp.rs @@ -1994,6 +1994,145 @@ fn lsp_diagnostics_deno_types() { shutdown(&mut client); } +#[cfg(not(windows))] +#[test] +fn lsp_diagnostics_refresh_dependents() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file_00.ts", + "languageId": "typescript", + "version": 1, + "text": "export const a = \"a\";\n", + }, + }), + ); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file_01.ts", + "languageId": "typescript", + "version": 1, + "text": "export * from \"./file_00.ts\";\n", + }, + }), + ); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": "file:///a/file_02.ts", + "languageId": "typescript", + "version": 1, + "text": "import { a, b } from \"./file_01.ts\";\n\nconsole.log(a, b);\n" + } + }), + ) + .unwrap(); + + let (id, method, _) = client.read_request::<Value>().unwrap(); + assert_eq!(method, "workspace/configuration"); + client + .write_response(id, json!({ "enable": false })) + .unwrap(); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, maybe_params) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + assert_eq!( + maybe_params, + Some(json!({ + "uri": "file:///a/file_02.ts", + "diagnostics": [ + { + "range": { + "start": { + "line": 0, + "character": 12 + }, + "end": { + "line": 0, + "character": 13 + } + }, + "severity": 1, + "code": 2305, + "source": "deno-ts", + "message": "Module '\"./file_01.ts\"' has no exported member 'b'." + } + ], + "version": 1 + })) + ); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file_00.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "text": "export const b = \"b\";\n" + } + ] + }), + ) + .unwrap(); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::<Value>().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, maybe_params) = client + .read_notification::<lsp::PublishDiagnosticsParams>() + .unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + assert!(maybe_params.is_some()); + let params = maybe_params.unwrap(); + assert!(params.diagnostics.is_empty()); + let (method, maybe_params) = client + .read_notification::<lsp::PublishDiagnosticsParams>() + .unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + assert!(maybe_params.is_some()); + let params = maybe_params.unwrap(); + assert!(params.diagnostics.is_empty()); + let (method, maybe_params) = client + .read_notification::<lsp::PublishDiagnosticsParams>() + .unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + assert!(maybe_params.is_some()); + let params = maybe_params.unwrap(); + assert!(params.diagnostics.is_empty()); + + shutdown(&mut client); +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct PerformanceAverage { |