summaryrefslogtreecommitdiff
path: root/cli/lsp/analysis.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/lsp/analysis.rs')
-rw-r--r--cli/lsp/analysis.rs159
1 files changed, 159 insertions, 0 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index 80db66d64..d189ab253 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -2,6 +2,7 @@
use super::diagnostics::DenoDiagnostic;
use super::diagnostics::DiagnosticSource;
+use super::documents::Document;
use super::documents::Documents;
use super::language_server;
use super::resolver::LspResolver;
@@ -9,7 +10,9 @@ use super::tsc;
use super::urls::url_to_uri;
use crate::args::jsr_url;
+use crate::lsp::search::PackageSearchApi;
use crate::tools::lint::CliLinter;
+use deno_config::workspace::MappedResolution;
use deno_lint::diagnostic::LintDiagnosticRange;
use deno_ast::SourceRange;
@@ -1151,6 +1154,162 @@ impl CodeActionCollection {
..Default::default()
}));
}
+
+ pub async fn add_source_actions(
+ &mut self,
+ document: &Document,
+ range: &lsp::Range,
+ language_server: &language_server::Inner,
+ ) {
+ async fn deno_types_for_npm_action(
+ document: &Document,
+ range: &lsp::Range,
+ language_server: &language_server::Inner,
+ ) -> Option<lsp::CodeAction> {
+ let (dep_key, dependency, _) =
+ document.get_maybe_dependency(&range.end)?;
+ if dependency.maybe_deno_types_specifier.is_some() {
+ return None;
+ }
+ if dependency.maybe_code.maybe_specifier().is_none()
+ && dependency.maybe_type.maybe_specifier().is_none()
+ {
+ // We're using byonm and the package is not cached.
+ return None;
+ }
+ let position = deno_graph::Position::new(
+ range.end.line as usize,
+ range.end.character as usize,
+ );
+ let import_range = dependency.imports.iter().find_map(|i| {
+ if json!(i.kind) != json!("es") && json!(i.kind) != json!("tsType") {
+ return None;
+ }
+ if !i.specifier_range.includes(&position) {
+ return None;
+ }
+ i.full_range.as_ref()
+ })?;
+ let referrer = document.specifier();
+ let file_referrer = document.file_referrer();
+ let config_data = language_server
+ .config
+ .tree
+ .data_for_specifier(file_referrer?)?;
+ let workspace_resolver = config_data.resolver.clone();
+ let npm_ref = if let Ok(resolution) =
+ workspace_resolver.resolve(&dep_key, document.specifier())
+ {
+ let specifier = match resolution {
+ MappedResolution::Normal { specifier, .. }
+ | MappedResolution::ImportMap { specifier, .. } => specifier,
+ _ => {
+ return None;
+ }
+ };
+ NpmPackageReqReference::from_specifier(&specifier).ok()?
+ } else {
+ // Only resolve bare package.json deps for byonm.
+ if !config_data.byonm {
+ return None;
+ }
+ if !language_server
+ .resolver
+ .is_bare_package_json_dep(&dep_key, referrer)
+ {
+ return None;
+ }
+ NpmPackageReqReference::from_str(&format!("npm:{}", &dep_key)).ok()?
+ };
+ let package_name = &npm_ref.req().name;
+ if package_name.starts_with("@types/") {
+ return None;
+ }
+ let managed_npm_resolver = language_server
+ .resolver
+ .maybe_managed_npm_resolver(file_referrer);
+ if let Some(npm_resolver) = managed_npm_resolver {
+ if !npm_resolver.is_pkg_req_folder_cached(npm_ref.req()) {
+ return None;
+ }
+ }
+ if language_server
+ .resolver
+ .npm_to_file_url(&npm_ref, document.specifier(), file_referrer)
+ .is_some()
+ {
+ // The package import has types.
+ return None;
+ }
+ let types_package_name = format!("@types/{package_name}");
+ let types_package_version = language_server
+ .npm_search_api
+ .versions(&types_package_name)
+ .await
+ .ok()
+ .and_then(|versions| versions.first().cloned())?;
+ let types_specifier_text =
+ if let Some(npm_resolver) = managed_npm_resolver {
+ let mut specifier_text = if let Some(req) =
+ npm_resolver.top_package_req_for_name(&types_package_name)
+ {
+ format!("npm:{req}")
+ } else {
+ format!("npm:{}@^{}", &types_package_name, types_package_version)
+ };
+ let specifier = ModuleSpecifier::parse(&specifier_text).ok()?;
+ if let Some(file_referrer) = file_referrer {
+ if let Some(text) = language_server
+ .get_ts_response_import_mapper(file_referrer)
+ .check_specifier(&specifier, referrer)
+ {
+ specifier_text = text;
+ }
+ }
+ specifier_text
+ } else {
+ types_package_name.clone()
+ };
+ let uri = language_server
+ .url_map
+ .specifier_to_uri(referrer, file_referrer)
+ .ok()?;
+ let position = lsp::Position {
+ line: import_range.start.line as u32,
+ character: import_range.start.character as u32,
+ };
+ let new_text = format!(
+ "{}// @deno-types=\"{}\"\n",
+ if position.character == 0 { "" } else { "\n" },
+ &types_specifier_text
+ );
+ let text_edit = lsp::TextEdit {
+ range: lsp::Range {
+ start: position,
+ end: position,
+ },
+ new_text,
+ };
+ Some(lsp::CodeAction {
+ title: format!(
+ "Add @deno-types directive for \"{}\"",
+ &types_specifier_text
+ ),
+ kind: Some(lsp::CodeActionKind::QUICKFIX),
+ diagnostics: None,
+ edit: Some(lsp::WorkspaceEdit {
+ changes: Some([(uri, vec![text_edit])].into_iter().collect()),
+ ..Default::default()
+ }),
+ ..Default::default()
+ })
+ }
+ if let Some(action) =
+ deno_types_for_npm_action(document, range, language_server).await
+ {
+ self.actions.push(CodeActionKind::Deno(action));
+ }
+ }
}
/// Prepend the whitespace characters found at the start of line_content to content.