diff --git a/cli/factory.rs b/cli/factory.rs index e4b9c1da85..5db09767cf 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -39,7 +39,7 @@ use crate::npm::CliNpmResolverManagedPackageJsonInstallerOption; use crate::npm::CliNpmResolverManagedSnapshotOption; use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; -use crate::resolver::UnstableSloppyImportsResolver; +use crate::resolver::SloppyImportsResolver; use crate::standalone::DenoCompileBinaryWriter; use crate::tools::check::TypeChecker; use crate::util::file_watcher::WatcherCommunicator; @@ -383,7 +383,7 @@ impl CliFactory { fs: self.fs().clone(), cjs_resolutions: Some(self.cjs_resolutions().clone()), sloppy_imports_resolver: if self.options.unstable_sloppy_imports() { - Some(UnstableSloppyImportsResolver::new(self.fs().clone())) + Some(SloppyImportsResolver::new(self.fs().clone())) } else { None }, diff --git a/cli/graph_util.rs b/cli/graph_util.rs index 2e7766b9f0..eba88e4d0d 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -12,12 +12,15 @@ use crate::errors::get_error_class_name; use crate::file_fetcher::FileFetcher; use crate::npm::CliNpmResolver; use crate::resolver::CliGraphResolver; +use crate::resolver::SloppyImportsResolution; +use crate::resolver::SloppyImportsResolver; use crate::tools::check; use crate::tools::check::TypeChecker; use crate::util::file_watcher::WatcherCommunicator; use crate::util::sync::TaskQueue; use crate::util::sync::TaskQueuePermit; +use deno_ast::MediaType; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::custom_error; @@ -58,11 +61,13 @@ pub struct GraphValidOptions { /// error statically reachable from `roots` and not a dynamic import. pub fn graph_valid_with_cli_options( graph: &ModuleGraph, + fs: &Arc, roots: &[ModuleSpecifier], options: &CliOptions, ) -> Result<(), AnyError> { graph_valid( graph, + fs, roots, GraphValidOptions { is_vendoring: false, @@ -81,6 +86,7 @@ pub fn graph_valid_with_cli_options( /// for the CLI. pub fn graph_valid( graph: &ModuleGraph, + fs: &Arc, roots: &[ModuleSpecifier], options: GraphValidOptions, ) -> Result<(), AnyError> { @@ -109,10 +115,12 @@ pub fn graph_valid( ModuleGraphError::TypesResolutionError(resolution_error) => { format!( "Failed resolving types. {}", - enhanced_resolution_error_message(resolution_error,) + enhanced_resolution_error_message(resolution_error) ) } - ModuleGraphError::ModuleError(_) => format!("{error}"), + ModuleGraphError::ModuleError(e) => { + enhanced_module_error_message(fs, e) + } }; if let Some(range) = error.maybe_range() { @@ -356,7 +364,12 @@ impl ModuleGraphBuilder { .await?; let graph = Arc::new(graph); - graph_valid_with_cli_options(&graph, &graph.roots, &self.options)?; + graph_valid_with_cli_options( + &graph, + &self.fs, + &graph.roots, + &self.options, + )?; if let Some(lockfile) = &self.lockfile { graph_lock_or_exit(&graph, &mut lockfile.lock()); } @@ -524,6 +537,68 @@ pub fn enhanced_resolution_error_message(error: &ResolutionError) -> String { message } +pub fn enhanced_module_error_message( + fs: &Arc, + error: &ModuleError, +) -> String { + let additional_message = match error { + ModuleError::Missing(specifier, _) => { + maybe_sloppy_imports_suggestion_message(fs, specifier) + } + _ => None, + }; + if let Some(message) = additional_message { + format!( + "{} {} or run with --unstable-sloppy-imports", + error, message + ) + } else { + format!("{}", error) + } +} + +pub fn maybe_sloppy_imports_suggestion_message( + fs: &Arc, + original_specifier: &ModuleSpecifier, +) -> Option { + let sloppy_imports_resolver = SloppyImportsResolver::new(fs.clone()); + let resolution = sloppy_imports_resolver.resolve(original_specifier); + sloppy_import_resolution_to_suggestion_message(&resolution) +} + +fn sloppy_import_resolution_to_suggestion_message( + resolution: &SloppyImportsResolution, +) -> Option { + match resolution { + SloppyImportsResolution::None(_) => None, + SloppyImportsResolution::JsToTs(specifier) => { + let media_type = MediaType::from_specifier(specifier); + Some(format!( + "Maybe change the extension to '{}'", + media_type.as_ts_extension() + )) + } + SloppyImportsResolution::NoExtension(specifier) => { + let media_type = MediaType::from_specifier(specifier); + Some(format!( + "Maybe add a '{}' extension", + media_type.as_ts_extension() + )) + } + SloppyImportsResolution::Directory(specifier) => { + let file_name = specifier + .path() + .rsplit_once('/') + .map(|(_, file_name)| file_name) + .unwrap_or(specifier.path()); + Some(format!( + "Maybe specify path to '{}' file in directory instead", + file_name + )) + } + } +} + pub fn get_resolution_error_bare_node_specifier( error: &ResolutionError, ) -> Option<&str> { @@ -897,4 +972,46 @@ mod test { assert_eq!(get_resolution_error_bare_node_specifier(&err), output,); } } + + #[test] + fn test_sloppy_import_resolution_to_message() { + // none + let url = ModuleSpecifier::parse("file:///dir/index.js").unwrap(); + assert_eq!( + sloppy_import_resolution_to_suggestion_message( + &SloppyImportsResolution::None(&url) + ), + None, + ); + // directory + assert_eq!( + sloppy_import_resolution_to_suggestion_message( + &SloppyImportsResolution::Directory( + ModuleSpecifier::parse("file:///dir/index.js").unwrap() + ) + ) + .unwrap(), + "Maybe specify path to 'index.js' file in directory instead" + ); + // no ext + assert_eq!( + sloppy_import_resolution_to_suggestion_message( + &SloppyImportsResolution::NoExtension( + ModuleSpecifier::parse("file:///dir/index.mjs").unwrap() + ) + ) + .unwrap(), + "Maybe add a '.mjs' extension" + ); + // js to ts + assert_eq!( + sloppy_import_resolution_to_suggestion_message( + &SloppyImportsResolution::JsToTs( + ModuleSpecifier::parse("file:///dir/index.mts").unwrap() + ) + ) + .unwrap(), + "Maybe change the extension to '.mts'" + ); + } } diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 4e4e9b3bb5..4dbb4e1dd0 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -37,6 +37,7 @@ use deno_graph::Resolution; use deno_graph::ResolutionError; use deno_graph::SpecifierError; use deno_lint::rules::LintRule; +use deno_runtime::deno_fs; use deno_runtime::deno_node; use deno_runtime::tokio_util::create_basic_runtime; use deno_semver::npm::NpmPackageReqReference; @@ -1166,6 +1167,21 @@ impl DenoDiagnostic { /// Convert to an lsp Diagnostic when the range the diagnostic applies to is /// provided. pub fn to_lsp_diagnostic(&self, range: &lsp::Range) -> lsp::Diagnostic { + fn no_local_message(specifier: &ModuleSpecifier) -> String { + let fs: Arc = Arc::new(deno_fs::RealFs); + let mut message = + format!("Unable to load a local module: {}\n", specifier); + if let Some(additional_message) = + graph_util::maybe_sloppy_imports_suggestion_message(&fs, specifier) + { + message.push_str(&additional_message); + message.push('.'); + } else { + message.push_str("Please check the file path."); + } + message + } + let (severity, message, data) = match self { Self::DenoWarn(message) => (lsp::DiagnosticSeverity::WARNING, message.to_string(), None), Self::ImportMapRemap { from, to } => (lsp::DiagnosticSeverity::HINT, format!("The import specifier can be remapped to \"{to}\" which will resolve it via the active import map."), Some(json!({ "from": from, "to": to }))), @@ -1173,7 +1189,7 @@ impl DenoDiagnostic { Self::NoAttributeType => (lsp::DiagnosticSeverity::ERROR, "The module is a JSON module and not being imported with an import attribute. Consider adding `with { type: \"json\" }` to the import statement.".to_string(), None), Self::NoCache(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing remote URL: {specifier}"), Some(json!({ "specifier": specifier }))), Self::NoCacheNpm(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing npm package: {}", pkg_req), Some(json!({ "specifier": specifier }))), - Self::NoLocal(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Unable to load a local module: {specifier}\n Please check the file path."), None), + Self::NoLocal(specifier) => (lsp::DiagnosticSeverity::ERROR, no_local_message(specifier), None), Self::Redirect { from, to} => (lsp::DiagnosticSeverity::INFORMATION, format!("The import of \"{from}\" was redirected to \"{to}\"."), Some(json!({ "specifier": from, "redirect": to }))), Self::ResolutionError(err) => ( lsp::DiagnosticSeverity::ERROR, diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 6687d2208d..a341ae2079 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -20,9 +20,9 @@ use crate::lsp::logging::lsp_warn; use crate::npm::CliNpmResolver; use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; -use crate::resolver::UnstableSloppyImportsFsEntry; -use crate::resolver::UnstableSloppyImportsResolution; -use crate::resolver::UnstableSloppyImportsResolver; +use crate::resolver::SloppyImportsFsEntry; +use crate::resolver::SloppyImportsResolution; +use crate::resolver::SloppyImportsResolver; use crate::util::glob; use crate::util::path::specifier_to_file_path; use crate::util::text_encoding; @@ -1065,20 +1065,20 @@ impl Documents { fn resolve_unstable_sloppy_import<'a>( &self, specifier: &'a ModuleSpecifier, - ) -> UnstableSloppyImportsResolution<'a> { - UnstableSloppyImportsResolver::resolve_with_stat_sync(specifier, |path| { + ) -> SloppyImportsResolution<'a> { + SloppyImportsResolver::resolve_with_stat_sync(specifier, |path| { if let Ok(specifier) = ModuleSpecifier::from_file_path(path) { if self.open_docs.contains_key(&specifier) || self.cache.contains(&specifier) { - return Some(UnstableSloppyImportsFsEntry::File); + return Some(SloppyImportsFsEntry::File); } } path.metadata().ok().and_then(|m| { if m.is_file() { - Some(UnstableSloppyImportsFsEntry::File) + Some(SloppyImportsFsEntry::File) } else if m.is_dir() { - Some(UnstableSloppyImportsFsEntry::Dir) + Some(SloppyImportsFsEntry::Dir) } else { None } @@ -1732,18 +1732,18 @@ impl<'a> OpenDocumentsGraphLoader<'a> { fn resolve_unstable_sloppy_import<'b>( &self, specifier: &'b ModuleSpecifier, - ) -> UnstableSloppyImportsResolution<'b> { - UnstableSloppyImportsResolver::resolve_with_stat_sync(specifier, |path| { + ) -> SloppyImportsResolution<'b> { + SloppyImportsResolver::resolve_with_stat_sync(specifier, |path| { if let Ok(specifier) = ModuleSpecifier::from_file_path(path) { if self.open_docs.contains_key(&specifier) { - return Some(UnstableSloppyImportsFsEntry::File); + return Some(SloppyImportsFsEntry::File); } } path.metadata().ok().and_then(|m| { if m.is_file() { - Some(UnstableSloppyImportsFsEntry::File) + Some(SloppyImportsFsEntry::File) } else if m.is_dir() { - Some(UnstableSloppyImportsFsEntry::Dir) + Some(SloppyImportsFsEntry::Dir) } else { None } diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 92173b8ad2..bc8a102353 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -263,6 +263,7 @@ impl LanguageServer { .await?; graph_util::graph_valid( &graph, + factory.fs(), &roots, graph_util::GraphValidOptions { is_vendoring: false, diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 92116dc7b9..afd2d1999d 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -169,7 +169,7 @@ impl ModuleLoadPreparer { ) .await?; - graph_valid_with_cli_options(graph, &roots, &self.options)?; + graph_valid_with_cli_options(graph, &self.fs, &roots, &self.options)?; // If there is a lockfile... if let Some(lockfile) = &self.lockfile { diff --git a/cli/resolver.rs b/cli/resolver.rs index 12b86632ff..3cf1242172 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -117,7 +117,7 @@ impl MappedSpecifierResolver { #[derive(Debug)] pub struct CliGraphResolver { fs: Arc, - sloppy_imports_resolver: Option, + sloppy_imports_resolver: Option, mapped_specifier_resolver: MappedSpecifierResolver, maybe_default_jsx_import_source: Option, maybe_jsx_import_source_module: Option, @@ -132,7 +132,7 @@ pub struct CliGraphResolver { pub struct CliGraphResolverOptions<'a> { pub fs: Arc, pub cjs_resolutions: Option>, - pub sloppy_imports_resolver: Option, + pub sloppy_imports_resolver: Option, pub node_resolver: Option>, pub npm_resolver: Option>, pub package_json_deps_provider: Arc, @@ -399,13 +399,13 @@ impl Resolver for CliGraphResolver { } fn sloppy_imports_resolve( - resolver: &UnstableSloppyImportsResolver, + resolver: &SloppyImportsResolver, specifier: ModuleSpecifier, referrer_range: &deno_graph::Range, ) -> ModuleSpecifier { let resolution = resolver.resolve(&specifier); let hint_message = match &resolution { - UnstableSloppyImportsResolution::JsToTs(to_specifier) => { + SloppyImportsResolution::JsToTs(to_specifier) => { let from_media_type = MediaType::from_specifier(&specifier); let to_media_type = MediaType::from_specifier(to_specifier); format!( @@ -414,11 +414,11 @@ fn sloppy_imports_resolve( to_media_type.as_ts_extension() ) } - UnstableSloppyImportsResolution::NoExtension(to_specifier) => { + SloppyImportsResolution::NoExtension(to_specifier) => { let to_media_type = MediaType::from_specifier(to_specifier); format!("add {} extension", to_media_type.as_ts_extension()) } - UnstableSloppyImportsResolution::Directory(to_specifier) => { + SloppyImportsResolution::Directory(to_specifier) => { let file_name = to_specifier .path() .rsplit_once('/') @@ -426,7 +426,7 @@ fn sloppy_imports_resolve( .unwrap_or(to_specifier.path()); format!("specify path to {} file in directory instead", file_name) } - UnstableSloppyImportsResolution::None(_) => return specifier, + SloppyImportsResolution::None(_) => return specifier, }; // show a warning when this happens in order to drive // the user towards correcting these specifiers @@ -541,12 +541,12 @@ impl NpmResolver for CliGraphResolver { } #[derive(Debug)] -struct UnstableSloppyImportsStatCache { +struct SloppyImportsStatCache { fs: Arc, - cache: Mutex>>, + cache: Mutex>>, } -impl UnstableSloppyImportsStatCache { +impl SloppyImportsStatCache { pub fn new(fs: Arc) -> Self { Self { fs, @@ -554,7 +554,7 @@ impl UnstableSloppyImportsStatCache { } } - pub fn stat_sync(&self, path: &Path) -> Option { + pub fn stat_sync(&self, path: &Path) -> Option { // there will only ever be one thread in here at a // time, so it's ok to hold the lock for so long let mut cache = self.cache.lock(); @@ -564,9 +564,9 @@ impl UnstableSloppyImportsStatCache { let entry = self.fs.stat_sync(path).ok().and_then(|stat| { if stat.is_file { - Some(UnstableSloppyImportsFsEntry::File) + Some(SloppyImportsFsEntry::File) } else if stat.is_directory { - Some(UnstableSloppyImportsFsEntry::Dir) + Some(SloppyImportsFsEntry::Dir) } else { None } @@ -577,13 +577,13 @@ impl UnstableSloppyImportsStatCache { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum UnstableSloppyImportsFsEntry { +pub enum SloppyImportsFsEntry { File, Dir, } #[derive(Debug, PartialEq, Eq)] -pub enum UnstableSloppyImportsResolution<'a> { +pub enum SloppyImportsResolution<'a> { /// No sloppy resolution was found. None(&'a ModuleSpecifier), /// Ex. `./file.js` to `./file.ts` @@ -594,7 +594,7 @@ pub enum UnstableSloppyImportsResolution<'a> { Directory(ModuleSpecifier), } -impl<'a> UnstableSloppyImportsResolution<'a> { +impl<'a> SloppyImportsResolution<'a> { pub fn into_specifier(self) -> Cow<'a, ModuleSpecifier> { match self { Self::None(specifier) => Cow::Borrowed(specifier), @@ -615,35 +615,35 @@ impl<'a> UnstableSloppyImportsResolution<'a> { } #[derive(Debug)] -pub struct UnstableSloppyImportsResolver { - stat_cache: UnstableSloppyImportsStatCache, +pub struct SloppyImportsResolver { + stat_cache: SloppyImportsStatCache, } -impl UnstableSloppyImportsResolver { +impl SloppyImportsResolver { pub fn new(fs: Arc) -> Self { Self { - stat_cache: UnstableSloppyImportsStatCache::new(fs), + stat_cache: SloppyImportsStatCache::new(fs), } } pub fn resolve_with_stat_sync( specifier: &ModuleSpecifier, - stat_sync: impl Fn(&Path) -> Option, - ) -> UnstableSloppyImportsResolution { + stat_sync: impl Fn(&Path) -> Option, + ) -> SloppyImportsResolution { if specifier.scheme() != "file" { - return UnstableSloppyImportsResolution::None(specifier); + return SloppyImportsResolution::None(specifier); } let Ok(path) = specifier_to_file_path(specifier) else { - return UnstableSloppyImportsResolution::None(specifier); + return SloppyImportsResolution::None(specifier); }; let mut is_dir_resolution = false; let mut is_no_ext_resolution = false; let probe_paths = match (stat_sync)(&path) { - Some(UnstableSloppyImportsFsEntry::File) => { - return UnstableSloppyImportsResolution::None(specifier); + Some(SloppyImportsFsEntry::File) => { + return SloppyImportsResolution::None(specifier); } - Some(UnstableSloppyImportsFsEntry::Dir) => { + Some(SloppyImportsFsEntry::Dir) => { is_dir_resolution = true; // try to resolve at the index file vec![ @@ -673,7 +673,7 @@ impl UnstableSloppyImportsResolver { | MediaType::Wasm | MediaType::TsBuildInfo | MediaType::SourceMap => { - return UnstableSloppyImportsResolution::None(specifier) + return SloppyImportsResolution::None(specifier) } MediaType::Unknown => { is_no_ext_resolution = true; @@ -692,7 +692,7 @@ impl UnstableSloppyImportsResolver { MediaType::Unknown => old_path_str, _ => match old_path_str.strip_suffix(media_type.as_ts_extension()) { Some(s) => Cow::Borrowed(s), - None => return UnstableSloppyImportsResolution::None(specifier), + None => return SloppyImportsResolution::None(specifier), }, }; probe_media_type_types @@ -709,26 +709,26 @@ impl UnstableSloppyImportsResolver { }; for probe_path in probe_paths { - if (stat_sync)(&probe_path) == Some(UnstableSloppyImportsFsEntry::File) { + if (stat_sync)(&probe_path) == Some(SloppyImportsFsEntry::File) { if let Ok(specifier) = ModuleSpecifier::from_file_path(probe_path) { if is_dir_resolution { - return UnstableSloppyImportsResolution::Directory(specifier); + return SloppyImportsResolution::Directory(specifier); } else if is_no_ext_resolution { - return UnstableSloppyImportsResolution::NoExtension(specifier); + return SloppyImportsResolution::NoExtension(specifier); } else { - return UnstableSloppyImportsResolution::JsToTs(specifier); + return SloppyImportsResolution::JsToTs(specifier); } } } } - UnstableSloppyImportsResolution::None(specifier) + SloppyImportsResolution::None(specifier) } pub fn resolve<'a>( &self, specifier: &'a ModuleSpecifier, - ) -> UnstableSloppyImportsResolution<'a> { + ) -> SloppyImportsResolution<'a> { Self::resolve_with_stat_sync(specifier, |path| { self.stat_cache.stat_sync(path) }) @@ -806,13 +806,13 @@ mod test { #[test] fn test_unstable_sloppy_imports() { - fn resolve(specifier: &ModuleSpecifier) -> UnstableSloppyImportsResolution { - UnstableSloppyImportsResolver::resolve_with_stat_sync(specifier, |path| { + fn resolve(specifier: &ModuleSpecifier) -> SloppyImportsResolution { + SloppyImportsResolver::resolve_with_stat_sync(specifier, |path| { RealFs.stat_sync(path).ok().and_then(|stat| { if stat.is_file { - Some(UnstableSloppyImportsFsEntry::File) + Some(SloppyImportsFsEntry::File) } else if stat.is_directory { - Some(UnstableSloppyImportsFsEntry::Dir) + Some(SloppyImportsFsEntry::Dir) } else { None } @@ -830,7 +830,7 @@ mod test { let ts_file_uri = ts_file.uri_file(); assert_eq!( resolve(&ts_file.uri_file()), - UnstableSloppyImportsResolution::None(&ts_file_uri), + SloppyImportsResolution::None(&ts_file_uri), ); assert_eq!( resolve( @@ -839,7 +839,7 @@ mod test { .join(&format!("file.{}", ext_from)) .unwrap() ), - UnstableSloppyImportsResolution::JsToTs(ts_file.uri_file()), + SloppyImportsResolution::JsToTs(ts_file.uri_file()), ); ts_file.remove_file(); } @@ -855,7 +855,7 @@ mod test { .join("file") // no ext .unwrap() ), - UnstableSloppyImportsResolution::NoExtension(file.uri_file()), + SloppyImportsResolution::NoExtension(file.uri_file()), ); file.remove_file(); } @@ -869,7 +869,7 @@ mod test { let js_file_uri = js_file.uri_file(); assert_eq!( resolve(&js_file.uri_file()), - UnstableSloppyImportsResolution::None(&js_file_uri), + SloppyImportsResolution::None(&js_file_uri), ); } @@ -881,7 +881,7 @@ mod test { index_file.write(""); assert_eq!( resolve(&routes_dir.uri_file()), - UnstableSloppyImportsResolution::Directory(index_file.uri_file()), + SloppyImportsResolution::Directory(index_file.uri_file()), ); } } diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 711d0bcd88..9283b40216 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -10668,3 +10668,55 @@ fn lsp_sloppy_imports_warn() { client.shutdown(); } + +#[test] +fn sloppy_imports_not_enabled() { + let context = TestContextBuilder::new().use_temp_cwd().build(); + let temp_dir = context.temp_dir(); + let temp_dir = temp_dir.path(); + temp_dir.join("deno.json").write(r#"{}"#); + // The enhanced, more helpful error message is only available + // when the file exists on the file system at the moment because + // it's a little more complicated to hook it up otherwise. + temp_dir.join("a.ts").write("export class A {}"); + let mut client = context.new_lsp_command().build(); + client.initialize(|builder| { + builder.set_root_uri(temp_dir.uri_dir()); + }); + let diagnostics = client.did_open(json!({ + "textDocument": { + "uri": temp_dir.join("file.ts").uri_file(), + "languageId": "typescript", + "version": 1, + "text": "import * as a from './a';\nconsole.log(a)\n", + }, + })); + assert_eq!( + diagnostics.messages_with_source("deno"), + lsp::PublishDiagnosticsParams { + uri: temp_dir.join("file.ts").uri_file(), + diagnostics: vec![lsp::Diagnostic { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 19 + }, + end: lsp::Position { + line: 0, + character: 24 + } + }, + severity: Some(lsp::DiagnosticSeverity::ERROR), + code: Some(lsp::NumberOrString::String("no-local".to_string())), + source: Some("deno".to_string()), + message: format!( + "Unable to load a local module: {}\nMaybe add a '.ts' extension.", + temp_dir.join("a").uri_file(), + ), + ..Default::default() + }], + version: Some(1), + } + ); + client.shutdown(); +} diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 4e0bbdfd20..03de97ee79 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -4740,7 +4740,6 @@ itest!(unsafe_proto_flag { fn test_unstable_sloppy_imports() { let context = TestContextBuilder::new().use_temp_cwd().build(); let temp_dir = context.temp_dir(); - temp_dir.write("deno.json", r#"{ "unstable": ["sloppy-imports"] }"#); temp_dir.write("a.ts", "export class A {}"); temp_dir.write("b.js", "export class B {}"); temp_dir.write("c.mts", "export class C {}"); @@ -4771,6 +4770,18 @@ console.log(g.G); "#, ); + // run without sloppy imports + context + .new_command() + .args("run main.ts") + .run() + .assert_matches_text(r#"error: Module not found "file:///[WILDCARD]/a.js". Maybe change the extension to '.ts' or run with --unstable-sloppy-imports + at file:///[WILDCARD]/main.ts:1:20 +"#) + .assert_exit_code(1); + + // now run with sloppy imports + temp_dir.write("deno.json", r#"{ "unstable": ["sloppy-imports"] }"#); context .new_command() .args("run main.ts") diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index ed6192b3bf..b04aa757d9 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -495,7 +495,12 @@ pub async fn run_benchmarks_with_watch( let graph = module_graph_builder .create_graph(graph_kind, bench_modules.clone()) .await?; - graph_valid_with_cli_options(&graph, &bench_modules, cli_options)?; + graph_valid_with_cli_options( + &graph, + factory.fs(), + &bench_modules, + cli_options, + )?; let bench_modules_to_reload = if let Some(changed_paths) = changed_paths { diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index c69c3115c9..5d943d7162 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1280,7 +1280,12 @@ pub async fn run_tests_with_watch( let graph = module_graph_builder .create_graph(graph_kind, test_modules.clone()) .await?; - graph_valid_with_cli_options(&graph, &test_modules, &cli_options)?; + graph_valid_with_cli_options( + &graph, + factory.fs(), + &test_modules, + &cli_options, + )?; let test_modules_to_reload = if let Some(changed_paths) = changed_paths { diff --git a/cli/tools/vendor/build.rs b/cli/tools/vendor/build.rs index 80dda86cc3..62fc0aa9aa 100644 --- a/cli/tools/vendor/build.rs +++ b/cli/tools/vendor/build.rs @@ -15,6 +15,7 @@ use deno_graph::source::ResolutionMode; use deno_graph::EsmModule; use deno_graph::Module; use deno_graph::ModuleGraph; +use deno_runtime::deno_fs; use import_map::ImportMap; use import_map::SpecifierMap; @@ -134,8 +135,10 @@ pub async fn build< } // surface any errors + let fs: Arc = Arc::new(deno_fs::RealFs); graph_util::graph_valid( &graph, + &fs, &graph.roots, graph_util::GraphValidOptions { is_vendoring: true,