diff --git a/cli/args/mod.rs b/cli/args/mod.rs index c0176dbf3d..c5a0639849 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -8,7 +8,7 @@ mod lockfile; mod package_json; use std::borrow::Cow; -use std::collections::BTreeSet; +use std::collections::BTreeMap; use std::collections::HashMap; use std::env; use std::net::SocketAddr; @@ -557,15 +557,6 @@ struct CliOptionOverrides { import_map_specifier: Option>, } -/// Overrides for the options below that when set will -/// use these values over the values derived from the -/// CLI flags or config file. -#[derive(Debug, Clone)] -pub struct ScopeOptions { - pub scope: Option>, - pub all_scopes: Arc>>, -} - fn load_external_import_map( deno_json: &ConfigFile, ) -> Result, AnyError> { @@ -593,11 +584,10 @@ pub struct CliOptions { npmrc: Arc, maybe_lockfile: Option>, maybe_external_import_map: Option<(PathBuf, serde_json::Value)>, - sys: CliSys, overrides: CliOptionOverrides, pub start_dir: Arc, + pub all_dirs: BTreeMap, Arc>, pub deno_dir_provider: Arc, - pub scope_options: Option>, } impl CliOptions { @@ -611,7 +601,6 @@ impl CliOptions { start_dir: Arc, force_global_cache: bool, maybe_external_import_map: Option<(PathBuf, serde_json::Value)>, - scope_options: Option>, ) -> Result { if let Some(insecure_allowlist) = flags.unsafely_ignore_certificate_errors.as_ref() @@ -643,6 +632,9 @@ impl CliOptions { load_env_variables_from_env_file(flags.env_file.as_ref()); + let all_dirs = [(start_dir.dir_url().clone(), start_dir.clone())] + .into_iter() + .collect(); Ok(Self { flags, initial_cwd, @@ -653,9 +645,8 @@ impl CliOptions { main_module_cell: std::sync::OnceLock::new(), maybe_external_import_map, start_dir, + all_dirs, deno_dir_provider, - sys: sys.clone(), - scope_options, }) } @@ -752,39 +743,18 @@ impl CliOptions { Arc::new(start_dir), false, external_import_map, - None, ) } - pub fn with_new_start_dir_and_scope_options( - &self, - start_dir: Arc, - scope_options: Option, - ) -> Result { - let (npmrc, _) = discover_npmrc_from_workspace(&start_dir.workspace)?; - let external_import_map = - if let Some(deno_json) = start_dir.workspace.root_deno_json() { - load_external_import_map(deno_json)? - } else { - None - }; - let lockfile = CliLockfile::discover( - &self.sys, - &self.flags, - &start_dir.workspace, - external_import_map.as_ref().map(|(_, v)| v), - )?; - Self::new( - &self.sys, - self.flags.clone(), - self.initial_cwd().to_path_buf(), - lockfile.map(Arc::new), - npmrc, - start_dir, - false, - external_import_map, - scope_options.map(Arc::new), - ) + pub fn with_all_dirs( + self, + all_dirs: impl IntoIterator>, + ) -> Self { + let all_dirs = all_dirs + .into_iter() + .map(|d| (d.dir_url().clone(), d)) + .collect(); + Self { all_dirs, ..self } } /// This method is purposefully verbose to disourage its use. Do not use it diff --git a/cli/factory.rs b/cli/factory.rs index 4970929456..8dce24f97b 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -1,6 +1,5 @@ // Copyright 2018-2025 the Deno authors. MIT license. -use std::collections::BTreeSet; use std::collections::HashSet; use std::future::Future; use std::path::PathBuf; @@ -13,7 +12,6 @@ use deno_config::glob::FilePatterns; use deno_config::workspace::PackageJsonDepResolution; use deno_config::workspace::WorkspaceDirectory; use deno_config::workspace::WorkspaceResolver; -use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_core::futures::FutureExt; use deno_core::FeatureChecker; @@ -42,7 +40,6 @@ use deno_runtime::deno_fs::RealFs; use deno_runtime::deno_node::RealIsBuiltInNodeModuleChecker; use deno_runtime::deno_permissions::Permissions; use deno_runtime::deno_permissions::PermissionsContainer; -use deno_runtime::deno_permissions::PermissionsOptions; use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_runtime::deno_web::BlobStore; @@ -57,7 +54,6 @@ use crate::args::CliOptions; use crate::args::DenoSubcommand; use crate::args::Flags; use crate::args::NpmInstallDepsProvider; -use crate::args::ScopeOptions; use crate::args::TsConfigType; use crate::cache::Caches; use crate::cache::CodeCache; @@ -79,7 +75,6 @@ use crate::graph_util::ModuleGraphCreator; use crate::http_util::HttpClientProvider; use crate::module_loader::CliModuleLoaderFactory; use crate::module_loader::ModuleLoadPreparer; -use crate::module_loader::PrepareModuleLoadError; use crate::node::CliCjsCodeAnalyzer; use crate::node::CliNodeCodeTranslator; use crate::node::CliNodeResolver; @@ -105,12 +100,10 @@ use crate::resolver::CliSloppyImportsResolver; use crate::resolver::FoundPackageJsonDepFlag; use crate::standalone::binary::DenoCompileBinaryWriter; use crate::sys::CliSys; -use crate::tools::check::CheckError; use crate::tools::check::TypeChecker; use crate::tools::coverage::CoverageCollector; use crate::tools::lint::LintRuleProvider; use crate::tools::run::hmr::HmrRunner; -use crate::tsc::Diagnostics; use crate::tsc::TypeCheckingCjsTracker; use crate::util::file_watcher::WatcherCommunicator; use crate::util::fs::canonicalize_path; @@ -1247,15 +1240,96 @@ pub struct SpecifierInfo { pub check_doc: bool, } -pub struct WorkspaceDirFilesFactory { +pub struct CliFactoryWithWorkspaceFiles { + pub inner: CliFactory, + pub cli_options: Arc, specifiers: Vec<(ModuleSpecifier, SpecifierInfo)>, doc_snippet_specifiers: Vec, - cli_options: Arc, - cli_factory: CliFactory, - permissions_options: Deferred, + initial_cwd: PathBuf, } -impl WorkspaceDirFilesFactory { +impl CliFactoryWithWorkspaceFiles { + #[allow(clippy::type_complexity)] + pub async fn from_workspace_dirs_with_files( + mut workspace_dirs_with_files: Vec<(Arc, FilePatterns)>, + collect_specifiers: fn( + FilePatterns, + Arc, + Arc, + T, + ) -> std::pin::Pin< + Box< + dyn Future< + Output = Result, AnyError>, + >, + >, + >, + args: T, + extract_doc_files: Option Result, AnyError>>, + cli_options: CliOptions, + watcher_communicator: Option<&Arc>, + ) -> Result { + let cli_options = + Arc::new(cli_options.with_all_dirs( + workspace_dirs_with_files.iter().map(|(d, _)| d.clone()), + )); + let mut factory = CliFactory::from_cli_options(cli_options.clone()); + factory.watcher_communicator = watcher_communicator.cloned(); + let initial_cwd = cli_options.initial_cwd().to_path_buf(); + if let Some(watcher_communicator) = watcher_communicator { + let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); + } + workspace_dirs_with_files.sort_by_cached_key(|(d, _)| d.dir_url().clone()); + let mut specifiers = Vec::new(); + let mut doc_snippet_specifiers = Vec::new(); + for (_, files) in workspace_dirs_with_files { + if let Some(watcher_communicator) = watcher_communicator { + let _ = watcher_communicator.watch_paths( + files + .include + .iter() + .flat_map(|set| set.base_paths()) + .collect(), + ); + } + let file_fetcher = factory.file_fetcher()?; + let dir_specifiers = collect_specifiers( + files, + cli_options.clone(), + file_fetcher.clone(), + args.clone(), + ) + .await?; + if let Some(extract_doc_files) = extract_doc_files { + let root_permissions = factory.root_permissions_container()?; + for (s, _) in dir_specifiers.iter().filter(|(_, i)| i.check_doc) { + let file = file_fetcher.fetch(s, root_permissions).await?; + let snippet_files = extract_doc_files(file)?; + for snippet_file in snippet_files { + doc_snippet_specifiers.push(snippet_file.url.clone()); + file_fetcher.insert_memory_files(snippet_file); + } + } + } + specifiers.extend(dir_specifiers); + } + Ok(Self { + inner: factory, + cli_options, + specifiers, + doc_snippet_specifiers, + initial_cwd, + }) + } + + pub fn initial_cwd(&self) -> &PathBuf { + &self.initial_cwd + } + + pub fn found_specifiers(&self) -> bool { + !self.specifiers.is_empty() + } + pub fn checked_specifiers(&self) -> impl Iterator { self .specifiers @@ -1268,23 +1342,20 @@ impl WorkspaceDirFilesFactory { &self, canonicalized_dep_paths: &HashSet, ) -> Result, AnyError> { - let graph_kind = self - .cli_factory - .cli_options()? - .type_check_mode() - .as_graph_kind(); - let module_graph_creator = self.cli_factory.module_graph_creator().await?; - let specifiers = self.checked_specifiers().cloned().collect::>(); + let graph_kind = + self.inner.cli_options()?.type_check_mode().as_graph_kind(); + let module_graph_creator = self.inner.module_graph_creator().await?; + let specifiers = self.checked_specifiers().collect::>(); let graph = module_graph_creator .create_graph( graph_kind, - specifiers.clone(), + specifiers.iter().map(|&s| s.clone()).collect(), crate::graph_util::NpmCachingStrategy::Eager, ) .await?; module_graph_creator.graph_valid(&graph)?; - let dependent_specifiers = self - .checked_specifiers() + let dependent_specifiers = specifiers + .into_iter() .filter(|s| { let mut dependency_specifiers = graph.walk( std::iter::once(*s), @@ -1313,172 +1384,16 @@ impl WorkspaceDirFilesFactory { Ok(dependent_specifiers) } - pub fn permissions_options(&self) -> &PermissionsOptions { - self - .permissions_options - .get_or_init(|| self.cli_options.permissions_options()) - } - - pub fn permission_desc_parser( - &self, - ) -> Result<&Arc>, AnyError> { - self.cli_factory.permission_desc_parser() - } - - pub async fn create_cli_main_worker_factory( - &self, - ) -> Result { - self.cli_factory.create_cli_main_worker_factory().await - } -} - -pub struct WorkspaceFilesFactory { - dirs: Vec, - initial_cwd: PathBuf, -} - -impl WorkspaceFilesFactory { - #[allow(clippy::type_complexity)] - pub async fn from_workspace_dirs_with_files( - mut workspace_dirs_with_files: Vec<(Arc, FilePatterns)>, - collect_specifiers: fn( - FilePatterns, - Arc, - Arc, - T, - ) -> std::pin::Pin< - Box< - dyn Future< - Output = Result, AnyError>, - >, - >, - >, - args: T, - extract_doc_files: Option Result, AnyError>>, - cli_options: &CliOptions, - watcher_communicator: Option<&Arc>, - ) -> Result { - let initial_cwd = cli_options.initial_cwd().to_path_buf(); - if let Some(watcher_communicator) = watcher_communicator { - let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); - } - workspace_dirs_with_files.sort_by_cached_key(|(d, _)| d.dir_url().clone()); - let all_scopes = Arc::new( - workspace_dirs_with_files - .iter() - .filter(|(d, _)| d.has_deno_or_pkg_json()) - .map(|(d, _)| d.dir_url().clone()) - .collect::>(), - ); - let dir_count = workspace_dirs_with_files.len(); - let mut dirs = Vec::with_capacity(dir_count); - for (workspace_dir, files) in workspace_dirs_with_files { - if let Some(watcher_communicator) = watcher_communicator { - let _ = watcher_communicator.watch_paths( - files - .include - .iter() - .flat_map(|set| set.base_paths()) - .collect(), - ); - } - let scope_options = (dir_count > 1).then(|| ScopeOptions { - scope: workspace_dir - .has_deno_or_pkg_json() - .then(|| workspace_dir.dir_url().clone()), - all_scopes: all_scopes.clone(), - }); - let cli_options = Arc::new( - cli_options - .with_new_start_dir_and_scope_options(workspace_dir, scope_options)?, - ); - let mut factory = CliFactory::from_cli_options(cli_options.clone()); - factory.watcher_communicator = watcher_communicator.cloned(); - let file_fetcher = factory.file_fetcher()?; - let specifiers = collect_specifiers( - files, - cli_options.clone(), - file_fetcher.clone(), - args.clone(), - ) - .await?; - let mut doc_snippet_specifiers = vec![]; - if let Some(extract_doc_files) = extract_doc_files { - let root_permissions = factory.root_permissions_container()?; - for (s, _) in specifiers.iter().filter(|(_, i)| i.check_doc) { - let file = file_fetcher.fetch(s, root_permissions).await?; - let snippet_files = extract_doc_files(file)?; - for snippet_file in snippet_files { - doc_snippet_specifiers.push(snippet_file.url.clone()); - file_fetcher.insert_memory_files(snippet_file); - } - } - } - dirs.push(WorkspaceDirFilesFactory { - specifiers, - doc_snippet_specifiers, - cli_options, - cli_factory: factory, - permissions_options: Default::default(), - }); - } - Ok(Self { dirs, initial_cwd }) - } - - pub fn dirs(&self) -> &Vec { - &self.dirs - } - - pub fn initial_cwd(&self) -> &PathBuf { - &self.initial_cwd - } - - pub fn found_specifiers(&self) -> bool { - self.dirs.iter().any(|e| !e.specifiers.is_empty()) - } - pub async fn check(&self) -> Result<(), AnyError> { - let mut diagnostics = vec![]; - let mut all_errors = vec![]; - for entry in &self.dirs { - let main_graph_container = entry - .cli_factory - .main_module_graph_container() - .await? - .clone(); - let specifiers_for_typecheck = - entry.checked_specifiers().cloned().collect::>(); - if specifiers_for_typecheck.is_empty() { - continue; - } - let ext_flag = entry.cli_factory.cli_options()?.ext_flag().as_ref(); - if let Err(err) = main_graph_container - .check_specifiers(&specifiers_for_typecheck, ext_flag) - .await - { - match err { - PrepareModuleLoadError::Check(CheckError::Diagnostics( - Diagnostics(d), - )) => diagnostics.extend(d), - err => all_errors.push(err), - } - } + let main_graph_container = + self.inner.main_module_graph_container().await?.clone(); + let specifiers = self.checked_specifiers().cloned().collect::>(); + if specifiers.is_empty() { + return Ok(()); } - if !diagnostics.is_empty() { - all_errors.push(PrepareModuleLoadError::Check(CheckError::Diagnostics( - Diagnostics(diagnostics), - ))); - } - if !all_errors.is_empty() { - return Err(anyhow!( - "{}", - all_errors - .into_iter() - .map(|e| e.to_string()) - .collect::>() - .join("\n\n"), - )); - } - Ok(()) + let ext_flag = self.cli_options.ext_flag().as_ref(); + main_graph_container + .check_specifiers(&specifiers, ext_flag) + .await } } diff --git a/cli/graph_container.rs b/cli/graph_container.rs index b23fe61170..1fe30b47ab 100644 --- a/cli/graph_container.rs +++ b/cli/graph_container.rs @@ -13,7 +13,6 @@ use deno_runtime::deno_permissions::PermissionsContainer; use crate::args::CliOptions; use crate::module_loader::ModuleLoadPreparer; -use crate::module_loader::PrepareModuleLoadError; use crate::util::fs::collect_specifiers; use crate::util::path::is_script_ext; @@ -70,7 +69,7 @@ impl MainModuleGraphContainer { &self, specifiers: &[ModuleSpecifier], ext_overwrite: Option<&String>, - ) -> Result<(), PrepareModuleLoadError> { + ) -> Result<(), AnyError> { let mut graph_permit = self.acquire_update_permit().await; let graph = graph_permit.graph_mut(); self @@ -100,7 +99,7 @@ impl MainModuleGraphContainer { log::warn!("{} No matching files found.", colors::yellow("Warning")); } - Ok(self.check_specifiers(&specifiers, None).await?) + self.check_specifiers(&specifiers, None).await } pub fn collect_specifiers( diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 3662451f01..d14f0e9246 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -3666,7 +3666,6 @@ impl Inner { workspace, force_global_cache, None, - None, )?; let open_docs = self.documents.documents(DocumentsFilter::OpenDiagnosable); diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index a85ca90709..b245fa360f 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -39,8 +39,8 @@ use crate::args::CliOptions; use crate::args::Flags; use crate::colors; use crate::display::write_json_to_stdout; +use crate::factory::CliFactoryWithWorkspaceFiles; use crate::factory::SpecifierInfo; -use crate::factory::WorkspaceFilesFactory; use crate::ops; use crate::sys::CliSys; use crate::tools::test::format_test_error; @@ -284,57 +284,46 @@ async fn bench_specifier_inner( /// Test a collection of specifiers with test modes concurrently. async fn bench_specifiers( - workspace_files_factory: &WorkspaceFilesFactory, + factory: &CliFactoryWithWorkspaceFiles, changed_paths: Option<&HashSet>, options: BenchSpecifierOptions, ) -> Result<(), AnyError> { - let mut specifiers_with_services = vec![]; - for factory in workspace_files_factory.dirs() { - let worker_factory = - Arc::new(factory.create_cli_main_worker_factory().await?); - let permission_desc_parser = factory.permission_desc_parser()?; - let permissions = Arc::new(Permissions::from_options( - permission_desc_parser.as_ref(), - factory.permissions_options(), - )?); - let specifiers = if let Some(changed_paths) = changed_paths { - factory.dependent_checked_specifiers(changed_paths).await? - } else { - factory.checked_specifiers().collect() - }; - specifiers_with_services.extend(specifiers.into_iter().map(|s| { - ( - s.clone(), - worker_factory.clone(), - permission_desc_parser.clone(), - permissions.clone(), - ) - })); - } + let specifiers = if let Some(changed_paths) = changed_paths { + factory.dependent_checked_specifiers(changed_paths).await? + } else { + factory.checked_specifiers().collect() + }; + let worker_factory = + Arc::new(factory.inner.create_cli_main_worker_factory().await?); + let permission_desc_parser = factory.inner.permission_desc_parser()?; + let permissions = Permissions::from_options( + permission_desc_parser.as_ref(), + &factory.cli_options.permissions_options(), + )?; + let (sender, mut receiver) = unbounded_channel::(); let log_level = options.log_level; let option_for_handles = options.clone(); - let join_handles = specifiers_with_services.into_iter().map( - move |(specifier, worker_factory, permissions_desc_parser, permissions)| { - let permissions_container = PermissionsContainer::new( - permissions_desc_parser.clone(), - permissions.as_ref().clone(), + let join_handles = specifiers.into_iter().cloned().map(move |specifier| { + let worker_factory = worker_factory.clone(); + let permissions_container = PermissionsContainer::new( + permission_desc_parser.clone(), + permissions.clone(), + ); + let sender = sender.clone(); + let options = option_for_handles.clone(); + spawn_blocking(move || { + let future = bench_specifier( + worker_factory, + permissions_container, + specifier, + sender, + options.filter, ); - let sender = sender.clone(); - let options = option_for_handles.clone(); - spawn_blocking(move || { - let future = bench_specifier( - worker_factory, - permissions_container, - specifier, - sender, - options.filter, - ); - create_and_run_current_thread(future) - }) - }, - ); + create_and_run_current_thread(future) + }) + }); let join_stream = stream::iter(join_handles) .buffer_unordered(1) @@ -457,43 +446,42 @@ pub async fn run_benchmarks( .resolve_bench_options_for_members(&bench_flags)? .into_iter() .map(|(d, o)| (d, o.files)) - .collect(); - let workspace_files_factory = - WorkspaceFilesFactory::from_workspace_dirs_with_files( - workspace_dirs_with_files, - |patterns, cli_options, _, _| { - async move { - let info = SpecifierInfo { - check: true, - check_doc: false, - }; - collect_specifiers( - patterns, - cli_options.vendor_dir_path().map(ToOwned::to_owned), - is_supported_bench_path, - ) - .map(|s| s.into_iter().map(|s| (s, info)).collect()) - } - .boxed_local() - }, - (), - None, - &cli_options, - None, - ) - .await?; - if !workspace_files_factory.found_specifiers() { + .collect::>(); + let factory = CliFactoryWithWorkspaceFiles::from_workspace_dirs_with_files( + workspace_dirs_with_files, + |patterns, cli_options, _, _| { + async move { + let info = SpecifierInfo { + check: true, + check_doc: false, + }; + collect_specifiers( + patterns, + cli_options.vendor_dir_path().map(ToOwned::to_owned), + is_supported_bench_path, + ) + .map(|s| s.into_iter().map(|s| (s, info)).collect()) + } + .boxed_local() + }, + (), + None, + cli_options, + None, + ) + .await?; + if !factory.found_specifiers() { return Err(anyhow!("No test modules found")); } - workspace_files_factory.check().await?; + factory.check().await?; if bench_flags.no_run { return Ok(()); } bench_specifiers( - &workspace_files_factory, + &factory, None, BenchSpecifierOptions { filter: TestFilter::from_flag(&bench_flags.filter), @@ -531,8 +519,8 @@ pub async fn run_benchmarks_with_watch( .into_iter() .map(|(d, o)| (d, o.files)) .collect::>(); - let workspace_files_factory = - WorkspaceFilesFactory::from_workspace_dirs_with_files( + let factory = + CliFactoryWithWorkspaceFiles::from_workspace_dirs_with_files( workspace_dirs_with_files, |patterns, cli_options, _, _| { async move { @@ -551,19 +539,19 @@ pub async fn run_benchmarks_with_watch( }, (), None, - &cli_options, + cli_options, Some(&watcher_communicator), ) .await?; - workspace_files_factory.check().await?; + factory.check().await?; if bench_flags.no_run { return Ok(()); } bench_specifiers( - &workspace_files_factory, + &factory, changed_paths.map(|p| p.into_iter().collect()).as_ref(), BenchSpecifierOptions { filter: TestFilter::from_flag(&bench_flags.filter), diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 596cd886b6..aa29a9448d 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -1,5 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. +use std::collections::BTreeMap; use std::collections::HashSet; use std::collections::VecDeque; use std::sync::Arc; @@ -7,6 +8,7 @@ use std::sync::Arc; use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_config::deno_json; +use deno_config::workspace::WorkspaceDirectory; use deno_core::error::AnyError; use deno_core::futures::FutureExt; use deno_error::JsErrorBox; @@ -15,6 +17,7 @@ use deno_graph::ModuleError; use deno_graph::ModuleGraph; use deno_graph::ModuleLoadError; use deno_lib::util::hash::FastInsecureHasher; +use deno_path_util::url_from_directory_path; use deno_semver::npm::NpmPackageNvReference; use deno_terminal::colors; use once_cell::sync::Lazy; @@ -32,8 +35,8 @@ use crate::args::TypeCheckMode; use crate::cache::CacheDBHash; use crate::cache::Caches; use crate::cache::TypeCheckCache; +use crate::factory::CliFactoryWithWorkspaceFiles; use crate::factory::SpecifierInfo; -use crate::factory::WorkspaceFilesFactory; use crate::graph_util::maybe_additional_sloppy_imports_message; use crate::graph_util::BuildFastCheckGraphOptions; use crate::graph_util::ModuleGraphBuilder; @@ -59,34 +62,33 @@ pub async fn check( ignore: Default::default(), include: check_flags.files, })?; - let workspace_files_factory = - WorkspaceFilesFactory::from_workspace_dirs_with_files( - workspace_dirs_with_files, - |patterns, cli_options, _, (doc, doc_only)| { - async move { - let info = SpecifierInfo { - check: !doc_only, - check_doc: doc || doc_only, - }; - collect_specifiers( - patterns, - cli_options.vendor_dir_path().map(ToOwned::to_owned), - |e| is_script_ext(e.path), - ) - .map(|s| s.into_iter().map(|s| (s, info)).collect()) - } - .boxed_local() - }, - (check_flags.doc, check_flags.doc_only), - Some(extract_snippet_files), - &cli_options, - None, - ) - .await?; - if !workspace_files_factory.found_specifiers() { + let factory = CliFactoryWithWorkspaceFiles::from_workspace_dirs_with_files( + workspace_dirs_with_files, + |patterns, cli_options, _, (doc, doc_only)| { + async move { + let info = SpecifierInfo { + check: !doc_only, + check_doc: doc || doc_only, + }; + collect_specifiers( + patterns, + cli_options.vendor_dir_path().map(ToOwned::to_owned), + |e| is_script_ext(e.path), + ) + .map(|s| s.into_iter().map(|s| (s, info)).collect()) + } + .boxed_local() + }, + (check_flags.doc, check_flags.doc_only), + Some(extract_snippet_files), + cli_options, + None, + ) + .await?; + if !factory.found_specifiers() { log::warn!("{} No matching files found.", colors::yellow("Warning")); } - workspace_files_factory.check().await + factory.check().await } /// Options for performing a check of a module graph. Note that the decision to @@ -229,17 +231,6 @@ impl TypeChecker { } log::debug!("Type checking."); - let ts_config_result = self - .cli_options - .resolve_ts_config_for_emit(TsConfigType::Check { lib: options.lib })?; - if options.log_ignored_options { - check_warn_tsconfig(&ts_config_result); - } - - let type_check_mode = options.type_check_mode; - let ts_config = ts_config_result.ts_config; - 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 { @@ -251,125 +242,159 @@ impl TypeChecker { )?; } - let is_visible_diagnostic = |d: &tsc::Diagnostic| { - if self.is_remote_diagnostic(d) { - return type_check_mode == TypeCheckMode::All - && d.include_when_remote() - && self - .cli_options - .scope_options - .as_ref() - .map(|o| o.scope.is_none()) - .unwrap_or(true); - } - let Some(scope_options) = &self.cli_options.scope_options else { - return true; - }; - let Some(specifier) = d - .file_name - .as_ref() - .and_then(|s| ModuleSpecifier::parse(s).ok()) - else { - return true; - }; - if specifier.scheme() != "file" { - return true; - } - let scope = scope_options - .all_scopes - .iter() - .rfind(|s| specifier.as_str().starts_with(s.as_str())); - scope == scope_options.scope.as_ref() - }; - let TscRoots { - roots: root_names, - missing_diagnostics, - maybe_check_hash, - } = get_tsc_roots( - &self.sys, - &self.npm_resolver, - &self.node_resolver, - &graph, - check_js, - check_state_hash(&self.npm_resolver), - type_check_mode, - &ts_config, - ); + let graph = Arc::new(graph); - let missing_diagnostics = missing_diagnostics.filter(is_visible_diagnostic); + let mut all_dirs = self.cli_options.all_dirs.clone(); + let initial_cwd_url = + url_from_directory_path(self.cli_options.initial_cwd()) + .map_err(JsErrorBox::from_err)?; + let initial_workspace_dir_url = all_dirs + .keys() + .rfind(|s| initial_cwd_url.as_str().starts_with(s.as_str())) + .cloned() + .unwrap_or_else(|| { + all_dirs.insert( + self.cli_options.start_dir.dir_url().clone(), + self.cli_options.start_dir.clone(), + ); + self.cli_options.start_dir.dir_url().clone() + }); + let is_scoped = all_dirs.len() > 1; - 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."); - return Ok((graph.into(), Default::default())); + let mut diagnostics = Diagnostics::default(); + + for (dir_url, workspace_dir) in &all_dirs { + let is_initial_workspace_dir = *dir_url == initial_workspace_dir_url; + let ts_config_result = workspace_dir + .to_ts_config_for_emit(TsConfigType::Check { lib: options.lib })?; + if options.log_ignored_options { + check_warn_tsconfig(&ts_config_result); + } + let type_check_mode = options.type_check_mode; + let ts_config = ts_config_result.ts_config; + let cache = TypeCheckCache::new(self.caches.type_checking_cache_db()); + let check_js = ts_config.get_check_js(); + + let is_visible_diagnostic = |d: &tsc::Diagnostic| { + if self.is_remote_diagnostic(d) { + return type_check_mode == TypeCheckMode::All + && d.include_when_remote() + && !is_scoped; + } + let Some(specifier) = d + .file_name + .as_ref() + .and_then(|s| ModuleSpecifier::parse(s).ok()) + else { + return true; + }; + if specifier.scheme() != "file" { + return true; + } + let scope = all_dirs + .keys() + .rfind(|s| specifier.as_str().starts_with(s.as_str())); + scope + .map(|s| s == dir_url) + .unwrap_or(is_initial_workspace_dir) + }; + let TscRoots { + roots: root_names, + display_roots, + missing_diagnostics, + maybe_check_hash, + } = get_tsc_roots( + &self.sys, + &self.npm_resolver, + &self.node_resolver, + &graph, + check_js, + check_state_hash(&self.npm_resolver), + type_check_mode, + &ts_config, + &all_dirs, + &initial_workspace_dir_url, + is_scoped.then_some(dir_url.as_ref()), + ); + + let missing_diagnostics = + missing_diagnostics.filter(is_visible_diagnostic); + let has_missing_diagnostics = !missing_diagnostics.is_empty(); + diagnostics.extend(missing_diagnostics); + + if root_names.is_empty() { + continue; + } + + 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."); + continue; + } } } + + for root in &display_roots { + let root_str = root.as_str(); + log::info!( + "{} {}", + colors::green("Check"), + to_percent_decoded_str(root_str) + ); + } + + // 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. + let first_root = root_names[0].0.clone(); + let maybe_tsbuildinfo = if options.reload { + None + } else { + cache.get_tsbuildinfo(&first_root) + }; + // 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 tsconfig_hash_data = FastInsecureHasher::new_deno_versioned() + .write(&ts_config.as_bytes()) + .finish(); + let response = tsc::exec(tsc::Request { + config: ts_config, + debug: self.cli_options.log_level() == Some(log::Level::Debug), + graph: graph.clone(), + hash_data: tsconfig_hash_data, + maybe_npm: Some(tsc::RequestNpmState { + cjs_tracker: self.cjs_tracker.clone(), + node_resolver: self.node_resolver.clone(), + npm_resolver: self.npm_resolver.clone(), + }), + maybe_tsbuildinfo, + root_names, + check_mode: type_check_mode, + })?; + + let response_diagnostics = + response.diagnostics.filter(is_visible_diagnostic); + + if let Some(tsbuildinfo) = response.maybe_tsbuildinfo { + cache.set_tsbuildinfo(&first_root, &tsbuildinfo); + } + + if !has_missing_diagnostics && response_diagnostics.is_empty() { + if let Some(check_hash) = maybe_check_hash { + cache.add_check_hash(check_hash); + } + } + + diagnostics.extend(response_diagnostics); + + log::debug!("{}", response.stats); } - for root in &graph.roots { - let root_str = root.as_str(); - log::info!( - "{} {}", - colors::green("Check"), - to_percent_decoded_str(root_str) - ); - } - - // 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. - let maybe_tsbuildinfo = if options.reload { - None - } else { - cache.get_tsbuildinfo(&graph.roots[0]) - }; - // 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 tsconfig_hash_data = FastInsecureHasher::new_deno_versioned() - .write(&ts_config.as_bytes()) - .finish(); - 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: tsconfig_hash_data, - maybe_npm: Some(tsc::RequestNpmState { - cjs_tracker: self.cjs_tracker.clone(), - node_resolver: self.node_resolver.clone(), - npm_resolver: self.npm_resolver.clone(), - }), - maybe_tsbuildinfo, - root_names, - check_mode: type_check_mode, - })?; - - let response_diagnostics = - response.diagnostics.filter(is_visible_diagnostic); - - let mut diagnostics = missing_diagnostics; - diagnostics.extend(response_diagnostics); - diagnostics.apply_fast_check_source_maps(&graph); - if let Some(tsbuildinfo) = response.maybe_tsbuildinfo { - cache.set_tsbuildinfo(&graph.roots[0], &tsbuildinfo); - } - - if diagnostics.is_empty() { - if let Some(check_hash) = maybe_check_hash { - cache.add_check_hash(check_hash); - } - } - - log::debug!("{}", response.stats); - Ok((graph, diagnostics)) } @@ -390,6 +415,7 @@ impl TypeChecker { struct TscRoots { roots: Vec<(ModuleSpecifier, MediaType)>, + display_roots: Vec, missing_diagnostics: tsc::Diagnostics, maybe_check_hash: Option, } @@ -410,6 +436,9 @@ fn get_tsc_roots( npm_cache_state_hash: Option, type_check_mode: TypeCheckMode, ts_config: &TsConfig, + all_dirs: &BTreeMap, Arc>, + initial_workspace_dir_url: &ModuleSpecifier, + current_workspace_dir_url: Option<&ModuleSpecifier>, ) -> TscRoots { fn maybe_get_check_entry( module: &deno_graph::Module, @@ -495,6 +524,7 @@ fn get_tsc_roots( let mut result = TscRoots { roots: Vec::with_capacity(graph.specifiers_count()), + display_roots: Vec::with_capacity(graph.roots.len()), missing_diagnostics: Default::default(), maybe_check_hash: None, }; @@ -525,6 +555,16 @@ fn get_tsc_roots( // put in the global types first so that they're resolved before anything else for (referrer, import) in graph.imports.iter() { + if let Some(current_workspace_dir_url) = current_workspace_dir_url { + let scope = all_dirs + .keys() + .rfind(|s| referrer.as_str().starts_with(s.as_str())) + .map(|s| s.as_ref()) + .unwrap_or(initial_workspace_dir_url); + if scope != current_workspace_dir_url { + continue; + } + } for specifier in import .dependencies .values() @@ -556,6 +596,17 @@ fn get_tsc_roots( // then the roots for root in &graph.roots { + if let Some(current_workspace_dir_url) = current_workspace_dir_url { + let scope = all_dirs + .keys() + .rfind(|s| root.as_str().starts_with(s.as_str())) + .map(|s| s.as_ref()) + .unwrap_or(initial_workspace_dir_url); + if scope != current_workspace_dir_url { + continue; + } + } + result.display_roots.push(root.clone()); let specifier = graph.resolve(root); if seen.insert(specifier) { pending.push_back((specifier, false)); diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 7b598f09f1..7573efb709 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -73,8 +73,8 @@ use crate::args::TestFlags; use crate::args::TestReporterConfig; use crate::colors; use crate::display; +use crate::factory::CliFactoryWithWorkspaceFiles; use crate::factory::SpecifierInfo; -use crate::factory::WorkspaceFilesFactory; use crate::file_fetcher::CliFileFetcher; use crate::ops; use crate::sys::CliSys; @@ -1180,38 +1180,27 @@ static HAS_TEST_RUN_SIGINT_HANDLER: AtomicBool = AtomicBool::new(false); /// Test a collection of specifiers with test modes concurrently. async fn test_specifiers( - workspace_files_factory: &WorkspaceFilesFactory, + factory: &CliFactoryWithWorkspaceFiles, changed_paths: Option<&HashSet>, options: TestSpecifiersOptions, ) -> Result<(), AnyError> { - let mut specifiers_with_services = vec![]; - for factory in workspace_files_factory.dirs() { - let worker_factory = - Arc::new(factory.create_cli_main_worker_factory().await?); - let permission_desc_parser = factory.permission_desc_parser()?; - let permissions = Arc::new(Permissions::from_options( - permission_desc_parser.as_ref(), - factory.permissions_options(), - )?); - let specifiers = if let Some(changed_paths) = changed_paths { - factory.dependent_checked_specifiers(changed_paths).await? - } else { - factory.checked_specifiers().collect() - }; - specifiers_with_services.extend(specifiers.into_iter().map(|s| { - ( - s.clone(), - worker_factory.clone(), - permission_desc_parser.clone(), - permissions.clone(), - ) - })); - } + let mut specifiers = if let Some(changed_paths) = changed_paths { + factory.dependent_checked_specifiers(changed_paths).await? + } else { + factory.checked_specifiers().collect() + }; if let Some(seed) = options.specifier.shuffle { let mut rng = SmallRng::seed_from_u64(seed); - specifiers_with_services.sort_by_cached_key(|(s, ..)| s.to_string()); - specifiers_with_services.shuffle(&mut rng); + specifiers.sort(); + specifiers.shuffle(&mut rng); } + let worker_factory = + Arc::new(factory.inner.create_cli_main_worker_factory().await?); + let permission_desc_parser = factory.inner.permission_desc_parser()?; + let permissions = Permissions::from_options( + permission_desc_parser.as_ref(), + &factory.cli_options.permissions_options(), + )?; let (test_event_sender_factory, receiver) = create_test_event_channel(); let concurrent_jobs = options.concurrent_jobs; @@ -1225,27 +1214,26 @@ async fn test_specifiers( let reporter = get_test_reporter(&options); let fail_fast_tracker = FailFastTracker::new(options.fail_fast); - let join_handles = specifiers_with_services.into_iter().map( - move |(specifier, worker_factory, permission_desc_parser, permissions)| { - let permissions_container = PermissionsContainer::new( - permission_desc_parser, - permissions.as_ref().clone(), - ); - let worker_sender = test_event_sender_factory.worker(); - let fail_fast_tracker = fail_fast_tracker.clone(); - let specifier_options = options.specifier.clone(); - spawn_blocking(move || { - create_and_run_current_thread(test_specifier( - worker_factory, - permissions_container, - specifier, - worker_sender, - fail_fast_tracker, - specifier_options, - )) - }) - }, - ); + let join_handles = specifiers.into_iter().cloned().map(move |specifier| { + let worker_factory = worker_factory.clone(); + let permissions_container = PermissionsContainer::new( + permission_desc_parser.clone(), + permissions.clone(), + ); + let worker_sender = test_event_sender_factory.worker(); + let fail_fast_tracker = fail_fast_tracker.clone(); + let specifier_options = options.specifier.clone(); + spawn_blocking(move || { + create_and_run_current_thread(test_specifier( + worker_factory, + permissions_container, + specifier, + worker_sender, + fail_fast_tracker, + specifier_options, + )) + }) + }); let join_stream = stream::iter(join_handles) .buffer_unordered(concurrent_jobs.get()) @@ -1516,40 +1504,38 @@ pub async fn run_tests( .resolve_test_options_for_members(&test_flags)? .into_iter() .map(|(d, o)| (d, o.files)) - .collect(); - let workspace_files_factory = - WorkspaceFilesFactory::from_workspace_dirs_with_files( - workspace_dirs_with_files, - |patterns, cli_options, file_fetcher, doc| { - collect_specifiers_for_tests(patterns, cli_options, file_fetcher, doc) - .boxed_local() - }, - test_flags.doc, - Some(extract_doc_tests), - &cli_options, - None, - ) - .await?; - if !test_flags.permit_no_files && !workspace_files_factory.found_specifiers() - { + .collect::>(); + let factory = CliFactoryWithWorkspaceFiles::from_workspace_dirs_with_files( + workspace_dirs_with_files, + |patterns, cli_options, file_fetcher, doc| { + collect_specifiers_for_tests(patterns, cli_options, file_fetcher, doc) + .boxed_local() + }, + test_flags.doc, + Some(extract_doc_tests), + cli_options, + None, + ) + .await?; + if !test_flags.permit_no_files && !factory.found_specifiers() { return Err(anyhow!("No test modules found")); } - workspace_files_factory.check().await?; + factory.check().await?; if test_flags.no_run { return Ok(()); } - let initial_cwd = workspace_files_factory.initial_cwd(); + let initial_cwd = factory.initial_cwd(); test_specifiers( - &workspace_files_factory, + &factory, None, TestSpecifiersOptions { cwd: Url::from_directory_path(initial_cwd).map_err(|_| { anyhow!( "Unable to construct URL from the path of cwd: {}", - cli_options.initial_cwd().to_string_lossy(), + factory.cli_options.initial_cwd().to_string_lossy(), ) })?, concurrent_jobs: test_flags @@ -1612,8 +1598,8 @@ pub async fn run_tests_with_watch( .into_iter() .map(|(d, o)| (d, o.files)) .collect::>(); - let workspace_files_factory = - WorkspaceFilesFactory::from_workspace_dirs_with_files( + let factory = + CliFactoryWithWorkspaceFiles::from_workspace_dirs_with_files( workspace_dirs_with_files, |patterns, cli_options, file_fetcher, doc| { collect_specifiers_for_tests( @@ -1626,26 +1612,26 @@ pub async fn run_tests_with_watch( }, test_flags.doc, Some(extract_doc_tests), - &cli_options, + cli_options, Some(&watcher_communicator), ) .await?; - workspace_files_factory.check().await?; + factory.check().await?; if test_flags.no_run { return Ok(()); } - let initial_cwd = workspace_files_factory.initial_cwd(); + let initial_cwd = factory.initial_cwd(); test_specifiers( - &workspace_files_factory, + &factory, changed_paths.map(|p| p.into_iter().collect()).as_ref(), TestSpecifiersOptions { cwd: Url::from_directory_path(initial_cwd).map_err(|_| { anyhow!( "Unable to construct URL from the path of cwd: {}", - cli_options.initial_cwd().to_string_lossy(), + factory.cli_options.initial_cwd().to_string_lossy(), ) })?, concurrent_jobs: test_flags diff --git a/tests/specs/check/module_not_found/missing_local_root.out b/tests/specs/check/module_not_found/missing_local_root.out index 34b150c9a3..01667f3f78 100644 --- a/tests/specs/check/module_not_found/missing_local_root.out +++ b/tests/specs/check/module_not_found/missing_local_root.out @@ -1,2 +1 @@ -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 index e408938e41..99b4cca155 100644 --- a/tests/specs/check/module_not_found/missing_remote_root.out +++ b/tests/specs/check/module_not_found/missing_remote_root.out @@ -1,3 +1,2 @@ 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'.