summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/analysis.rs18
-rw-r--r--cli/lsp/diagnostics.rs112
-rw-r--r--cli/lsp/language_server.rs60
-rw-r--r--cli/tests/integration/lsp_tests.rs145
4 files changed, 267 insertions, 68 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index c1def6012..0d987163b 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -917,6 +917,24 @@ impl CodeActionCollection {
}
}
}
+
+ pub fn add_cache_all_action(
+ &mut self,
+ specifier: &ModuleSpecifier,
+ diagnostics: Vec<lsp::Diagnostic>,
+ ) {
+ self.actions.push(CodeActionKind::Deno(lsp::CodeAction {
+ title: "Cache all dependencies of this module.".to_string(),
+ kind: Some(lsp::CodeActionKind::QUICKFIX),
+ diagnostics: Some(diagnostics),
+ command: Some(lsp::Command {
+ title: "".to_string(),
+ command: "deno.cache".to_string(),
+ arguments: Some(vec![json!([]), json!(&specifier)]),
+ }),
+ ..Default::default()
+ }));
+ }
}
/// Prepend the whitespace characters found at the start of line_content to content.
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index f5a0af455..2ab9656a4 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -24,6 +24,7 @@ use crate::tools::lint::get_configured_rules;
use deno_ast::MediaType;
use deno_core::anyhow::anyhow;
use deno_core::error::AnyError;
+use deno_core::parking_lot::RwLock;
use deno_core::resolve_url;
use deno_core::serde::Deserialize;
use deno_core::serde_json;
@@ -48,7 +49,6 @@ 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;
@@ -96,13 +96,13 @@ type DiagnosticsBySource = HashMap<DiagnosticSource, VersionedDiagnostics>;
#[derive(Debug)]
struct DiagnosticsPublisher {
client: Client,
- state: Arc<RwLock<DiagnosticsState>>,
+ state: Arc<DiagnosticsState>,
diagnostics_by_specifier:
Mutex<HashMap<ModuleSpecifier, DiagnosticsBySource>>,
}
impl DiagnosticsPublisher {
- pub fn new(client: Client, state: Arc<RwLock<DiagnosticsState>>) -> Self {
+ pub fn new(client: Client, state: Arc<DiagnosticsState>) -> Self {
Self {
client,
state,
@@ -119,7 +119,6 @@ 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;
@@ -146,7 +145,9 @@ impl DiagnosticsPublisher {
.cloned()
.collect::<Vec<_>>();
- state.update(&record.specifier, version, &all_specifier_diagnostics);
+ self
+ .state
+ .update(&record.specifier, version, &all_specifier_diagnostics);
self
.client
.when_outside_lsp_lock()
@@ -177,7 +178,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.state.update(specifier, removed_value.version, &[]);
self
.client
.when_outside_lsp_lock()
@@ -296,57 +297,72 @@ struct ChannelUpdateMessage {
batch_index: Option<usize>,
}
-#[derive(Clone, Debug)]
+#[derive(Debug)]
struct SpecifierState {
- has_no_cache_diagnostic: bool,
+ version: Option<i32>,
+ no_cache_diagnostics: Vec<lsp::Diagnostic>,
}
-#[derive(Clone, Debug, Default)]
+#[derive(Debug, Default)]
pub struct DiagnosticsState {
- specifiers: HashMap<ModuleSpecifier, (Option<i32>, SpecifierState)>,
+ specifiers: RwLock<HashMap<ModuleSpecifier, SpecifierState>>,
}
impl DiagnosticsState {
fn update(
- &mut self,
+ &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(),
- ))
- }),
- },
- ),
- );
+ let mut specifiers = self.specifiers.write();
+ let current_version = specifiers.get(specifier).and_then(|s| s.version);
+ match (version, current_version) {
+ (Some(arg), Some(existing)) if arg < existing => return,
+ _ => {}
}
+ let mut no_cache_diagnostics = vec![];
+ for diagnostic in diagnostics {
+ if diagnostic.code
+ == Some(lsp::NumberOrString::String("no-cache".to_string()))
+ || diagnostic.code
+ == Some(lsp::NumberOrString::String("no-cache-npm".to_string()))
+ {
+ no_cache_diagnostics.push(diagnostic.clone());
+ }
+ }
+ specifiers.insert(
+ specifier.clone(),
+ SpecifierState {
+ version,
+ no_cache_diagnostics,
+ },
+ );
}
- pub fn clear(&mut self, specifier: &ModuleSpecifier) {
- self.specifiers.remove(specifier);
+ pub fn clear(&self, specifier: &ModuleSpecifier) {
+ self.specifiers.write().remove(specifier);
}
- pub fn has_no_cache_diagnostic(&self, specifier: &ModuleSpecifier) -> bool {
+ pub fn has_no_cache_diagnostics(&self, specifier: &ModuleSpecifier) -> bool {
self
.specifiers
+ .read()
.get(specifier)
- .map_or(false, |s| s.1.has_no_cache_diagnostic)
+ .map(|s| !s.no_cache_diagnostics.is_empty())
+ .unwrap_or(false)
+ }
+
+ pub fn no_cache_diagnostics(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Vec<lsp::Diagnostic> {
+ self
+ .specifiers
+ .read()
+ .get(specifier)
+ .map(|s| s.no_cache_diagnostics.clone())
+ .unwrap_or_default()
}
}
@@ -358,7 +374,7 @@ pub struct DiagnosticsServer {
performance: Arc<Performance>,
ts_server: Arc<TsServer>,
batch_counter: DiagnosticBatchCounter,
- state: Arc<RwLock<DiagnosticsState>>,
+ state: Arc<DiagnosticsState>,
}
impl DiagnosticsServer {
@@ -366,6 +382,7 @@ impl DiagnosticsServer {
client: Client,
performance: Arc<Performance>,
ts_server: Arc<TsServer>,
+ state: Arc<DiagnosticsState>,
) -> Self {
DiagnosticsServer {
channel: Default::default(),
@@ -374,14 +391,10 @@ impl DiagnosticsServer {
performance,
ts_server,
batch_counter: Default::default(),
- state: Default::default(),
+ state,
}
}
- pub fn state(&self) -> Arc<RwLock<DiagnosticsState>> {
- self.state.clone()
- }
-
pub fn get_ts_diagnostics(
&self,
specifier: &ModuleSpecifier,
@@ -906,7 +919,7 @@ async fn generate_ts_diagnostics(
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
-struct DiagnosticDataSpecifier {
+pub struct DiagnosticDataSpecifier {
pub specifier: ModuleSpecifier,
}
@@ -1050,14 +1063,11 @@ impl DenoDiagnostic {
.clone()
.ok_or_else(|| anyhow!("Diagnostic is missing data"))?;
let data: DiagnosticDataSpecifier = serde_json::from_value(data)?;
- let title = match code.as_str() {
- "no-cache" | "no-cache-npm" => {
- format!("Cache \"{}\" and its dependencies.", data.specifier)
- }
- _ => "Cache the data URL and its dependencies.".to_string(),
- };
lsp::CodeAction {
- title,
+ title: format!(
+ "Cache \"{}\" and its dependencies.",
+ data.specifier
+ ),
kind: Some(lsp::CodeActionKind::QUICKFIX),
diagnostics: Some(vec![diagnostic.clone()]),
command: Some(lsp::Command {
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 2a17b2e59..c415cf45b 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -48,8 +48,10 @@ use super::config::ConfigSnapshot;
use super::config::WorkspaceSettings;
use super::config::SETTINGS_SECTION;
use super::diagnostics;
+use super::diagnostics::DiagnosticDataSpecifier;
use super::diagnostics::DiagnosticServerUpdateMessage;
use super::diagnostics::DiagnosticsServer;
+use super::diagnostics::DiagnosticsState;
use super::documents::to_hover_text;
use super::documents::to_lsp_range;
use super::documents::AssetOrDocument;
@@ -180,6 +182,7 @@ pub struct Inner {
/// Configuration information.
pub config: Config,
deps_http_cache: Arc<dyn HttpCache>,
+ diagnostics_state: Arc<diagnostics::DiagnosticsState>,
diagnostics_server: diagnostics::DiagnosticsServer,
/// The collection of documents that the server is currently handling, either
/// on disk or "open" within the client.
@@ -557,10 +560,12 @@ impl Inner {
let ts_server =
Arc::new(TsServer::new(performance.clone(), deps_http_cache.clone()));
let config = Config::new();
+ let diagnostics_state = Arc::new(DiagnosticsState::default());
let diagnostics_server = DiagnosticsServer::new(
client.clone(),
performance.clone(),
ts_server.clone(),
+ diagnostics_state.clone(),
);
let assets = Assets::new(ts_server.clone());
let registry_url = CliNpmRegistryApi::default_url();
@@ -587,6 +592,7 @@ impl Inner {
client,
config,
deps_http_cache,
+ diagnostics_state,
diagnostics_server,
documents,
http_client,
@@ -1442,12 +1448,7 @@ 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);
+ self.diagnostics_state.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
@@ -1914,6 +1915,7 @@ impl Inner {
let file_diagnostics = self
.diagnostics_server
.get_ts_diagnostics(&specifier, asset_or_doc.document_lsp_version());
+ let mut includes_no_cache = false;
for diagnostic in &fixable_diagnostics {
match diagnostic.source.as_deref() {
Some("deno-ts") => {
@@ -1957,12 +1959,21 @@ impl Inner {
}
}
}
- Some("deno") => code_actions
- .add_deno_fix_action(&specifier, diagnostic)
- .map_err(|err| {
- error!("{}", err);
- LspError::internal_error()
- })?,
+ Some("deno") => {
+ if diagnostic.code
+ == Some(NumberOrString::String("no-cache".to_string()))
+ || diagnostic.code
+ == Some(NumberOrString::String("no-cache-npm".to_string()))
+ {
+ includes_no_cache = true;
+ }
+ code_actions
+ .add_deno_fix_action(&specifier, diagnostic)
+ .map_err(|err| {
+ error!("{}", err);
+ LspError::internal_error()
+ })?
+ }
Some("deno-lint") => code_actions
.add_deno_lint_ignore_action(
&specifier,
@@ -1977,6 +1988,24 @@ impl Inner {
_ => (),
}
}
+ if includes_no_cache {
+ let no_cache_diagnostics =
+ self.diagnostics_state.no_cache_diagnostics(&specifier);
+ let uncached_deps = no_cache_diagnostics
+ .iter()
+ .filter_map(|d| {
+ let data = serde_json::from_value::<DiagnosticDataSpecifier>(
+ d.data.clone().into(),
+ )
+ .ok()?;
+ Some(data.specifier)
+ })
+ .collect::<HashSet<_>>();
+ if uncached_deps.len() > 1 {
+ code_actions
+ .add_cache_all_action(&specifier, no_cache_diagnostics.to_owned());
+ }
+ }
code_actions.set_preferred_fixes();
all_actions.extend(code_actions.get_response());
}
@@ -3277,17 +3306,14 @@ impl tower_lsp::LanguageServer for LanguageServer {
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)
+ || !inner.diagnostics_state.has_no_cache_diagnostics(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(
diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs
index 9e9b3f8cd..336304db7 100644
--- a/cli/tests/integration/lsp_tests.rs
+++ b/cli/tests/integration/lsp_tests.rs
@@ -4504,6 +4504,151 @@ fn lsp_code_actions_deno_cache_npm() {
}
#[test]
+fn lsp_code_actions_deno_cache_all() {
+ let context = TestContextBuilder::new().use_temp_cwd().build();
+ let mut client = context.new_lsp_command().build();
+ client.initialize_default();
+ let diagnostics = client.did_open(json!({
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ "languageId": "typescript",
+ "version": 1,
+ "text": r#"
+ import * as a from "https://deno.land/x/a/mod.ts";
+ import chalk from "npm:chalk";
+ console.log(a);
+ console.log(chalk);
+ "#,
+ }
+ }));
+ assert_eq!(
+ diagnostics.messages_with_source("deno"),
+ serde_json::from_value(json!({
+ "uri": "file:///a/file.ts",
+ "diagnostics": [
+ {
+ "range": {
+ "start": { "line": 1, "character": 27 },
+ "end": { "line": 1, "character": 57 },
+ },
+ "severity": 1,
+ "code": "no-cache",
+ "source": "deno",
+ "message": "Uncached or missing remote URL: https://deno.land/x/a/mod.ts",
+ "data": { "specifier": "https://deno.land/x/a/mod.ts" },
+ },
+ {
+ "range": {
+ "start": { "line": 2, "character": 26 },
+ "end": { "line": 2, "character": 37 },
+ },
+ "severity": 1,
+ "code": "no-cache-npm",
+ "source": "deno",
+ "message": "Uncached or missing npm package: chalk",
+ "data": { "specifier": "npm:chalk" },
+ },
+ ],
+ "version": 1,
+ })).unwrap()
+ );
+
+ let res =
+ client
+ .write_request( "textDocument/codeAction",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ },
+ "range": {
+ "start": { "line": 1, "character": 27 },
+ "end": { "line": 1, "character": 57 },
+ },
+ "context": {
+ "diagnostics": [{
+ "range": {
+ "start": { "line": 1, "character": 27 },
+ "end": { "line": 1, "character": 57 },
+ },
+ "severity": 1,
+ "code": "no-cache",
+ "source": "deno",
+ "message": "Uncached or missing remote URL: https://deno.land/x/a/mod.ts",
+ "data": {
+ "specifier": "https://deno.land/x/a/mod.ts",
+ },
+ }],
+ "only": ["quickfix"],
+ }
+ }),
+ )
+ ;
+ assert_eq!(
+ res,
+ json!([
+ {
+ "title": "Cache \"https://deno.land/x/a/mod.ts\" and its dependencies.",
+ "kind": "quickfix",
+ "diagnostics": [{
+ "range": {
+ "start": { "line": 1, "character": 27 },
+ "end": { "line": 1, "character": 57 },
+ },
+ "severity": 1,
+ "code": "no-cache",
+ "source": "deno",
+ "message": "Uncached or missing remote URL: https://deno.land/x/a/mod.ts",
+ "data": {
+ "specifier": "https://deno.land/x/a/mod.ts",
+ },
+ }],
+ "command": {
+ "title": "",
+ "command": "deno.cache",
+ "arguments": [["https://deno.land/x/a/mod.ts"], "file:///a/file.ts"],
+ }
+ },
+ {
+ "title": "Cache all dependencies of this module.",
+ "kind": "quickfix",
+ "diagnostics": [
+ {
+ "range": {
+ "start": { "line": 1, "character": 27 },
+ "end": { "line": 1, "character": 57 },
+ },
+ "severity": 1,
+ "code": "no-cache",
+ "source": "deno",
+ "message": "Uncached or missing remote URL: https://deno.land/x/a/mod.ts",
+ "data": {
+ "specifier": "https://deno.land/x/a/mod.ts",
+ },
+ },
+ {
+ "range": {
+ "start": { "line": 2, "character": 26 },
+ "end": { "line": 2, "character": 37 },
+ },
+ "severity": 1,
+ "code": "no-cache-npm",
+ "source": "deno",
+ "message": "Uncached or missing npm package: chalk",
+ "data": { "specifier": "npm:chalk" },
+ },
+ ],
+ "command": {
+ "title": "",
+ "command": "deno.cache",
+ "arguments": [[], "file:///a/file.ts"],
+ }
+ },
+ ])
+ );
+ client.shutdown();
+}
+
+#[test]
fn lsp_cache_on_save() {
let context = TestContextBuilder::new()
.use_http_server()