summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/diagnostics.rs7
-rw-r--r--cli/lsp/documents.rs61
-rw-r--r--cli/lsp/language_server.rs10
-rw-r--r--cli/tests/integration_tests_lsp.rs139
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, &params.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 {