diff options
Diffstat (limited to 'cli/lsp/analysis.rs')
-rw-r--r-- | cli/lsp/analysis.rs | 159 |
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. |