summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/analysis.rs39
-rw-r--r--cli/lsp/diagnostics.rs715
-rw-r--r--cli/lsp/language_server.rs24
3 files changed, 353 insertions, 425 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index fd848c564..19ee60d4c 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -75,6 +75,24 @@ pub struct Reference {
range: Range,
}
+impl Reference {
+ pub fn to_diagnostic(&self) -> lsp::Diagnostic {
+ match &self.category {
+ Category::Lint { message, code, .. } => lsp::Diagnostic {
+ range: self.range,
+ severity: Some(lsp::DiagnosticSeverity::Warning),
+ code: Some(lsp::NumberOrString::String(code.to_string())),
+ code_description: None,
+ source: Some("deno-lint".to_string()),
+ message: message.to_string(),
+ related_information: None,
+ tags: None, // we should tag unused code
+ data: None,
+ },
+ }
+ }
+}
+
fn as_lsp_range(range: &deno_lint::diagnostic::Range) -> Range {
Range {
start: Position {
@@ -116,27 +134,6 @@ pub fn get_lint_references(
)
}
-pub fn references_to_diagnostics(
- references: Vec<Reference>,
-) -> Vec<lsp::Diagnostic> {
- references
- .into_iter()
- .map(|r| match r.category {
- Category::Lint { message, code, .. } => lsp::Diagnostic {
- range: r.range,
- severity: Some(lsp::DiagnosticSeverity::Warning),
- code: Some(lsp::NumberOrString::String(code)),
- code_description: None,
- source: Some("deno-lint".to_string()),
- message,
- related_information: None,
- tags: None, // we should tag unused code
- data: None,
- },
- })
- .collect()
-}
-
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Dependency {
pub is_dynamic: bool,
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index a67f3805e..a5b8df879 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -1,8 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-use super::analysis::get_lint_references;
-use super::analysis::references_to_diagnostics;
-use super::analysis::ResolvedDependency;
+use super::analysis;
use super::language_server;
use super::tsc;
@@ -12,199 +10,128 @@ use crate::tokio_util::create_basic_runtime;
use deno_core::error::anyhow;
use deno_core::error::AnyError;
-use deno_core::serde_json;
-use deno_core::serde_json::json;
+use deno_core::resolve_url;
use deno_core::ModuleSpecifier;
use log::error;
use lspower::lsp;
-use lspower::Client;
use std::collections::HashMap;
use std::collections::HashSet;
use std::mem;
use std::sync::Arc;
-use std::sync::Mutex;
use std::thread;
use tokio::sync::mpsc;
-use tokio::sync::oneshot;
+use tokio::sync::Mutex;
use tokio::time::sleep;
use tokio::time::Duration;
use tokio::time::Instant;
-#[derive(Debug, Clone, Hash, PartialEq, Eq)]
-pub enum DiagnosticSource {
+pub type DiagnosticRecord =
+ (ModuleSpecifier, Option<i32>, Vec<lsp::Diagnostic>);
+pub type DiagnosticVec = Vec<DiagnosticRecord>;
+type TsDiagnosticsMap = HashMap<String, Vec<diagnostics::Diagnostic>>;
+
+#[derive(Debug, Hash, Clone, PartialEq, Eq)]
+pub(crate) enum DiagnosticSource {
Deno,
- Lint,
+ DenoLint,
TypeScript,
}
-#[derive(Debug)]
-enum DiagnosticRequest {
- Get(
- ModuleSpecifier,
- DiagnosticSource,
- oneshot::Sender<Vec<lsp::Diagnostic>>,
- ),
- Invalidate(ModuleSpecifier),
- Update,
+#[derive(Debug, Default)]
+struct DiagnosticCollection {
+ map: HashMap<(ModuleSpecifier, DiagnosticSource), Vec<lsp::Diagnostic>>,
+ versions: HashMap<ModuleSpecifier, HashMap<DiagnosticSource, i32>>,
+ changes: HashSet<ModuleSpecifier>,
}
-/// Given a client and a diagnostics collection, publish the appropriate changes
-/// to the client.
-async fn publish_diagnostics(
- client: &Client,
- collection: Arc<Mutex<DiagnosticCollection>>,
- snapshot: &language_server::StateSnapshot,
-) {
- let mut collection = collection.lock().unwrap();
- let maybe_changes = collection.take_changes();
- if let Some(diagnostic_changes) = maybe_changes {
- for specifier in diagnostic_changes {
- // TODO(@kitsonk) not totally happy with the way we collect and store
- // different types of diagnostics and offer them up to the client, we
- // do need to send "empty" vectors though when a particular feature is
- // disabled, otherwise the client will not clear down previous
- // diagnostics
- let mut diagnostics: Vec<lsp::Diagnostic> =
- if snapshot.config.workspace_settings.lint {
- collection
- .diagnostics_for(&specifier, &DiagnosticSource::Lint)
- .cloned()
- .collect()
- } else {
- vec![]
- };
- if snapshot.config.specifier_enabled(&specifier) {
- diagnostics.extend(
- collection
- .diagnostics_for(&specifier, &DiagnosticSource::TypeScript)
- .cloned(),
- );
- diagnostics.extend(
- collection
- .diagnostics_for(&specifier, &DiagnosticSource::Deno)
- .cloned(),
- );
- }
- let uri = specifier.clone();
- let version = snapshot.documents.version(&specifier);
- client.publish_diagnostics(uri, diagnostics, version).await;
- }
+impl DiagnosticCollection {
+ pub fn get(
+ &self,
+ specifier: &ModuleSpecifier,
+ source: DiagnosticSource,
+ ) -> impl Iterator<Item = &lsp::Diagnostic> {
+ self
+ .map
+ .get(&(specifier.clone(), source))
+ .into_iter()
+ .flatten()
}
-}
-async fn update_diagnostics(
- client: &Client,
- collection: Arc<Mutex<DiagnosticCollection>>,
- snapshot: &language_server::StateSnapshot,
- ts_server: &tsc::TsServer,
-) {
- let mark = snapshot.performance.mark("update_diagnostics", None::<()>);
- let lint_enabled = snapshot.config.workspace_settings.lint;
-
- let lint = async {
- let collection = collection.clone();
- if lint_enabled {
- let mark = snapshot
- .performance
- .mark("update_diagnostics_lint", None::<()>);
- let diagnostics =
- generate_lint_diagnostics(snapshot.clone(), collection.clone()).await;
- {
- let mut collection = collection.lock().unwrap();
- for (specifier, version, diagnostics) in diagnostics {
- collection.set(
- specifier,
- DiagnosticSource::Lint,
- version,
- diagnostics,
- );
- }
- }
- publish_diagnostics(client, collection, snapshot).await;
- snapshot.performance.measure(mark);
- };
- };
+ pub fn get_version(
+ &self,
+ specifier: &ModuleSpecifier,
+ source: &DiagnosticSource,
+ ) -> Option<i32> {
+ let source_version = self.versions.get(specifier)?;
+ source_version.get(source).cloned()
+ }
- let ts = async {
- let collection = collection.clone();
- let mark = snapshot
- .performance
- .mark("update_diagnostics_ts", None::<()>);
- let diagnostics =
- generate_ts_diagnostics(snapshot.clone(), collection.clone(), ts_server)
- .await
- .map_err(|err| {
- error!("Error generating TypeScript diagnostics: {}", err);
- err
- })
- .unwrap_or_default();
- {
- let mut collection = collection.lock().unwrap();
- for (specifier, version, diagnostics) in diagnostics {
- collection.set(
- specifier,
- DiagnosticSource::TypeScript,
- version,
- diagnostics,
- );
- }
+ pub fn set(&mut self, source: DiagnosticSource, record: DiagnosticRecord) {
+ let (specifier, maybe_version, diagnostics) = record;
+ self
+ .map
+ .insert((specifier.clone(), source.clone()), diagnostics);
+ if let Some(version) = maybe_version {
+ let source_version = self.versions.entry(specifier.clone()).or_default();
+ source_version.insert(source, version);
}
- publish_diagnostics(client, collection, snapshot).await;
- snapshot.performance.measure(mark);
- };
+ self.changes.insert(specifier);
+ }
- let deps = async {
- let collection = collection.clone();
- let mark = snapshot
- .performance
- .mark("update_diagnostics_deps", None::<()>);
- let diagnostics =
- generate_dependency_diagnostics(snapshot.clone(), collection.clone())
- .await
- .map_err(|err| {
- error!("Error generating dependency diagnostics: {}", err);
- err
- })
- .unwrap_or_default();
- {
- let mut collection = collection.lock().unwrap();
- for (specifier, version, diagnostics) in diagnostics {
- collection.set(specifier, DiagnosticSource::Deno, version, diagnostics);
- }
+ pub fn take_changes(&mut self) -> Option<HashSet<ModuleSpecifier>> {
+ if self.changes.is_empty() {
+ None
+ } else {
+ Some(mem::take(&mut self.changes))
}
- publish_diagnostics(client, collection, snapshot).await;
- snapshot.performance.measure(mark);
- };
-
- tokio::join!(lint, ts, deps);
-
- snapshot.performance.measure(mark);
+ }
}
-/// A server which calculates diagnostics in its own thread and publishes them
-/// to an LSP client.
#[derive(Debug)]
-pub(crate) struct DiagnosticsServer(
- Option<mpsc::UnboundedSender<DiagnosticRequest>>,
-);
+pub(crate) struct DiagnosticsServer {
+ channel: Option<mpsc::UnboundedSender<()>>,
+ collection: Arc<Mutex<DiagnosticCollection>>,
+}
impl DiagnosticsServer {
pub(crate) fn new() -> Self {
- Self(None)
+ let collection = Arc::new(Mutex::new(DiagnosticCollection::default()));
+ Self {
+ channel: None,
+ collection,
+ }
+ }
+
+ pub(crate) async fn get(
+ &self,
+ specifier: &ModuleSpecifier,
+ source: DiagnosticSource,
+ ) -> Vec<lsp::Diagnostic> {
+ self
+ .collection
+ .lock()
+ .await
+ .get(specifier, source)
+ .cloned()
+ .collect()
+ }
+
+ pub(crate) async fn invalidate(&self, specifier: &ModuleSpecifier) {
+ self.collection.lock().await.versions.remove(specifier);
}
pub(crate) fn start(
&mut self,
- language_server: Arc<tokio::sync::Mutex<language_server::Inner>>,
- client: Client,
+ language_server: Arc<Mutex<language_server::Inner>>,
+ client: lspower::Client,
ts_server: Arc<tsc::TsServer>,
) {
- let (tx, mut rx) = mpsc::unbounded_channel::<DiagnosticRequest>();
- self.0 = Some(tx);
+ let (tx, mut rx) = mpsc::unbounded_channel::<()>();
+ self.channel = Some(tx);
+ let collection = self.collection.clone();
let _join_handle = thread::spawn(move || {
let runtime = create_basic_runtime();
- let collection = Arc::new(Mutex::new(DiagnosticCollection::default()));
runtime.block_on(async {
// Debounce timer delay. 150ms between keystrokes is about 45 WPM, so we
@@ -229,23 +156,10 @@ impl DiagnosticsServer {
// up-to-date state is used to produce diagnostics.
tokio::select! {
maybe_request = rx.recv() => {
- use DiagnosticRequest::*;
match maybe_request {
- None => break, // Request channel closed.
- Some(Get(specifier, source, tx)) => {
- let diagnostics = collection
- .lock()
- .unwrap()
- .diagnostics_for(&specifier, &source)
- .cloned()
- .collect();
- // If this fails, the requestor disappeared; not a problem.
- let _ = tx.send(diagnostics);
- }
- Some(Invalidate(specifier)) => {
- collection.lock().unwrap().invalidate(&specifier);
- }
- Some(Update) => {
+ // channel has closed
+ None => break,
+ Some(_) => {
dirty = true;
debounce_timer.as_mut().reset(Instant::now() + DELAY);
}
@@ -269,142 +183,13 @@ impl DiagnosticsServer {
});
}
- pub async fn get(
- &self,
- specifier: ModuleSpecifier,
- source: DiagnosticSource,
- ) -> Result<Vec<lsp::Diagnostic>, AnyError> {
- let (tx, rx) = oneshot::channel::<Vec<lsp::Diagnostic>>();
- if let Some(self_tx) = &self.0 {
- self_tx.send(DiagnosticRequest::Get(specifier, source, tx))?;
- rx.await.map_err(|err| err.into())
- } else {
- Err(anyhow!("diagnostic server not started"))
- }
- }
-
- pub fn invalidate(&self, specifier: ModuleSpecifier) -> Result<(), AnyError> {
- if let Some(tx) = &self.0 {
- tx.send(DiagnosticRequest::Invalidate(specifier))
- .map_err(|err| err.into())
- } else {
- Err(anyhow!("diagnostic server not started"))
- }
- }
-
- pub fn update(&self) -> Result<(), AnyError> {
- if let Some(tx) = &self.0 {
- tx.send(DiagnosticRequest::Update).map_err(|err| err.into())
- } else {
- Err(anyhow!("diagnostic server not started"))
- }
- }
-}
-
-#[derive(Debug, Default, Clone)]
-struct DiagnosticCollection {
- map: HashMap<(ModuleSpecifier, DiagnosticSource), Vec<lsp::Diagnostic>>,
- versions: HashMap<ModuleSpecifier, HashMap<DiagnosticSource, i32>>,
- changes: HashSet<ModuleSpecifier>,
-}
-
-impl DiagnosticCollection {
- pub fn set(
- &mut self,
- specifier: ModuleSpecifier,
- source: DiagnosticSource,
- version: Option<i32>,
- diagnostics: Vec<lsp::Diagnostic>,
- ) {
- self
- .map
- .insert((specifier.clone(), source.clone()), diagnostics);
- if let Some(version) = version {
- let source_versions = self.versions.entry(specifier.clone()).or_default();
- source_versions.insert(source, version);
- }
- self.changes.insert(specifier);
- }
-
- pub fn diagnostics_for(
- &self,
- specifier: &ModuleSpecifier,
- source: &DiagnosticSource,
- ) -> impl Iterator<Item = &lsp::Diagnostic> {
- self
- .map
- .get(&(specifier.clone(), source.clone()))
- .into_iter()
- .flatten()
- }
-
- pub fn get_version(
- &self,
- specifier: &ModuleSpecifier,
- source: &DiagnosticSource,
- ) -> Option<i32> {
- if let Some(source_versions) = self.versions.get(specifier) {
- source_versions.get(source).cloned()
+ pub(crate) fn update(&self) -> Result<(), AnyError> {
+ if let Some(tx) = &self.channel {
+ tx.send(()).map_err(|err| err.into())
} else {
- None
+ Err(anyhow!("diagnostics server not started"))
}
}
-
- pub fn invalidate(&mut self, specifier: &ModuleSpecifier) {
- self.versions.remove(specifier);
- }
-
- pub fn take_changes(&mut self) -> Option<HashSet<ModuleSpecifier>> {
- if self.changes.is_empty() {
- return None;
- }
- Some(mem::take(&mut self.changes))
- }
-}
-
-pub type DiagnosticVec =
- Vec<(ModuleSpecifier, Option<i32>, Vec<lsp::Diagnostic>)>;
-
-async fn generate_lint_diagnostics(
- state_snapshot: language_server::StateSnapshot,
- collection: Arc<Mutex<DiagnosticCollection>>,
-) -> DiagnosticVec {
- tokio::task::spawn_blocking(move || {
- let mut diagnostic_list = Vec::new();
- for specifier in state_snapshot.documents.open_specifiers() {
- let version = state_snapshot.documents.version(specifier);
- let current_version = collection
- .lock()
- .unwrap()
- .get_version(specifier, &DiagnosticSource::Lint);
- if version != current_version {
- let media_type = MediaType::from(specifier);
- if let Ok(Some(source_code)) =
- state_snapshot.documents.content(specifier)
- {
- if let Ok(references) =
- get_lint_references(specifier, &media_type, &source_code)
- {
- if !references.is_empty() {
- diagnostic_list.push((
- specifier.clone(),
- version,
- references_to_diagnostics(references),
- ));
- } else {
- diagnostic_list.push((specifier.clone(), version, Vec::new()));
- }
- }
- } else {
- error!("Missing file contents for: {}", specifier);
- }
- }
- }
-
- diagnostic_list
- })
- .await
- .unwrap()
}
impl<'a> From<&'a diagnostics::DiagnosticCategory> for lsp::DiagnosticSeverity {
@@ -433,18 +218,6 @@ impl<'a> From<&'a diagnostics::Position> for lsp::Position {
}
}
-fn to_lsp_range(
- start: &diagnostics::Position,
- end: &diagnostics::Position,
-) -> lsp::Range {
- lsp::Range {
- start: start.into(),
- end: end.into(),
- }
-}
-
-type TsDiagnostics = HashMap<String, Vec<diagnostics::Diagnostic>>;
-
fn get_diagnostic_message(diagnostic: &diagnostics::Diagnostic) -> String {
if let Some(message) = diagnostic.message_text.clone() {
message
@@ -455,6 +228,16 @@ fn get_diagnostic_message(diagnostic: &diagnostics::Diagnostic) -> String {
}
}
+fn to_lsp_range(
+ start: &diagnostics::Position,
+ end: &diagnostics::Position,
+) -> lsp::Range {
+ lsp::Range {
+ start: start.into(),
+ end: end.into(),
+ }
+}
+
fn to_lsp_related_information(
related_information: &Option<Vec<diagnostics::Diagnostic>>,
) -> Option<Vec<lsp::DiagnosticRelatedInformation>> {
@@ -482,7 +265,7 @@ fn to_lsp_related_information(
}
fn ts_json_to_diagnostics(
- diagnostics: &[diagnostics::Diagnostic],
+ diagnostics: Vec<diagnostics::Diagnostic>,
) -> Vec<lsp::Diagnostic> {
diagnostics
.iter()
@@ -514,91 +297,139 @@ fn ts_json_to_diagnostics(
.collect()
}
-async fn generate_ts_diagnostics(
- state_snapshot: language_server::StateSnapshot,
+async fn generate_lint_diagnostics(
+ snapshot: &language_server::StateSnapshot,
collection: Arc<Mutex<DiagnosticCollection>>,
- ts_server: &tsc::TsServer,
) -> Result<DiagnosticVec, AnyError> {
- let mut diagnostics = Vec::new();
- let mut specifiers = Vec::new();
- {
- let collection = collection.lock().unwrap();
- for specifier in state_snapshot.documents.open_specifiers() {
- if state_snapshot.config.specifier_enabled(specifier) {
- let version = state_snapshot.documents.version(specifier);
- let current_version =
- collection.get_version(specifier, &DiagnosticSource::TypeScript);
+ let documents = snapshot.documents.clone();
+ let workspace_settings = snapshot.config.workspace_settings.clone();
+ tokio::task::spawn(async move {
+ let mut diagnostics_vec = Vec::new();
+ if workspace_settings.lint {
+ for specifier in documents.open_specifiers() {
+ let version = documents.version(specifier);
+ let current_version = collection
+ .lock()
+ .await
+ .get_version(specifier, &DiagnosticSource::DenoLint);
if version != current_version {
- specifiers.push(specifier.clone());
+ let media_type = MediaType::from(specifier);
+ if let Ok(Some(source_code)) = documents.content(specifier) {
+ if let Ok(references) = analysis::get_lint_references(
+ specifier,
+ &media_type,
+ &source_code,
+ ) {
+ let diagnostics =
+ references.into_iter().map(|r| r.to_diagnostic()).collect();
+ diagnostics_vec.push((specifier.clone(), version, diagnostics));
+ } else {
+ diagnostics_vec.push((specifier.clone(), version, Vec::new()));
+ }
+ } else {
+ error!("Missing file contents for: {}", specifier);
+ }
}
}
}
- }
+ Ok(diagnostics_vec)
+ })
+ .await
+ .unwrap()
+}
+
+async fn generate_ts_diagnostics(
+ snapshot: &language_server::StateSnapshot,
+ collection: Arc<Mutex<DiagnosticCollection>>,
+ ts_server: &tsc::TsServer,
+) -> Result<DiagnosticVec, AnyError> {
+ let mut diagnostics_vec = Vec::new();
+ let specifiers: Vec<ModuleSpecifier> = {
+ let collection = collection.lock().await;
+ snapshot
+ .documents
+ .open_specifiers()
+ .iter()
+ .filter_map(|&s| {
+ let version = snapshot.documents.version(s);
+ let current_version =
+ collection.get_version(s, &DiagnosticSource::TypeScript);
+ if version == current_version {
+ None
+ } else {
+ Some(s.clone())
+ }
+ })
+ .collect()
+ };
if !specifiers.is_empty() {
let req = tsc::RequestMethod::GetDiagnostics(specifiers);
- let res = ts_server.request(state_snapshot.clone(), req).await?;
- let ts_diagnostic_map: TsDiagnostics = serde_json::from_value(res)?;
- for (specifier_str, ts_diagnostics) in ts_diagnostic_map.iter() {
- let specifier = deno_core::resolve_url(specifier_str)?;
- let version = state_snapshot.documents.version(&specifier);
- diagnostics.push((
+ let ts_diagnostics_map: TsDiagnosticsMap =
+ ts_server.request(snapshot.clone(), req).await?;
+ for (specifier_str, ts_diagnostics) in ts_diagnostics_map {
+ let specifier = resolve_url(&specifier_str)?;
+ let version = snapshot.documents.version(&specifier);
+ diagnostics_vec.push((
specifier,
version,
ts_json_to_diagnostics(ts_diagnostics),
));
}
}
- Ok(diagnostics)
+ Ok(diagnostics_vec)
}
-async fn generate_dependency_diagnostics(
- mut state_snapshot: language_server::StateSnapshot,
+/// Generate diagnostics for dependencies of a module, attempting to resolve
+/// dependencies on the local file system or in the DENO_DIR cache.
+async fn generate_deps_diagnostics(
+ snapshot: &language_server::StateSnapshot,
collection: Arc<Mutex<DiagnosticCollection>>,
) -> Result<DiagnosticVec, AnyError> {
- tokio::task::spawn_blocking(move || {
- let mut diagnostics = Vec::new();
-
- let sources = &mut state_snapshot.sources;
- for specifier in state_snapshot.documents.open_specifiers() {
- if !state_snapshot.config.specifier_enabled(specifier) {
+ let config = snapshot.config.clone();
+ let documents = snapshot.documents.clone();
+ let sources = snapshot.sources.clone();
+ tokio::task::spawn(async move {
+ let mut diagnostics_vec = Vec::new();
+
+ for specifier in documents.open_specifiers() {
+ if !config.specifier_enabled(specifier) {
continue;
}
- let version = state_snapshot.documents.version(specifier);
- let current_version = collection.lock().unwrap().get_version(specifier, &DiagnosticSource::Deno);
+ let version = documents.version(specifier);
+ let current_version = collection
+ .lock()
+ .await
+ .get_version(specifier, &DiagnosticSource::Deno);
if version != current_version {
- let mut diagnostic_list = Vec::new();
- if let Some(dependencies) = state_snapshot.documents.dependencies(specifier) {
- for (_, dependency) in dependencies.iter() {
- if let (Some(code), Some(range)) = (
- &dependency.maybe_code,
- &dependency.maybe_code_specifier_range,
- ) {
- match code.clone() {
- ResolvedDependency::Err(dependency_err) => {
- diagnostic_list.push(lsp::Diagnostic {
- range: *range,
- severity: Some(lsp::DiagnosticSeverity::Error),
- code: Some(dependency_err.as_code()),
- code_description: None,
- source: Some("deno".to_string()),
- message: format!("{}", dependency_err),
- related_information: None,
- tags: None,
- data: None,
- })
- }
- ResolvedDependency::Resolved(specifier) => {
- if !(state_snapshot.documents.contains_key(&specifier) || sources.contains_key(&specifier)) {
- let scheme = specifier.scheme();
- let (code, message) = if scheme == "file" {
- (Some(lsp::NumberOrString::String("no-local".to_string())), format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier))
- } else if scheme == "data" {
- (Some(lsp::NumberOrString::String("no-cache-data".to_string())), "Uncached data URL.".to_string())
- } else {
- (Some(lsp::NumberOrString::String("no-cache".to_string())), format!("Unable to load the remote module: \"{}\".", specifier))
+ let mut diagnostics = Vec::new();
+ if let Some(dependencies) = documents.dependencies(specifier) {
+ for (_, dependency) in dependencies {
+ // TODO(@kitsonk) add diagnostics for maybe_type dependencies
+ if let (Some(code), Some(range)) =
+ (dependency.maybe_code, dependency.maybe_code_specifier_range)
+ {
+ match code {
+ analysis::ResolvedDependency::Err(err) => diagnostics.push(lsp::Diagnostic {
+ range,
+ severity: Some(lsp::DiagnosticSeverity::Error),
+ code: Some(err.as_code()),
+ code_description: None,
+ source: Some("deno".to_string()),
+ message: err.to_string(),
+ related_information: None,
+ tags: None,
+ data: None,
+ }),
+ analysis::ResolvedDependency::Resolved(specifier) => {
+ if !(documents.contains_key(&specifier) || sources.contains_key(&specifier)) {
+ let (code, message) = match specifier.scheme() {
+ "file" => (Some(lsp::NumberOrString::String("no-local".to_string())), format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier)),
+ "data" => (Some(lsp::NumberOrString::String("no-cache-data".to_string())), "Uncached data URL.".to_string()),
+ "blob" => (Some(lsp::NumberOrString::String("no-cache-blob".to_string())), "Uncached blob URL.".to_string()),
+ _ => (Some(lsp::NumberOrString::String("no-cache".to_string())), format!("Uncached or missing remote URL: \"{}\".", specifier)),
};
- diagnostic_list.push(lsp::Diagnostic {
- range: *range,
+ diagnostics.push(lsp::Diagnostic {
+ range,
severity: Some(lsp::DiagnosticSeverity::Error),
code,
code_description: None,
@@ -606,22 +437,132 @@ async fn generate_dependency_diagnostics(
message,
related_information: None,
tags: None,
- data: Some(json!({
- "specifier": specifier
- })),
- })
+ data: None,
+ });
}
},
}
}
}
}
- diagnostics.push((specifier.clone(), version, diagnostic_list))
+ diagnostics_vec.push((specifier.clone(), version, diagnostics));
}
}
- Ok(diagnostics)
+ Ok(diagnostics_vec)
})
.await
.unwrap()
}
+
+/// Publishes diagnostics to the client.
+async fn publish_diagnostics(
+ client: &lspower::Client,
+ collection: Arc<Mutex<DiagnosticCollection>>,
+ snapshot: &language_server::StateSnapshot,
+) {
+ let mut collection = collection.lock().await;
+ if let Some(changes) = collection.take_changes() {
+ for specifier in changes {
+ let mut diagnostics: Vec<lsp::Diagnostic> =
+ if snapshot.config.workspace_settings.lint {
+ collection
+ .get(&specifier, DiagnosticSource::DenoLint)
+ .cloned()
+ .collect()
+ } else {
+ Vec::new()
+ };
+ if snapshot.config.specifier_enabled(&specifier) {
+ diagnostics.extend(
+ collection
+ .get(&specifier, DiagnosticSource::TypeScript)
+ .cloned(),
+ );
+ diagnostics
+ .extend(collection.get(&specifier, DiagnosticSource::Deno).cloned());
+ }
+ let uri = specifier.clone();
+ let version = snapshot.documents.version(&specifier);
+ client.publish_diagnostics(uri, diagnostics, version).await;
+ }
+ }
+}
+
+/// Updates diagnostics for any specifiers that don't have the correct version
+/// generated and publishes the diagnostics to the client.
+async fn update_diagnostics(
+ client: &lspower::Client,
+ collection: Arc<Mutex<DiagnosticCollection>>,
+ snapshot: &language_server::StateSnapshot,
+ ts_server: &tsc::TsServer,
+) {
+ let mark = snapshot.performance.mark("update_diagnostics", None::<()>);
+
+ let lint = async {
+ let mark = snapshot
+ .performance
+ .mark("update_diagnostics_lint", None::<()>);
+ let collection = collection.clone();
+ let diagnostics = generate_lint_diagnostics(snapshot, collection.clone())
+ .await
+ .map_err(|err| {
+ error!("Error generating lint diagnostics: {}", err);
+ })
+ .unwrap_or_default();
+ {
+ let mut collection = collection.lock().await;
+ for diagnostic_record in diagnostics {
+ collection.set(DiagnosticSource::DenoLint, diagnostic_record);
+ }
+ }
+ publish_diagnostics(client, collection, snapshot).await;
+ snapshot.performance.measure(mark);
+ };
+
+ let ts = async {
+ let mark = snapshot
+ .performance
+ .mark("update_diagnostics_ts", None::<()>);
+ let collection = collection.clone();
+ let diagnostics =
+ generate_ts_diagnostics(snapshot, collection.clone(), ts_server)
+ .await
+ .map_err(|err| {
+ error!("Error generating TypeScript diagnostics: {}", err);
+ })
+ .unwrap_or_default();
+ {
+ let mut collection = collection.lock().await;
+ for diagnostic_record in diagnostics {
+ collection.set(DiagnosticSource::TypeScript, diagnostic_record);
+ }
+ }
+ publish_diagnostics(client, collection, snapshot).await;
+ snapshot.performance.measure(mark);
+ };
+
+ let deps = async {
+ let mark = snapshot
+ .performance
+ .mark("update_diagnostics_deps", None::<()>);
+ let collection = collection.clone();
+ let diagnostics = generate_deps_diagnostics(snapshot, collection.clone())
+ .await
+ .map_err(|err| {
+ error!("Error generating Deno diagnostics: {}", err);
+ })
+ .unwrap_or_default();
+ {
+ let mut collection = collection.lock().await;
+ for diagnostic_record in diagnostics {
+ collection.set(DiagnosticSource::Deno, diagnostic_record);
+ }
+ }
+ publish_diagnostics(client, collection, snapshot).await;
+ snapshot.performance.measure(mark);
+ };
+
+ tokio::join!(lint, ts, deps);
+ snapshot.performance.measure(mark);
+}
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index b05499b7a..5ed6f693c 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -91,7 +91,7 @@ pub(crate) struct Inner {
/// are part of the TypeScript snapshot and have to be fetched out.
assets: Assets,
/// The LSP client that this LSP server is connected to.
- client: Client,
+ pub(crate) client: Client,
/// Configuration information.
config: Config,
diagnostics_server: diagnostics::DiagnosticsServer,
@@ -117,9 +117,9 @@ pub(crate) struct Inner {
/// A memoized version of fixable diagnostic codes retrieved from TypeScript.
ts_fixable_diagnostics: Vec<String>,
/// An abstraction that handles interactions with TypeScript.
- ts_server: Arc<TsServer>,
+ pub(crate) ts_server: Arc<TsServer>,
/// A map of specifiers and URLs used to translate over the LSP.
- pub url_map: urls::LspUrlMap,
+ pub(crate) url_map: urls::LspUrlMap,
}
impl LanguageServer {
@@ -959,12 +959,8 @@ impl Inner {
let mut code_actions = CodeActionCollection::default();
let file_diagnostics = self
.diagnostics_server
- .get(specifier.clone(), DiagnosticSource::TypeScript)
- .await
- .map_err(|err| {
- error!("Unable to get diagnostics: {}", err);
- LspError::internal_error()
- })?;
+ .get(&specifier, DiagnosticSource::TypeScript)
+ .await;
for diagnostic in &fixable_diagnostics {
match diagnostic.source.as_deref() {
Some("deno-ts") => {
@@ -2484,13 +2480,7 @@ impl Inner {
if let Some(source) = self.documents.content(&referrer).unwrap() {
self.analyze_dependencies(&referrer, &source);
}
- self
- .diagnostics_server
- .invalidate(referrer)
- .map_err(|err| {
- error!("{}", err);
- LspError::internal_error()
- })?;
+ self.diagnostics_server.invalidate(&referrer).await;
}
self.diagnostics_server.update().map_err(|err| {
@@ -4608,7 +4598,7 @@ mod tests {
LspFixture::Path("did_open_notification_code_action.json"),
LspResponse::None,
),
- (LspFixture::None, LspResponse::Delay(500)),
+ (LspFixture::None, LspResponse::Delay(20000)),
(
LspFixture::Path("code_action_request.json"),
LspResponse::RequestFixture(2, "code_action_response.json".to_string()),