From 89c92b84fadf99e2e2d9ba6b9f469fa8991319d7 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 3 Jan 2025 16:49:56 -0500 Subject: [PATCH] fix(check): move module not found errors to typescript diagnostics (#27533) Instead of hard erroring, we now surface module not found errors as TypeScript diagnostics (we have yet to show the source code of the error, but something we can improve over time). --- Cargo.lock | 8 +- Cargo.toml | 2 +- cli/Cargo.toml | 2 +- cli/errors.rs | 84 ++-- cli/factory.rs | 1 + cli/graph_util.rs | 108 ++-- cli/module_loader.rs | 20 +- cli/tools/check.rs | 467 ++++++++++-------- cli/tsc/99_main_compiler.js | 49 +- cli/tsc/diagnostics.rs | 49 ++ cli/tsc/mod.rs | 73 +-- tests/integration/jsr_tests.rs | 3 +- tests/specs/check/css_import/__test__.jsonc | 4 + tests/specs/check/css_import/not_exists.out | 3 +- .../dts_importing_non_existent/check.out | 3 +- .../__test__.jsonc | 14 + .../check_all.out | 5 + .../import_remote.ts | 3 + .../check/module_not_found/__test__.jsonc | 24 + tests/specs/check/module_not_found/main.out | 9 + tests/specs/check/module_not_found/main.ts | 5 + .../module_not_found/missing_local_root.out | 2 + .../module_not_found/missing_remote_root.out | 3 + .../types_resolved_relative_config/main.out | 3 +- .../sloppy_imports_not_enabled.out | 5 +- .../jsx_import_source_error.out | 5 +- .../reference_types_error.js.out | 5 +- .../reference_types_error.js.out | 5 +- tests/specs/run/sloppy_imports/no_sloppy.out | 26 +- .../import_file_not_found/__test__.jsonc | 15 +- .../import_file_not_found/check.out | 4 + .../wasm_module/import_file_not_found/main.js | 1 + .../import_file_not_found/main.out | 2 +- .../__test__.jsonc | 15 +- .../import_named_export_not_found/check.out | 9 + .../import_named_export_not_found/main.js | 1 + tests/testdata/check/import_non_existent.ts | 5 + 37 files changed, 684 insertions(+), 358 deletions(-) create mode 100644 tests/specs/check/import_non_existent_in_remote/__test__.jsonc create mode 100644 tests/specs/check/import_non_existent_in_remote/check_all.out create mode 100644 tests/specs/check/import_non_existent_in_remote/import_remote.ts create mode 100644 tests/specs/check/module_not_found/__test__.jsonc create mode 100644 tests/specs/check/module_not_found/main.out create mode 100644 tests/specs/check/module_not_found/main.ts create mode 100644 tests/specs/check/module_not_found/missing_local_root.out create mode 100644 tests/specs/check/module_not_found/missing_remote_root.out create mode 100644 tests/specs/run/wasm_module/import_file_not_found/check.out create mode 100644 tests/specs/run/wasm_module/import_named_export_not_found/check.out create mode 100644 tests/testdata/check/import_non_existent.ts diff --git a/Cargo.lock b/Cargo.lock index eaba6115da..b054e9fe7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1750,9 +1750,9 @@ dependencies = [ [[package]] name = "deno_graph" -version = "0.86.6" +version = "0.86.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83af194ca492ea7b624d21055f933676d3f3d27586de93be31c8f1babcc73510" +checksum = "ace3acf321fac446636ae605b01723f2120b40ab3d32c6836aeb7d603a8e08f9" dependencies = [ "anyhow", "async-trait", @@ -1905,9 +1905,9 @@ dependencies = [ [[package]] name = "deno_media_type" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa135b8a9febc9a51c16258e294e268a1276750780d69e46edb31cced2826e4" +checksum = "a417f8bd3f1074185c4c8ccb6ea6261ae173781596cc358e68ad07aaac11009d" dependencies = [ "data-url", "serde", diff --git a/Cargo.toml b/Cargo.toml index 722fe10c60..fa2813caed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ deno_core = { version = "0.327.0" } deno_bench_util = { version = "0.178.0", path = "./bench_util" } deno_config = { version = "=0.42.0", features = ["workspace", "sync"] } deno_lockfile = "=0.24.0" -deno_media_type = { version = "0.2.0", features = ["module_specifier"] } +deno_media_type = { version = "0.2.3", features = ["module_specifier"] } deno_npm = "=0.27.0" deno_path_util = "=0.3.0" deno_permissions = { version = "0.43.0", path = "./runtime/permissions" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 3ca1afc872..a2245b0806 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -74,7 +74,7 @@ deno_config.workspace = true deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } deno_doc = { version = "=0.161.3", features = ["rust", "comrak"] } deno_error.workspace = true -deno_graph = { version = "=0.86.6" } +deno_graph = { version = "=0.86.7" } deno_lint = { version = "=0.68.2", features = ["docs"] } deno_lockfile.workspace = true deno_npm.workspace = true diff --git a/cli/errors.rs b/cli/errors.rs index ead1ccf127..6500efec50 100644 --- a/cli/errors.rs +++ b/cli/errors.rs @@ -26,51 +26,55 @@ fn get_diagnostic_class(_: &ParseDiagnostic) -> &'static str { "SyntaxError" } -fn get_module_graph_error_class(err: &ModuleGraphError) -> &'static str { - use deno_graph::JsrLoadError; - use deno_graph::NpmLoadError; - +pub fn get_module_graph_error_class(err: &ModuleGraphError) -> &'static str { match err { ModuleGraphError::ResolutionError(err) | ModuleGraphError::TypesResolutionError(err) => { get_resolution_error_class(err) } - ModuleGraphError::ModuleError(err) => match err { - ModuleError::InvalidTypeAssertion { .. } => "SyntaxError", - ModuleError::ParseErr(_, diagnostic) => get_diagnostic_class(diagnostic), - ModuleError::WasmParseErr(..) => "SyntaxError", - ModuleError::UnsupportedMediaType { .. } - | ModuleError::UnsupportedImportAttributeType { .. } => "TypeError", - ModuleError::Missing(_, _) | ModuleError::MissingDynamic(_, _) => { - "NotFound" - } - ModuleError::LoadingErr(_, _, err) => match err { - ModuleLoadError::Loader(err) => get_error_class_name(err.as_ref()), - ModuleLoadError::HttpsChecksumIntegrity(_) - | ModuleLoadError::TooManyRedirects => "Error", - ModuleLoadError::NodeUnknownBuiltinModule(_) => "NotFound", - ModuleLoadError::Decode(_) => "TypeError", - ModuleLoadError::Npm(err) => match err { - NpmLoadError::NotSupportedEnvironment - | NpmLoadError::PackageReqResolution(_) - | NpmLoadError::RegistryInfo(_) => "Error", - NpmLoadError::PackageReqReferenceParse(_) => "TypeError", - }, - ModuleLoadError::Jsr(err) => match err { - JsrLoadError::UnsupportedManifestChecksum - | JsrLoadError::PackageFormat(_) => "TypeError", - JsrLoadError::ContentLoadExternalSpecifier - | JsrLoadError::ContentLoad(_) - | JsrLoadError::ContentChecksumIntegrity(_) - | JsrLoadError::PackageManifestLoad(_, _) - | JsrLoadError::PackageVersionManifestChecksumIntegrity(..) - | JsrLoadError::PackageVersionManifestLoad(_, _) - | JsrLoadError::RedirectInPackage(_) => "Error", - JsrLoadError::PackageNotFound(_) - | JsrLoadError::PackageReqNotFound(_) - | JsrLoadError::PackageVersionNotFound(_) - | JsrLoadError::UnknownExport { .. } => "NotFound", - }, + ModuleGraphError::ModuleError(err) => get_module_error_class(err), + } +} + +pub fn get_module_error_class(err: &ModuleError) -> &'static str { + use deno_graph::JsrLoadError; + use deno_graph::NpmLoadError; + + match err { + ModuleError::InvalidTypeAssertion { .. } => "SyntaxError", + ModuleError::ParseErr(_, diagnostic) => get_diagnostic_class(diagnostic), + ModuleError::WasmParseErr(..) => "SyntaxError", + ModuleError::UnsupportedMediaType { .. } + | ModuleError::UnsupportedImportAttributeType { .. } => "TypeError", + ModuleError::Missing(_, _) | ModuleError::MissingDynamic(_, _) => { + "NotFound" + } + ModuleError::LoadingErr(_, _, err) => match err { + ModuleLoadError::Loader(err) => get_error_class_name(err.as_ref()), + ModuleLoadError::HttpsChecksumIntegrity(_) + | ModuleLoadError::TooManyRedirects => "Error", + ModuleLoadError::NodeUnknownBuiltinModule(_) => "NotFound", + ModuleLoadError::Decode(_) => "TypeError", + ModuleLoadError::Npm(err) => match err { + NpmLoadError::NotSupportedEnvironment + | NpmLoadError::PackageReqResolution(_) + | NpmLoadError::RegistryInfo(_) => "Error", + NpmLoadError::PackageReqReferenceParse(_) => "TypeError", + }, + ModuleLoadError::Jsr(err) => match err { + JsrLoadError::UnsupportedManifestChecksum + | JsrLoadError::PackageFormat(_) => "TypeError", + JsrLoadError::ContentLoadExternalSpecifier + | JsrLoadError::ContentLoad(_) + | JsrLoadError::ContentChecksumIntegrity(_) + | JsrLoadError::PackageManifestLoad(_, _) + | JsrLoadError::PackageVersionManifestChecksumIntegrity(..) + | JsrLoadError::PackageVersionManifestLoad(_, _) + | JsrLoadError::RedirectInPackage(_) => "Error", + JsrLoadError::PackageNotFound(_) + | JsrLoadError::PackageReqNotFound(_) + | JsrLoadError::PackageVersionNotFound(_) + | JsrLoadError::UnknownExport { .. } => "NotFound", }, }, } diff --git a/cli/factory.rs b/cli/factory.rs index c507d8388d..4ae1d94ea8 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -753,6 +753,7 @@ impl CliFactory { self.module_graph_builder().await?.clone(), self.node_resolver().await?.clone(), self.npm_resolver().await?.clone(), + self.sys(), ))) }) .await diff --git a/cli/graph_util.rs b/cli/graph_util.rs index fb933bdac8..ac9e75cff0 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -50,6 +50,7 @@ use crate::cache::ModuleInfoCache; use crate::cache::ParsedSourceCache; use crate::colors; use crate::errors::get_error_class_name; +use crate::errors::get_module_graph_error_class; use crate::file_fetcher::CliFileFetcher; use crate::npm::CliNpmResolver; use crate::resolver::CjsTracker; @@ -164,29 +165,15 @@ pub fn graph_walk_errors<'a>( roots.contains(error.specifier()) } }; - let mut message = match &error { - ModuleGraphError::ResolutionError(resolution_error) => { - enhanced_resolution_error_message(resolution_error) - } - ModuleGraphError::TypesResolutionError(resolution_error) => { - format!( - "Failed resolving types. {}", - enhanced_resolution_error_message(resolution_error) - ) - } - ModuleGraphError::ModuleError(error) => { - enhanced_integrity_error_message(error) - .or_else(|| enhanced_sloppy_imports_error_message(sys, error)) - .unwrap_or_else(|| format_deno_graph_error(error)) - } - }; - - if let Some(range) = error.maybe_range() { - if !is_root && !range.specifier.as_str().contains("/$deno$eval") { - message.push_str("\n at "); - message.push_str(&format_range_with_colors(range)); - } - } + let message = enhance_graph_error( + sys, + &error, + if is_root { + EnhanceGraphErrorMode::HideRange + } else { + EnhanceGraphErrorMode::ShowRange + }, + ); if graph.graph_kind() == GraphKind::TypesOnly && matches!( @@ -198,10 +185,61 @@ pub fn graph_walk_errors<'a>( return None; } - Some(custom_error(get_error_class_name(&error.into()), message)) + if graph.graph_kind().include_types() + && (message.contains(RUN_WITH_SLOPPY_IMPORTS_MSG) + || matches!( + error, + ModuleGraphError::ModuleError(ModuleError::Missing(..)) + )) + { + // ignore and let typescript surface this as a diagnostic instead + log::debug!("Ignoring: {}", message); + return None; + } + + Some(custom_error(get_module_graph_error_class(&error), message)) }) } +#[derive(Debug, PartialEq, Eq)] +pub enum EnhanceGraphErrorMode { + ShowRange, + HideRange, +} + +pub fn enhance_graph_error( + sys: &CliSys, + error: &ModuleGraphError, + mode: EnhanceGraphErrorMode, +) -> String { + let mut message = match &error { + ModuleGraphError::ResolutionError(resolution_error) => { + enhanced_resolution_error_message(resolution_error) + } + ModuleGraphError::TypesResolutionError(resolution_error) => { + format!( + "Failed resolving types. {}", + enhanced_resolution_error_message(resolution_error) + ) + } + ModuleGraphError::ModuleError(error) => { + enhanced_integrity_error_message(error) + .or_else(|| enhanced_sloppy_imports_error_message(sys, error)) + .unwrap_or_else(|| format_deno_graph_error(error)) + } + }; + + if let Some(range) = error.maybe_range() { + if mode == EnhanceGraphErrorMode::ShowRange + && !range.specifier.as_str().contains("/$deno$eval") + { + message.push_str("\n at "); + message.push_str(&format_range_with_colors(range)); + } + } + message +} + pub fn graph_exit_integrity_errors(graph: &ModuleGraph) { for error in graph.module_errors() { exit_for_integrity_error(error); @@ -835,6 +873,9 @@ pub fn enhanced_resolution_error_message(error: &ResolutionError) -> String { message } +static RUN_WITH_SLOPPY_IMPORTS_MSG: &str = + "or run with --unstable-sloppy-imports"; + fn enhanced_sloppy_imports_error_message( sys: &CliSys, error: &ModuleError, @@ -842,11 +883,9 @@ fn enhanced_sloppy_imports_error_message( match error { ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error | ModuleError::Missing(specifier, _) => { - let additional_message = CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(sys.clone())) - .resolve(specifier, SloppyImportsResolutionKind::Execution)? - .as_suggestion_message(); + let additional_message = maybe_additional_sloppy_imports_message(sys, specifier)?; Some(format!( - "{} {} or run with --unstable-sloppy-imports", + "{} {}", error, additional_message, )) @@ -855,6 +894,19 @@ fn enhanced_sloppy_imports_error_message( } } +pub fn maybe_additional_sloppy_imports_message( + sys: &CliSys, + specifier: &ModuleSpecifier, +) -> Option { + Some(format!( + "{} {}", + CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(sys.clone())) + .resolve(specifier, SloppyImportsResolutionKind::Execution)? + .as_suggestion_message(), + RUN_WITH_SLOPPY_IMPORTS_MSG + )) +} + fn enhanced_integrity_error_message(err: &ModuleError) -> Option { match err { ModuleError::LoadingErr( diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 8256c56781..446397cad1 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -36,6 +36,7 @@ use deno_graph::JsModule; use deno_graph::JsonModule; use deno_graph::Module; use deno_graph::ModuleGraph; +use deno_graph::ModuleGraphError; use deno_graph::Resolution; use deno_graph::WasmModule; use deno_runtime::code_cache; @@ -58,10 +59,13 @@ use crate::cache::CodeCache; use crate::cache::FastInsecureHasher; use crate::cache::ParsedSourceCache; use crate::emit::Emitter; +use crate::errors::get_module_error_class; use crate::graph_container::MainModuleGraphContainer; use crate::graph_container::ModuleGraphContainer; use crate::graph_container::ModuleGraphUpdatePermit; +use crate::graph_util::enhance_graph_error; use crate::graph_util::CreateGraphOptions; +use crate::graph_util::EnhanceGraphErrorMode; use crate::graph_util::ModuleGraphBuilder; use crate::node::CliNodeCodeTranslator; use crate::node::CliNodeResolver; @@ -703,7 +707,21 @@ impl unreachable!("Deno bug. {} was misconfigured internally.", specifier); } - match graph.get(specifier) { + let maybe_module = match graph.try_get(specifier) { + Ok(module) => module, + Err(err) => { + return Err(custom_error( + get_module_error_class(err), + enhance_graph_error( + &self.shared.sys, + &ModuleGraphError::ModuleError(err.clone()), + EnhanceGraphErrorMode::ShowRange, + ), + )) + } + }; + + match maybe_module { Some(deno_graph::Module::Json(JsonModule { source, media_type, diff --git a/cli/tools/check.rs b/cli/tools/check.rs index f3df54626a..1ee3f1782b 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -8,7 +8,9 @@ use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; use deno_graph::Module; +use deno_graph::ModuleError; use deno_graph::ModuleGraph; +use deno_graph::ModuleLoadError; use deno_terminal::colors; use once_cell::sync::Lazy; use regex::Regex; @@ -26,10 +28,12 @@ use crate::cache::Caches; use crate::cache::FastInsecureHasher; use crate::cache::TypeCheckCache; use crate::factory::CliFactory; +use crate::graph_util::maybe_additional_sloppy_imports_message; use crate::graph_util::BuildFastCheckGraphOptions; use crate::graph_util::ModuleGraphBuilder; use crate::node::CliNodeResolver; use crate::npm::CliNpmResolver; +use crate::sys::CliSys; use crate::tsc; use crate::tsc::Diagnostics; use crate::tsc::TypeCheckingCjsTracker; @@ -105,6 +109,7 @@ pub struct TypeChecker { module_graph_builder: Arc, node_resolver: Arc, npm_resolver: Arc, + sys: CliSys, } impl TypeChecker { @@ -115,6 +120,7 @@ impl TypeChecker { module_graph_builder: Arc, node_resolver: Arc, npm_resolver: Arc, + sys: CliSys, ) -> Self { Self { caches, @@ -123,6 +129,7 @@ impl TypeChecker { module_graph_builder, node_resolver, npm_resolver, + sys, } } @@ -177,26 +184,47 @@ impl TypeChecker { let type_check_mode = options.type_check_mode; let ts_config = ts_config_result.ts_config; - let maybe_check_hash = match self.npm_resolver.check_state_hash() { - Some(npm_check_hash) => { - match get_check_hash( - &graph, - npm_check_hash, - type_check_mode, - &ts_config, - ) { - CheckHashResult::NoFiles => { - return Ok((graph.into(), Default::default())) - } - CheckHashResult::Hash(hash) => Some(hash), - } - } - None => None, // we can't determine a check hash - }; - - // do not type check if we know this is type checked let cache = TypeCheckCache::new(self.caches.type_checking_cache_db()); + let check_js = ts_config.get_check_js(); + + // add fast check to the graph before getting the roots + if options.build_fast_check_graph { + self.module_graph_builder.build_fast_check_graph( + &mut graph, + BuildFastCheckGraphOptions { + workspace_fast_check: deno_graph::WorkspaceFastCheckOption::Disabled, + }, + )?; + } + + let filter_remote_diagnostics = |d: &tsc::Diagnostic| { + if self.is_remote_diagnostic(d) { + type_check_mode == TypeCheckMode::All && d.include_when_remote() + } else { + true + } + }; + let TscRoots { + roots: root_names, + missing_diagnostics, + maybe_check_hash, + } = get_tsc_roots( + &self.sys, + &graph, + check_js, + self.npm_resolver.check_state_hash(), + type_check_mode, + &ts_config, + ); + + let missing_diagnostics = + missing_diagnostics.filter(filter_remote_diagnostics); + + if root_names.is_empty() && missing_diagnostics.is_empty() { + return Ok((graph.into(), Default::default())); + } if !options.reload { + // do not type check if we know this is type checked if let Some(check_hash) = maybe_check_hash { if cache.has_check_hash(check_hash) { log::debug!("Already type checked."); @@ -214,7 +242,6 @@ impl TypeChecker { ); } - let check_js = ts_config.get_check_js(); // while there might be multiple roots, we can't "merge" the build info, so we // try to retrieve the build info for first root, which is the most common use // case. @@ -226,27 +253,15 @@ impl TypeChecker { // to make tsc build info work, we need to consistently hash modules, so that // tsc can better determine if an emit is still valid or not, so we provide // that data here. - let hash_data = FastInsecureHasher::new_deno_versioned() + let tsconfig_hash_data = FastInsecureHasher::new_deno_versioned() .write(&ts_config.as_bytes()) .finish(); - - // add fast check to the graph before getting the roots - if options.build_fast_check_graph { - self.module_graph_builder.build_fast_check_graph( - &mut graph, - BuildFastCheckGraphOptions { - workspace_fast_check: deno_graph::WorkspaceFastCheckOption::Disabled, - }, - )?; - } - - let root_names = get_tsc_roots(&graph, check_js); let graph = Arc::new(graph); let response = tsc::exec(tsc::Request { config: ts_config, debug: self.cli_options.log_level() == Some(log::Level::Debug), graph: graph.clone(), - hash_data, + hash_data: tsconfig_hash_data, maybe_npm: Some(tsc::RequestNpmState { cjs_tracker: self.cjs_tracker.clone(), node_resolver: self.node_resolver.clone(), @@ -257,13 +272,11 @@ impl TypeChecker { check_mode: type_check_mode, })?; - let mut diagnostics = response.diagnostics.filter(|d| { - if self.is_remote_diagnostic(d) { - type_check_mode == TypeCheckMode::All && d.include_when_remote() - } else { - true - } - }); + let response_diagnostics = + response.diagnostics.filter(filter_remote_diagnostics); + + let mut diagnostics = missing_diagnostics; + diagnostics.extend(response_diagnostics); diagnostics.apply_fast_check_source_maps(&graph); @@ -297,108 +310,10 @@ impl TypeChecker { } } -enum CheckHashResult { - Hash(CacheDBHash), - NoFiles, -} - -/// Gets a hash of the inputs for type checking. This can then -/// be used to tell -fn get_check_hash( - graph: &ModuleGraph, - package_reqs_hash: u64, - type_check_mode: TypeCheckMode, - ts_config: &TsConfig, -) -> CheckHashResult { - let mut hasher = FastInsecureHasher::new_deno_versioned(); - hasher.write_u8(match type_check_mode { - TypeCheckMode::All => 0, - TypeCheckMode::Local => 1, - TypeCheckMode::None => 2, - }); - hasher.write(&ts_config.as_bytes()); - - let check_js = ts_config.get_check_js(); - let mut has_file = false; - let mut has_file_to_type_check = false; - // this iterator of modules is already deterministic, so no need to sort it - for module in graph.modules() { - match module { - Module::Js(module) => { - let ts_check = has_ts_check(module.media_type, &module.source); - if ts_check { - has_file_to_type_check = true; - } - - match module.media_type { - MediaType::TypeScript - | MediaType::Dts - | MediaType::Dmts - | MediaType::Dcts - | MediaType::Mts - | MediaType::Cts - | MediaType::Tsx => { - has_file = true; - has_file_to_type_check = true; - } - MediaType::JavaScript - | MediaType::Mjs - | MediaType::Cjs - | MediaType::Jsx => { - has_file = true; - if !check_js && !ts_check { - continue; - } - } - MediaType::Json - | MediaType::Css - | MediaType::SourceMap - | MediaType::Wasm - | MediaType::Unknown => continue, - } - - hasher.write_str(module.specifier.as_str()); - hasher.write_str( - // the fast check module will only be set when publishing - module - .fast_check_module() - .map(|s| s.source.as_ref()) - .unwrap_or(&module.source), - ); - } - Module::Node(_) => { - // the @types/node package will be in the resolved - // snapshot below so don't bother including it here - } - Module::Npm(_) => { - // don't bother adding this specifier to the hash - // because what matters is the resolved npm snapshot, - // which is hashed below - } - Module::Json(module) => { - has_file_to_type_check = true; - hasher.write_str(module.specifier.as_str()); - hasher.write_str(&module.source); - } - Module::Wasm(module) => { - has_file_to_type_check = true; - hasher.write_str(module.specifier.as_str()); - hasher.write_str(&module.source_dts); - } - Module::External(module) => { - hasher.write_str(module.specifier.as_str()); - } - } - } - - hasher.write_hashable(package_reqs_hash); - - if !has_file || !check_js && !has_file_to_type_check { - // no files to type check - CheckHashResult::NoFiles - } else { - CheckHashResult::Hash(CacheDBHash::new(hasher.finish())) - } +struct TscRoots { + roots: Vec<(ModuleSpecifier, MediaType)>, + missing_diagnostics: tsc::Diagnostics, + maybe_check_hash: Option, } /// Transform the graph into root specifiers that we can feed `tsc`. We have to @@ -408,52 +323,115 @@ fn get_check_hash( /// the roots, so they get type checked and optionally emitted, /// otherwise they would be ignored if only imported into JavaScript. fn get_tsc_roots( + sys: &CliSys, graph: &ModuleGraph, check_js: bool, -) -> Vec<(ModuleSpecifier, MediaType)> { + npm_cache_state_hash: Option, + type_check_mode: TypeCheckMode, + ts_config: &TsConfig, +) -> TscRoots { fn maybe_get_check_entry( module: &deno_graph::Module, check_js: bool, + hasher: Option<&mut FastInsecureHasher>, ) -> Option<(ModuleSpecifier, MediaType)> { match module { - Module::Js(module) => match module.media_type { - MediaType::TypeScript - | MediaType::Tsx - | MediaType::Mts - | MediaType::Cts - | MediaType::Dts - | MediaType::Dmts - | MediaType::Dcts => { - Some((module.specifier.clone(), module.media_type)) - } - MediaType::JavaScript - | MediaType::Mjs - | MediaType::Cjs - | MediaType::Jsx => { - if check_js || has_ts_check(module.media_type, &module.source) { + Module::Js(module) => { + let result = match module.media_type { + MediaType::TypeScript + | MediaType::Tsx + | MediaType::Mts + | MediaType::Cts + | MediaType::Dts + | MediaType::Dmts + | MediaType::Dcts => { Some((module.specifier.clone(), module.media_type)) - } else { - None + } + MediaType::JavaScript + | MediaType::Mjs + | MediaType::Cjs + | MediaType::Jsx => { + if check_js || has_ts_check(module.media_type, &module.source) { + Some((module.specifier.clone(), module.media_type)) + } else { + None + } + } + MediaType::Json + | MediaType::Wasm + | MediaType::Css + | MediaType::SourceMap + | MediaType::Unknown => None, + }; + if result.is_some() { + if let Some(hasher) = hasher { + hasher.write_str(module.specifier.as_str()); + hasher.write_str( + // the fast check module will only be set when publishing + module + .fast_check_module() + .map(|s| s.source.as_ref()) + .unwrap_or(&module.source), + ); } } - MediaType::Json - | MediaType::Wasm - | MediaType::Css - | MediaType::SourceMap - | MediaType::Unknown => None, - }, - Module::Wasm(module) => Some((module.specifier.clone(), MediaType::Dmts)), - Module::External(_) - | Module::Node(_) - | Module::Npm(_) - | Module::Json(_) => None, + result + } + Module::Node(_) => { + // the @types/node package will be in the resolved + // snapshot so don't bother including it in the hash + None + } + Module::Npm(_) => { + // don't bother adding this specifier to the hash + // because what matters is the resolved npm snapshot, + // which is hashed below + None + } + Module::Json(module) => { + if let Some(hasher) = hasher { + hasher.write_str(module.specifier.as_str()); + hasher.write_str(&module.source); + } + None + } + Module::Wasm(module) => { + if let Some(hasher) = hasher { + hasher.write_str(module.specifier.as_str()); + hasher.write_str(&module.source_dts); + } + Some((module.specifier.clone(), MediaType::Dmts)) + } + Module::External(module) => { + if let Some(hasher) = hasher { + hasher.write_str(module.specifier.as_str()); + } + None + } } } - let mut result = Vec::with_capacity(graph.specifiers_count()); + let mut result = TscRoots { + roots: Vec::with_capacity(graph.specifiers_count()), + missing_diagnostics: Default::default(), + maybe_check_hash: None, + }; + let mut maybe_hasher = npm_cache_state_hash.map(|npm_cache_state_hash| { + let mut hasher = FastInsecureHasher::new_deno_versioned(); + hasher.write_hashable(npm_cache_state_hash); + hasher.write_u8(match type_check_mode { + TypeCheckMode::All => 0, + TypeCheckMode::Local => 1, + TypeCheckMode::None => 2, + }); + hasher.write_hashable(graph.has_node_specifier); + hasher.write(&ts_config.as_bytes()); + hasher + }); + if graph.has_node_specifier { // inject a specifier that will resolve node types - result.push(( + result.roots.push(( ModuleSpecifier::parse("asset:///node_types.d.ts").unwrap(), MediaType::Dts, )); @@ -464,65 +442,134 @@ fn get_tsc_roots( let mut pending = VecDeque::new(); // put in the global types first so that they're resolved before anything else - for import in graph.imports.values() { - for dep in import.dependencies.values() { - let specifier = dep.get_type().or_else(|| dep.get_code()); - if let Some(specifier) = &specifier { - let specifier = graph.resolve(specifier); - if seen.insert(specifier.clone()) { - pending.push_back(specifier); - } - } + let get_import_specifiers = || { + graph + .imports + .values() + .flat_map(|i| i.dependencies.values()) + .filter_map(|dep| dep.get_type().or_else(|| dep.get_code())) + }; + for specifier in get_import_specifiers() { + let specifier = graph.resolve(specifier); + if seen.insert(specifier) { + pending.push_back((specifier, false)); } } // then the roots for root in &graph.roots { let specifier = graph.resolve(root); - if seen.insert(specifier.clone()) { - pending.push_back(specifier); + if seen.insert(specifier) { + pending.push_back((specifier, false)); } } // now walk the graph that only includes the fast check dependencies - while let Some(specifier) = pending.pop_front() { - let Some(module) = graph.get(specifier) else { - continue; + while let Some((specifier, is_dynamic)) = pending.pop_front() { + let module = match graph.try_get(specifier) { + Ok(Some(module)) => module, + Ok(None) => continue, + Err(ModuleError::Missing(specifier, maybe_range)) => { + if !is_dynamic { + result + .missing_diagnostics + .push(tsc::Diagnostic::from_missing_error( + specifier, + maybe_range.as_ref(), + maybe_additional_sloppy_imports_message(sys, specifier), + )); + } + continue; + } + Err(ModuleError::LoadingErr( + specifier, + maybe_range, + ModuleLoadError::Loader(_), + )) => { + // these will be errors like attempting to load a directory + if !is_dynamic { + result + .missing_diagnostics + .push(tsc::Diagnostic::from_missing_error( + specifier, + maybe_range.as_ref(), + maybe_additional_sloppy_imports_message(sys, specifier), + )); + } + continue; + } + Err(_) => continue, }; - if let Some(entry) = maybe_get_check_entry(module, check_js) { - result.push(entry); + if is_dynamic && !seen.insert(specifier) { + continue; } - if let Some(module) = module.js() { - let deps = module.dependencies_prefer_fast_check(); + if let Some(entry) = + maybe_get_check_entry(module, check_js, maybe_hasher.as_mut()) + { + result.roots.push(entry); + } + + let mut maybe_module_dependencies = None; + let mut maybe_types_dependency = None; + if let Module::Js(module) = module { + maybe_module_dependencies = Some(module.dependencies_prefer_fast_check()); + maybe_types_dependency = module + .maybe_types_dependency + .as_ref() + .and_then(|d| d.dependency.ok()); + } else if let Module::Wasm(module) = module { + maybe_module_dependencies = Some(&module.dependencies); + } + + fn handle_specifier<'a>( + graph: &'a ModuleGraph, + seen: &mut HashSet<&'a ModuleSpecifier>, + pending: &mut VecDeque<(&'a ModuleSpecifier, bool)>, + specifier: &'a ModuleSpecifier, + is_dynamic: bool, + ) { + let specifier = graph.resolve(specifier); + if is_dynamic { + if !seen.contains(specifier) { + pending.push_back((specifier, true)); + } + } else if seen.insert(specifier) { + pending.push_back((specifier, false)); + } + } + + if let Some(deps) = maybe_module_dependencies { for dep in deps.values() { // walk both the code and type dependencies if let Some(specifier) = dep.get_code() { - let specifier = graph.resolve(specifier); - if seen.insert(specifier.clone()) { - pending.push_back(specifier); - } + handle_specifier( + graph, + &mut seen, + &mut pending, + specifier, + dep.is_dynamic, + ); } if let Some(specifier) = dep.get_type() { - let specifier = graph.resolve(specifier); - if seen.insert(specifier.clone()) { - pending.push_back(specifier); - } - } - } - - if let Some(dep) = module - .maybe_types_dependency - .as_ref() - .and_then(|d| d.dependency.ok()) - { - let specifier = graph.resolve(&dep.specifier); - if seen.insert(specifier.clone()) { - pending.push_back(specifier); + handle_specifier( + graph, + &mut seen, + &mut pending, + specifier, + dep.is_dynamic, + ); } } } + + if let Some(dep) = maybe_types_dependency { + handle_specifier(graph, &mut seen, &mut pending, &dep.specifier, false); + } } + result.maybe_check_hash = + maybe_hasher.map(|hasher| CacheDBHash::new(hasher.finish())); + result } diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index d532de265f..25813c3f9d 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -409,9 +409,20 @@ delete Object.prototype.__proto__; messageText = formatMessage(msgText, ri.code); } if (start !== undefined && length !== undefined && file) { - const startPos = file.getLineAndCharacterOfPosition(start); - const sourceLine = file.getFullText().split("\n")[startPos.line]; - const fileName = file.fileName; + let startPos = file.getLineAndCharacterOfPosition(start); + let sourceLine = file.getFullText().split("\n")[startPos.line]; + const originalFileName = file.fileName; + const fileName = ops.op_remap_specifier + ? (ops.op_remap_specifier(file.fileName) ?? file.fileName) + : file.fileName; + // Bit of a hack to detect when we have a .wasm file and want to hide + // the .d.ts text. This is not perfect, but will work in most scenarios + if ( + fileName.endsWith(".wasm") && originalFileName.endsWith(".wasm.d.mts") + ) { + startPos = { line: 0, character: 0 }; + sourceLine = undefined; + } return { start: startPos, end: file.getLineAndCharacterOfPosition(start + length), @@ -475,6 +486,9 @@ delete Object.prototype.__proto__; 2792, // TS2307: Cannot find module '{0}' or its corresponding type declarations. 2307, + // Relative import errors to add an extension + 2834, + 2835, // TS5009: Cannot find the common subdirectory path for the input files. 5009, // TS5055: Cannot write file @@ -1037,24 +1051,27 @@ delete Object.prototype.__proto__; configFileParsingDiagnostics, }); - const checkFiles = localOnly - ? rootNames - .filter((n) => !n.startsWith("http")) - .map((checkName) => { - const sourceFile = program.getSourceFile(checkName); - if (sourceFile == null) { - throw new Error("Could not find source file for: " + checkName); - } - return sourceFile; - }) - : undefined; + let checkFiles = undefined; + + if (localOnly) { + const checkFileNames = new Set(); + checkFiles = []; + + for (const checkName of rootNames) { + if (checkName.startsWith("http")) { + continue; + } + const sourceFile = program.getSourceFile(checkName); + if (sourceFile != null) { + checkFiles.push(sourceFile); + } + checkFileNames.add(checkName); + } - if (checkFiles != null) { // When calling program.getSemanticDiagnostics(...) with a source file, we // need to call this code first in order to get it to invalidate cached // diagnostics correctly. This is what program.getSemanticDiagnostics() // does internally when calling without any arguments. - const checkFileNames = new Set(checkFiles.map((f) => f.fileName)); while ( program.getSemanticDiagnosticsOfNextAffectedFile( undefined, diff --git a/cli/tsc/diagnostics.rs b/cli/tsc/diagnostics.rs index cda41525c2..ac93c8575d 100644 --- a/cli/tsc/diagnostics.rs +++ b/cli/tsc/diagnostics.rs @@ -110,6 +110,15 @@ pub struct Position { pub character: u64, } +impl Position { + pub fn from_deno_graph(deno_graph_position: deno_graph::Position) -> Self { + Self { + line: deno_graph_position.line as u64, + character: deno_graph_position.character as u64, + } + } +} + #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Diagnostic { @@ -142,6 +151,38 @@ pub struct Diagnostic { } impl Diagnostic { + pub fn from_missing_error( + specifier: &ModuleSpecifier, + maybe_range: Option<&deno_graph::Range>, + additional_message: Option, + ) -> Self { + Self { + category: DiagnosticCategory::Error, + code: 2307, + start: maybe_range.map(|r| Position::from_deno_graph(r.range.start)), + end: maybe_range.map(|r| Position::from_deno_graph(r.range.end)), + original_source_start: None, // will be applied later + message_text: Some(format!( + "Cannot find module '{}'.{}{}", + specifier, + if additional_message.is_none() { + "" + } else { + " " + }, + additional_message.unwrap_or_default() + )), + message_chain: None, + source: None, + source_line: None, + file_name: maybe_range.map(|r| r.specifier.to_string()), + related_information: None, + reports_deprecated: None, + reports_unnecessary: None, + other: Default::default(), + } + } + /// If this diagnostic should be included when it comes from a remote module. pub fn include_when_remote(&self) -> bool { /// TS6133: value is declared but its value is never read (noUnusedParameters and noUnusedLocals) @@ -299,6 +340,14 @@ impl Diagnostics { }); } + pub fn push(&mut self, diagnostic: Diagnostic) { + self.0.push(diagnostic); + } + + pub fn extend(&mut self, diagnostic: Diagnostics) { + self.0.extend(diagnostic.0); + } + /// Return a set of diagnostics where only the values where the predicate /// returns `true` are included. pub fn filter

(self, predicate: P) -> Self diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index 9f1dc3653a..3176c50d5c 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -129,6 +129,7 @@ fn get_asset_texts_from_new_runtime() -> Result, AnyError> { op_emit, op_is_node_file, op_load, + op_remap_specifier, op_resolve, op_respond, ] @@ -275,30 +276,6 @@ fn hash_url(specifier: &ModuleSpecifier, media_type: MediaType) -> String { ) } -/// If the provided URLs derivable tsc media type doesn't match the media type, -/// we will add an extension to the output. This is to avoid issues with -/// specifiers that don't have extensions, that tsc refuses to emit because they -/// think a `.js` version exists, when it doesn't. -fn maybe_remap_specifier( - specifier: &ModuleSpecifier, - media_type: MediaType, -) -> Option { - let path = if specifier.scheme() == "file" { - if let Ok(path) = specifier.to_file_path() { - path - } else { - PathBuf::from(specifier.path()) - } - } else { - PathBuf::from(specifier.path()) - }; - if path.extension().is_none() { - Some(format!("{}{}", specifier, media_type.as_ts_extension())) - } else { - None - } -} - #[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct EmittedFile { pub data: String, @@ -316,7 +293,7 @@ pub fn into_specifier_and_media_type( (specifier, media_type) } None => ( - Url::parse("internal:///missing_dependency.d.ts").unwrap(), + Url::parse(MISSING_DEPENDENCY_SPECIFIER).unwrap(), MediaType::Dts, ), } @@ -422,6 +399,8 @@ struct State { maybe_tsbuildinfo: Option, maybe_response: Option, maybe_npm: Option, + // todo(dsherret): it looks like the remapped_specifiers and + // root_map could be combined... what is the point of the separation? remapped_specifiers: HashMap, root_map: HashMap, current_dir: PathBuf, @@ -463,6 +442,16 @@ impl State { current_dir, } } + + pub fn maybe_remapped_specifier( + &self, + specifier: &str, + ) -> Option<&ModuleSpecifier> { + self + .remapped_specifiers + .get(specifier) + .or_else(|| self.root_map.get(specifier)) + } } fn normalize_specifier( @@ -607,10 +596,7 @@ fn op_load_inner( maybe_source.map(Cow::Borrowed) } else { let specifier = if let Some(remapped_specifier) = - state.remapped_specifiers.get(load_specifier) - { - remapped_specifier - } else if let Some(remapped_specifier) = state.root_map.get(load_specifier) + state.maybe_remapped_specifier(load_specifier) { remapped_specifier } else { @@ -713,6 +699,18 @@ pub struct ResolveArgs { pub specifiers: Vec<(bool, String)>, } +#[op2] +#[string] +fn op_remap_specifier( + state: &mut OpState, + #[string] specifier: &str, +) -> Option { + let state = state.borrow::(); + state + .maybe_remapped_specifier(specifier) + .map(|url| url.to_string()) +} + #[op2] #[serde] fn op_resolve( @@ -732,11 +730,9 @@ fn op_resolve_inner( let mut resolved: Vec<(String, &'static str)> = Vec::with_capacity(args.specifiers.len()); let referrer = if let Some(remapped_specifier) = - state.remapped_specifiers.get(&args.base) + state.maybe_remapped_specifier(&args.base) { remapped_specifier.clone() - } else if let Some(remapped_base) = state.root_map.get(&args.base) { - remapped_base.clone() } else { normalize_specifier(&args.base, &state.current_dir).context( "Error converting a string module specifier for \"op_resolve\".", @@ -759,8 +755,12 @@ fn op_resolve_inner( } let resolved_dep = referrer_module - .and_then(|m| m.js()) - .and_then(|m| m.dependencies_prefer_fast_check().get(&specifier)) + .and_then(|m| match m { + Module::Js(m) => m.dependencies_prefer_fast_check().get(&specifier), + Module::Json(_) => None, + Module::Wasm(m) => m.dependencies.get(&specifier), + Module::Npm(_) | Module::Node(_) | Module::External(_) => None, + }) .and_then(|d| d.maybe_type.ok().or_else(|| d.maybe_code.ok())); let resolution_mode = if is_cjs { ResolutionMode::Require @@ -816,7 +816,7 @@ fn op_resolve_inner( } _ => { if let Some(specifier_str) = - maybe_remap_specifier(&specifier, media_type) + mapped_specifier_for_tsc(&specifier, media_type) { state .remapped_specifiers @@ -840,7 +840,7 @@ fn op_resolve_inner( MediaType::Dts.as_ts_extension(), ), }; - log::debug!("Resolved {} to {:?}", specifier, result); + log::debug!("Resolved {} from {} to {:?}", specifier, referrer, result); resolved.push(result); } @@ -1072,6 +1072,7 @@ pub fn exec(request: Request) -> Result { op_emit, op_is_node_file, op_load, + op_remap_specifier, op_resolve, op_respond, ], diff --git a/tests/integration/jsr_tests.rs b/tests/integration/jsr_tests.rs index 95a9fcb437..0902b7d0a2 100644 --- a/tests/integration/jsr_tests.rs +++ b/tests/integration/jsr_tests.rs @@ -66,12 +66,11 @@ fn fast_check_cache() { // ensure cache works let output = check_debug_cmd.run(); assert_contains!(output.combined_output(), "Already type checked."); - let building_fast_check_msg = "Building fast check graph"; - assert_not_contains!(output.combined_output(), building_fast_check_msg); // now validated type_check_cache_path.remove_file(); let output = check_debug_cmd.run(); + let building_fast_check_msg = "Building fast check graph"; assert_contains!(output.combined_output(), building_fast_check_msg); assert_contains!( output.combined_output(), diff --git a/tests/specs/check/css_import/__test__.jsonc b/tests/specs/check/css_import/__test__.jsonc index 629dcd3833..4e16560ec2 100644 --- a/tests/specs/check/css_import/__test__.jsonc +++ b/tests/specs/check/css_import/__test__.jsonc @@ -10,6 +10,10 @@ "args": "check not_exists.ts", "output": "not_exists.out", "exitCode": 1 + }, { + "args": "run --check not_exists.ts", + "output": "not_exists.out", + "exitCode": 1 }, { "args": "check exists_and_try_uses.ts", "output": "exists_and_try_uses.out", diff --git a/tests/specs/check/css_import/not_exists.out b/tests/specs/check/css_import/not_exists.out index 95fd14668e..1e9dce6b70 100644 --- a/tests/specs/check/css_import/not_exists.out +++ b/tests/specs/check/css_import/not_exists.out @@ -1,2 +1,3 @@ -error: Module not found "file:///[WILDLINE]/not_exists.css". +Check [WILDLINE]exists.ts +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/not_exists.css'. at file:///[WILDLINE]/not_exists.ts:1:8 diff --git a/tests/specs/check/dts_importing_non_existent/check.out b/tests/specs/check/dts_importing_non_existent/check.out index 80ec9593b0..65e27bce83 100644 --- a/tests/specs/check/dts_importing_non_existent/check.out +++ b/tests/specs/check/dts_importing_non_existent/check.out @@ -1,2 +1,3 @@ -error: Module not found "file:///[WILDLINE]/test". +Check file:///[WILDLINE]/dts_importing_non_existent/index.js +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/test'. at file:///[WILDLINE]/index.d.ts:1:22 diff --git a/tests/specs/check/import_non_existent_in_remote/__test__.jsonc b/tests/specs/check/import_non_existent_in_remote/__test__.jsonc new file mode 100644 index 0000000000..39cd37ffc0 --- /dev/null +++ b/tests/specs/check/import_non_existent_in_remote/__test__.jsonc @@ -0,0 +1,14 @@ +{ + "tests": { + "not_all": { + "args": "check --allow-import import_remote.ts", + "output": "[WILDCARD]", + "exitCode": 0 + }, + "all": { + "args": "check --all --allow-import import_remote.ts", + "output": "check_all.out", + "exitCode": 1 + } + } +} diff --git a/tests/specs/check/import_non_existent_in_remote/check_all.out b/tests/specs/check/import_non_existent_in_remote/check_all.out new file mode 100644 index 0000000000..a3c3b1759c --- /dev/null +++ b/tests/specs/check/import_non_existent_in_remote/check_all.out @@ -0,0 +1,5 @@ +Download http://localhost:4545/check/import_non_existent.ts +Download http://localhost:4545/check/non-existent-module.ts +Check file:///[WILDLINE]/import_remote.ts +error: TS2307 [ERROR]: Cannot find module 'http://localhost:4545/check/non-existent-module.ts'. + at http://localhost:4545/check/import_non_existent.ts:1:22 diff --git a/tests/specs/check/import_non_existent_in_remote/import_remote.ts b/tests/specs/check/import_non_existent_in_remote/import_remote.ts new file mode 100644 index 0000000000..47c5c654b8 --- /dev/null +++ b/tests/specs/check/import_non_existent_in_remote/import_remote.ts @@ -0,0 +1,3 @@ +import { Other } from "http://localhost:4545/check/import_non_existent.ts"; + +console.log(Other); diff --git a/tests/specs/check/module_not_found/__test__.jsonc b/tests/specs/check/module_not_found/__test__.jsonc new file mode 100644 index 0000000000..5e7cfa2e59 --- /dev/null +++ b/tests/specs/check/module_not_found/__test__.jsonc @@ -0,0 +1,24 @@ +{ + "tests": { + "check": { + "args": "check --allow-import main.ts", + "output": "main.out", + "exitCode": 1 + }, + "run": { + "args": "run --check --allow-import main.ts", + "output": "main.out", + "exitCode": 1 + }, + "missing_local_root": { + "args": "check --allow-import non_existent.ts", + "output": "missing_local_root.out", + "exitCode": 1 + }, + "missing_remote_root": { + "args": "check --allow-import http://localhost:4545/missing_non_existent.ts", + "output": "missing_remote_root.out", + "exitCode": 1 + } + } +} diff --git a/tests/specs/check/module_not_found/main.out b/tests/specs/check/module_not_found/main.out new file mode 100644 index 0000000000..6c16183560 --- /dev/null +++ b/tests/specs/check/module_not_found/main.out @@ -0,0 +1,9 @@ +Download http://localhost:4545/remote.ts +Check file:///[WILDLINE]/module_not_found/main.ts +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/other.js'. + at file:///[WILDLINE]/main.ts:1:22 + +TS2307 [ERROR]: Cannot find module 'http://localhost:4545/remote.ts'. + at file:///[WILDLINE]/main.ts:2:24 + +Found 2 errors. diff --git a/tests/specs/check/module_not_found/main.ts b/tests/specs/check/module_not_found/main.ts new file mode 100644 index 0000000000..cec9512569 --- /dev/null +++ b/tests/specs/check/module_not_found/main.ts @@ -0,0 +1,5 @@ +import { Test } from "./other.js"; +import { Remote } from "http://localhost:4545/remote.ts"; + +console.log(new Test()); +console.log(new Remote()); diff --git a/tests/specs/check/module_not_found/missing_local_root.out b/tests/specs/check/module_not_found/missing_local_root.out new file mode 100644 index 0000000000..34b150c9a3 --- /dev/null +++ b/tests/specs/check/module_not_found/missing_local_root.out @@ -0,0 +1,2 @@ +Check file:///[WILDLINE]/non_existent.ts +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/non_existent.ts'. diff --git a/tests/specs/check/module_not_found/missing_remote_root.out b/tests/specs/check/module_not_found/missing_remote_root.out new file mode 100644 index 0000000000..e408938e41 --- /dev/null +++ b/tests/specs/check/module_not_found/missing_remote_root.out @@ -0,0 +1,3 @@ +Download http://localhost:4545/missing_non_existent.ts +Check http://localhost:4545/missing_non_existent.ts +error: TS2307 [ERROR]: Cannot find module 'http://localhost:4545/missing_non_existent.ts'. diff --git a/tests/specs/check/types_resolved_relative_config/main.out b/tests/specs/check/types_resolved_relative_config/main.out index 212e1224ca..5763d3298c 100644 --- a/tests/specs/check/types_resolved_relative_config/main.out +++ b/tests/specs/check/types_resolved_relative_config/main.out @@ -1,3 +1,4 @@ [# It should be resolving relative the config in sub_dir instead of the cwd] -error: Module not found "file:///[WILDLINE]/sub_dir/a.d.ts". +Check file:///[WILDLINE]/main.ts +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/sub_dir/a.d.ts'. at file:///[WILDLINE]/sub_dir/deno.json:1:1 diff --git a/tests/specs/publish/sloppy_imports/sloppy_imports_not_enabled.out b/tests/specs/publish/sloppy_imports/sloppy_imports_not_enabled.out index 4eacbea655..8388e4751e 100644 --- a/tests/specs/publish/sloppy_imports/sloppy_imports_not_enabled.out +++ b/tests/specs/publish/sloppy_imports/sloppy_imports_not_enabled.out @@ -1,2 +1,3 @@ -error: [WILDCARD] Maybe specify path to 'index.ts' file in directory instead or run with --unstable-sloppy-imports - at file:///[WILDCARD]/mod.ts:1:20 +Check file:///[WILDLINE]/mod.ts +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/b'. Maybe specify path to 'index.ts' file in directory instead or run with --unstable-sloppy-imports + at file:///[WILDLINE]/mod.ts:1:20 diff --git a/tests/specs/run/jsx_import_source/jsx_import_source_error.out b/tests/specs/run/jsx_import_source/jsx_import_source_error.out index 634a5b09ba..cb673c6bc9 100644 --- a/tests/specs/run/jsx_import_source/jsx_import_source_error.out +++ b/tests/specs/run/jsx_import_source/jsx_import_source_error.out @@ -1,2 +1,3 @@ -error: Module not found "file:///[WILDCARD]/nonexistent/jsx-runtime". - at file:///[WILDCARD]/jsx_import_source_no_pragma.tsx:1:1 +Check file:///[WILDLINE]/jsx_import_source_no_pragma.tsx +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDCARD]/nonexistent/jsx-runtime'. + at file:///[WILDLINE]/jsx_import_source_no_pragma.tsx:1:1 diff --git a/tests/specs/run/reference_types_error/reference_types_error.js.out b/tests/specs/run/reference_types_error/reference_types_error.js.out index 86055f3ac3..3f22354915 100644 --- a/tests/specs/run/reference_types_error/reference_types_error.js.out +++ b/tests/specs/run/reference_types_error/reference_types_error.js.out @@ -1,2 +1,3 @@ -error: Module not found "file:///[WILDCARD]/nonexistent.d.ts". - at file:///[WILDCARD]/reference_types_error.js:1:22 +Check file:///[WILDLINE]/reference_types_error.js +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/nonexistent.d.ts'. + at file:///[WILDLINE]/reference_types_error.js:1:22 diff --git a/tests/specs/run/reference_types_error_vendor_dir/reference_types_error.js.out b/tests/specs/run/reference_types_error_vendor_dir/reference_types_error.js.out index 86055f3ac3..3f22354915 100644 --- a/tests/specs/run/reference_types_error_vendor_dir/reference_types_error.js.out +++ b/tests/specs/run/reference_types_error_vendor_dir/reference_types_error.js.out @@ -1,2 +1,3 @@ -error: Module not found "file:///[WILDCARD]/nonexistent.d.ts". - at file:///[WILDCARD]/reference_types_error.js:1:22 +Check file:///[WILDLINE]/reference_types_error.js +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/nonexistent.d.ts'. + at file:///[WILDLINE]/reference_types_error.js:1:22 diff --git a/tests/specs/run/sloppy_imports/no_sloppy.out b/tests/specs/run/sloppy_imports/no_sloppy.out index d3a205e990..f28d9181ff 100644 --- a/tests/specs/run/sloppy_imports/no_sloppy.out +++ b/tests/specs/run/sloppy_imports/no_sloppy.out @@ -1,2 +1,26 @@ -error: Module not found "file:///[WILDCARD]/a.js". Maybe change the extension to '.ts' or run with --unstable-sloppy-imports +Check file:///[WILDLINE]/main.ts +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/a.js'. Maybe change the extension to '.ts' or run with --unstable-sloppy-imports at file:///[WILDLINE]/main.ts:1:20 + +TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/b'. Maybe add a '.js' extension or run with --unstable-sloppy-imports + at file:///[WILDLINE]/main.ts:2:20 + +TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/c'. Maybe add a '.mts' extension or run with --unstable-sloppy-imports + at file:///[WILDLINE]/main.ts:3:20 + +TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/d'. Maybe add a '.mjs' extension or run with --unstable-sloppy-imports + at file:///[WILDLINE]/main.ts:4:20 + +TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/e'. Maybe add a '.tsx' extension or run with --unstable-sloppy-imports + at file:///[WILDLINE]/main.ts:5:20 + +TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/e.js'. Maybe change the extension to '.tsx' or run with --unstable-sloppy-imports + at file:///[WILDLINE]/main.ts:6:21 + +TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/f'. Maybe add a '.jsx' extension or run with --unstable-sloppy-imports + at file:///[WILDLINE]/main.ts:7:20 + +TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/dir'. Maybe specify path to 'index.tsx' file in directory instead or run with --unstable-sloppy-imports + at file:///[WILDLINE]/main.ts:8:20 + +Found 8 errors. diff --git a/tests/specs/run/wasm_module/import_file_not_found/__test__.jsonc b/tests/specs/run/wasm_module/import_file_not_found/__test__.jsonc index a27fcfa82b..0141f9828c 100644 --- a/tests/specs/run/wasm_module/import_file_not_found/__test__.jsonc +++ b/tests/specs/run/wasm_module/import_file_not_found/__test__.jsonc @@ -1,5 +1,14 @@ { - "args": "--allow-import main.js", - "output": "main.out", - "exitCode": 1 + "tests": { + "run": { + "args": "--allow-import main.js", + "output": "main.out", + "exitCode": 1 + }, + "check": { + "args": "check --all --allow-import main.js", + "output": "check.out", + "exitCode": 1 + } + } } diff --git a/tests/specs/run/wasm_module/import_file_not_found/check.out b/tests/specs/run/wasm_module/import_file_not_found/check.out new file mode 100644 index 0000000000..59c052297c --- /dev/null +++ b/tests/specs/run/wasm_module/import_file_not_found/check.out @@ -0,0 +1,4 @@ +Download http://localhost:4545/wasm/math_with_import.wasm +Check file:///[WILDLINE]/main.js +error: TS2307 [ERROR]: Cannot find module 'file:///[WILDLINE]/local_math.ts'. + at http://localhost:4545/wasm/math_with_import.wasm:1:87 diff --git a/tests/specs/run/wasm_module/import_file_not_found/main.js b/tests/specs/run/wasm_module/import_file_not_found/main.js index 9ad66df35b..b55405bd31 100644 --- a/tests/specs/run/wasm_module/import_file_not_found/main.js +++ b/tests/specs/run/wasm_module/import_file_not_found/main.js @@ -1,3 +1,4 @@ +// @ts-check import { add, subtract, diff --git a/tests/specs/run/wasm_module/import_file_not_found/main.out b/tests/specs/run/wasm_module/import_file_not_found/main.out index 54343673f1..ed021b9a21 100644 --- a/tests/specs/run/wasm_module/import_file_not_found/main.out +++ b/tests/specs/run/wasm_module/import_file_not_found/main.out @@ -1,3 +1,3 @@ Download http://localhost:4545/wasm/math_with_import.wasm error: Module not found "file:///[WILDLINE]/local_math.ts". - at http://localhost:4545/wasm/math_with_import.wasm:1:8 + at http://localhost:4545/wasm/math_with_import.wasm:1:87 diff --git a/tests/specs/run/wasm_module/import_named_export_not_found/__test__.jsonc b/tests/specs/run/wasm_module/import_named_export_not_found/__test__.jsonc index a27fcfa82b..0141f9828c 100644 --- a/tests/specs/run/wasm_module/import_named_export_not_found/__test__.jsonc +++ b/tests/specs/run/wasm_module/import_named_export_not_found/__test__.jsonc @@ -1,5 +1,14 @@ { - "args": "--allow-import main.js", - "output": "main.out", - "exitCode": 1 + "tests": { + "run": { + "args": "--allow-import main.js", + "output": "main.out", + "exitCode": 1 + }, + "check": { + "args": "check --all --allow-import main.js", + "output": "check.out", + "exitCode": 1 + } + } } diff --git a/tests/specs/run/wasm_module/import_named_export_not_found/check.out b/tests/specs/run/wasm_module/import_named_export_not_found/check.out new file mode 100644 index 0000000000..d7cc2ea0fb --- /dev/null +++ b/tests/specs/run/wasm_module/import_named_export_not_found/check.out @@ -0,0 +1,9 @@ +Download http://localhost:4545/wasm/math_with_import.wasm +Check file:///[WILDLINE]/main.js +error: TS2305 [ERROR]: Module '"file:///[WILDLINE]/local_math.ts"' has no exported member '"add"'. + at http://localhost:4545/wasm/math_with_import.wasm:1:1 + +TS2305 [ERROR]: Module '"file:///[WILDLINE]/local_math.ts"' has no exported member '"subtract"'. + at http://localhost:4545/wasm/math_with_import.wasm:1:1 + +Found 2 errors. diff --git a/tests/specs/run/wasm_module/import_named_export_not_found/main.js b/tests/specs/run/wasm_module/import_named_export_not_found/main.js index 9ad66df35b..b55405bd31 100644 --- a/tests/specs/run/wasm_module/import_named_export_not_found/main.js +++ b/tests/specs/run/wasm_module/import_named_export_not_found/main.js @@ -1,3 +1,4 @@ +// @ts-check import { add, subtract, diff --git a/tests/testdata/check/import_non_existent.ts b/tests/testdata/check/import_non_existent.ts new file mode 100644 index 0000000000..ae511bca8a --- /dev/null +++ b/tests/testdata/check/import_non_existent.ts @@ -0,0 +1,5 @@ +import { Test } from "./non-existent-module.ts"; + +console.log(Test); + +export class Other {}