diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2023-01-27 17:36:23 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-27 22:36:23 +0000 |
commit | 2b247be517d789a37e532849e2e40b724af0918f (patch) | |
tree | 1f689a0b872a5b05a4ce812f9f120df7134ba69d | |
parent | f5840bdcd360ec0bac2501f333e58e25742b1537 (diff) |
fix: ensure "fs" -> "node:fs" error/quick fix works when user has import map (#17566)
Closes #17563
-rw-r--r-- | cli/graph_util.rs | 87 | ||||
-rw-r--r-- | cli/lsp/diagnostics.rs | 71 | ||||
-rw-r--r-- | cli/tests/integration/lsp_tests.rs | 4 |
3 files changed, 117 insertions, 45 deletions
diff --git a/cli/graph_util.rs b/cli/graph_util.rs index c216ba503..3c5545a50 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -31,6 +31,7 @@ use deno_graph::ResolutionError; use deno_graph::Resolved; use deno_graph::SpecifierError; use deno_runtime::permissions::PermissionsContainer; +use import_map::ImportMapError; use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; @@ -646,17 +647,93 @@ fn handle_check_error( pub fn enhanced_resolution_error_message(error: &ResolutionError) -> String { let mut message = format!("{error}"); + if let Some(specifier) = get_resolution_error_bare_node_specifier(error) { + message.push_str(&format!( + "\nIf you want to use a built-in Node module, add a \"node:\" prefix (ex. \"node:{specifier}\")." + )); + } + + message +} + +pub fn get_resolution_error_bare_node_specifier( + error: &ResolutionError, +) -> Option<&str> { + get_resolution_error_bare_specifier(error).filter(|specifier| { + crate::node::resolve_builtin_node_module(specifier).is_ok() + }) +} + +fn get_resolution_error_bare_specifier( + error: &ResolutionError, +) -> Option<&str> { if let ResolutionError::InvalidSpecifier { error: SpecifierError::ImportPrefixMissing(specifier, _), .. } = error { - if crate::node::resolve_builtin_node_module(specifier).is_ok() { - message.push_str(&format!( - "\nIf you want to use a built-in Node module, add a \"node:\" prefix (ex. \"node:{specifier}\")." - )); + Some(specifier.as_str()) + } else if let ResolutionError::ResolverError { error, .. } = error { + if let Some(ImportMapError::UnmappedBareSpecifier(specifier, _)) = + error.downcast_ref::<ImportMapError>() + { + Some(specifier.as_str()) + } else { + None } + } else { + None } +} - message +#[cfg(test)] +mod test { + use std::sync::Arc; + + use deno_ast::ModuleSpecifier; + use deno_graph::Position; + use deno_graph::Range; + use deno_graph::ResolutionError; + use deno_graph::SpecifierError; + + use crate::graph_util::get_resolution_error_bare_node_specifier; + + #[test] + fn import_map_node_resolution_error() { + let cases = vec![("fs", Some("fs")), ("other", None)]; + for (input, output) in cases { + let import_map = import_map::ImportMap::new( + ModuleSpecifier::parse("file:///deno.json").unwrap(), + ); + let specifier = ModuleSpecifier::parse("file:///file.ts").unwrap(); + let err = import_map.resolve(input, &specifier).err().unwrap(); + let err = ResolutionError::ResolverError { + error: Arc::new(err.into()), + specifier: input.to_string(), + range: Range { + specifier, + start: Position::zeroed(), + end: Position::zeroed(), + }, + }; + assert_eq!(get_resolution_error_bare_node_specifier(&err), output); + } + } + + #[test] + fn bare_specifier_node_resolution_error() { + let cases = vec![("process", Some("process")), ("other", None)]; + for (input, output) in cases { + let specifier = ModuleSpecifier::parse("file:///file.ts").unwrap(); + let err = ResolutionError::InvalidSpecifier { + range: Range { + specifier, + start: Position::zeroed(), + end: Position::zeroed(), + }, + error: SpecifierError::ImportPrefixMissing(input.to_string(), None), + }; + assert_eq!(get_resolution_error_bare_node_specifier(&err), output,); + } + } } diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index b6dff8682..0a6f33126 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -13,6 +13,7 @@ use super::tsc; use super::tsc::TsServer; use crate::args::LintOptions; +use crate::graph_util; use crate::graph_util::enhanced_resolution_error_message; use crate::node; use crate::npm::NpmPackageReference; @@ -641,15 +642,25 @@ impl DenoDiagnostic { Self::NoCacheNpm(_, _) => "no-cache-npm", Self::NoLocal(_) => "no-local", Self::Redirect { .. } => "redirect", - Self::ResolutionError(err) => match err { - ResolutionError::InvalidDowngrade { .. } => "invalid-downgrade", - ResolutionError::InvalidLocalImport { .. } => "invalid-local-import", - ResolutionError::InvalidSpecifier { error, .. } => match error { - SpecifierError::ImportPrefixMissing(_, _) => "import-prefix-missing", - SpecifierError::InvalidUrl(_) => "invalid-url", - }, - ResolutionError::ResolverError { .. } => "resolver-error", - }, + Self::ResolutionError(err) => { + if graph_util::get_resolution_error_bare_node_specifier(err).is_some() { + "import-node-prefix-missing" + } else { + match err { + ResolutionError::InvalidDowngrade { .. } => "invalid-downgrade", + ResolutionError::InvalidLocalImport { .. } => { + "invalid-local-import" + } + ResolutionError::InvalidSpecifier { error, .. } => match error { + SpecifierError::ImportPrefixMissing(_, _) => { + "import-prefix-missing" + } + SpecifierError::InvalidUrl(_) => "invalid-url", + }, + ResolutionError::ResolverError { .. } => "resolver-error", + } + } + } Self::InvalidNodeSpecifier(_) => "resolver-error", } } @@ -752,10 +763,7 @@ impl DenoDiagnostic { ..Default::default() } } - "import-prefix-missing" => { - // if an import-prefix-missing diagnostic ends up here, then that currently - // will only ever occur for a possible "node:" specifier, so don't bother - // checking if it's actually a "node:"" specifier + "import-node-prefix-missing" => { let data = diagnostic .data .clone() @@ -795,19 +803,16 @@ impl DenoDiagnostic { /// diagnostic is fixable or not pub fn is_fixable(diagnostic: &lsp_types::Diagnostic) -> bool { if let Some(lsp::NumberOrString::String(code)) = &diagnostic.code { - if code == "import-prefix-missing" { - diagnostic.data.is_some() - } else { - matches!( - code.as_str(), - "import-map-remap" - | "no-cache" - | "no-cache-npm" - | "no-cache-data" - | "no-assert-type" - | "redirect" - ) - } + matches!( + code.as_str(), + "import-map-remap" + | "no-cache" + | "no-cache-npm" + | "no-cache-data" + | "no-assert-type" + | "redirect" + | "import-node-prefix-missing" + ) } else { false } @@ -830,18 +835,8 @@ impl DenoDiagnostic { Self::ResolutionError(err) => ( lsp::DiagnosticSeverity::ERROR, enhanced_resolution_error_message(err), - if let ResolutionError::InvalidSpecifier { - error: SpecifierError::ImportPrefixMissing(specifier, _), - .. - } = err { - if crate::node::resolve_builtin_node_module(specifier).is_ok() { - Some(json!({ "specifier": specifier })) - } else { - None - } - } else { - None - }, + graph_util::get_resolution_error_bare_node_specifier(err) + .map(|specifier| json!({ "specifier": specifier })) ), Self::InvalidNodeSpecifier(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Unknown Node built-in module: {}", specifier.path()), None), }; diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 1fd619a40..d149e6919 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -4563,7 +4563,7 @@ fn lsp_completions_node_specifier() { .filter(|d| { d.code == Some(lsp::NumberOrString::String( - "import-prefix-missing".to_string(), + "import-node-prefix-missing".to_string(), )) }) .collect::<Vec<_>>(); @@ -4602,7 +4602,7 @@ fn lsp_completions_node_specifier() { "end": { "line": 0, "character": 19 } }, "severity": 1, - "code": "import-prefix-missing", + "code": "import-node-prefix-missing", "source": "deno", "message": "Relative import path \"fs\" not prefixed with / or ./ or ../\nIf you want to use a built-in Node module, add a \"node:\" prefix (ex. \"node:fs\").", "data": { |