diff --git a/Cargo.lock b/Cargo.lock index d7bddb2a73..df9af4b355 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1432,7 +1432,7 @@ dependencies = [ [[package]] name = "deno_config" version = "0.39.3" -source = "git+https://github.com/denoland/deno_config.git?branch=compiler-options-from-workspace-member#81d8e844624a8f6b3961213d1480dcc923d08b11" +source = "git+https://github.com/denoland/deno_config.git?rev=81d8e844624a8f6b3961213d1480dcc923d08b11#81d8e844624a8f6b3961213d1480dcc923d08b11" dependencies = [ "anyhow", "deno_package_json", diff --git a/Cargo.toml b/Cargo.toml index a8f4e44160..a01f86aa94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ deno_core = { version = "0.323.0" } deno_bench_util = { version = "0.174.0", path = "./bench_util" } # TODO(nayeemrmn): Use proper version when https://github.com/denoland/deno_config/pull/143 lands! -deno_config = { git = "https://github.com/denoland/deno_config.git", branch = "compiler-options-from-workspace-member", features = ["workspace", "sync"] } +deno_config = { git = "https://github.com/denoland/deno_config.git", rev = "81d8e844624a8f6b3961213d1480dcc923d08b11", features = ["workspace", "sync"] } deno_lockfile = "=0.23.2" deno_media_type = { version = "0.2.0", features = ["module_specifier"] } deno_npm = "=0.26.0" diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 2782ea3acd..f78cd4edc6 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -811,7 +811,7 @@ pub struct ScopeOptions { pub struct CliOptions { // the source of the options is a detail the rest of the // application need not concern itself with, so keep these private - flags: Arc, + pub flags: Arc, initial_cwd: PathBuf, main_module_cell: std::sync::OnceLock>, maybe_node_modules_folder: Option, diff --git a/cli/factory.rs b/cli/factory.rs index 6937b750f9..e1611df14f 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -1,12 +1,15 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use crate::args::check_warn_tsconfig; +use crate::args::discover_npmrc_from_workspace; use crate::args::get_root_cert_store; use crate::args::CaData; +use crate::args::CliLockfile; use crate::args::CliOptions; use crate::args::DenoSubcommand; use crate::args::Flags; use crate::args::NpmInstallDepsProvider; +use crate::args::ScopeOptions; use crate::args::StorageKeyResolver; use crate::args::TsConfigType; use crate::cache::Caches; @@ -51,22 +54,30 @@ use crate::resolver::CliSloppyImportsResolver; use crate::resolver::NpmModuleLoader; use crate::resolver::SloppyImportsCachedFs; use crate::standalone::DenoCompileBinaryWriter; +use crate::tools::check::MaybeDiagnostics; 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::extract; use crate::util::file_watcher::WatcherCommunicator; use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::worker::CliMainWorkerFactory; use crate::worker::CliMainWorkerOptions; +use std::collections::BTreeSet; use std::path::PathBuf; +use deno_ast::ModuleSpecifier; use deno_cache_dir::npm::NpmCacheDir; +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; @@ -1052,3 +1063,134 @@ impl CliFactory { }) } } + +pub struct WorkspaceFileContainerEntry { + main_graph_container: Arc, + specifiers: Vec<(ModuleSpecifier, T)>, + snippet_file_specifiers: Option>, +} + +pub struct WorkspaceFileContainer { + entries: Vec>, +} + +impl WorkspaceFileContainer { + #[allow(clippy::type_complexity)] + pub async fn from_workspace_dirs_with_files( + mut workspace_dirs_with_files: Vec<(Arc, FilePatterns)>, + collect_files: fn( + FilePatterns, + Arc, + Arc, + ) -> std::pin::Pin< + Box, AnyError>>>, + >, + cli_options: &CliOptions, + check_doc: bool, + ) -> Result { + 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 entries = Vec::with_capacity(dir_count); + for (workspace_dir, files) in workspace_dirs_with_files { + let (npmrc, _) = discover_npmrc_from_workspace(&workspace_dir.workspace)?; + let lockfile = + CliLockfile::discover(&cli_options.flags, &workspace_dir.workspace)?; + 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(CliOptions::new( + cli_options.flags.clone(), + cli_options.initial_cwd().to_path_buf(), + lockfile.map(Arc::new), + npmrc, + workspace_dir, + false, + scope_options.map(Arc::new), + )?); + let factory = CliFactory::from_cli_options(cli_options.clone()); + let file_fetcher = factory.file_fetcher()?; + let main_graph_container = + factory.main_module_graph_container().await?.clone(); + let specifiers = + collect_files(files, cli_options, file_fetcher.clone()).await?; + let snippet_file_specifiers = if check_doc { + let root_permissions = factory.root_permissions_container()?; + let mut snippet_file_specifiers = Vec::new(); + for (s, _) in specifiers.iter() { + let file = file_fetcher.fetch(s, root_permissions).await?; + let snippet_files = extract::extract_snippet_files(file)?; + for snippet_file in snippet_files { + snippet_file_specifiers.push(snippet_file.specifier.clone()); + file_fetcher.insert_memory_files(snippet_file); + } + } + Some(snippet_file_specifiers) + } else { + None + }; + entries.push(WorkspaceFileContainerEntry { + main_graph_container, + specifiers, + snippet_file_specifiers, + }); + } + Ok(Self { entries }) + } + + pub async fn check(&self) -> Result<(), AnyError> { + let mut diagnostics = vec![]; + let mut all_errors = vec![]; + for entry in &self.entries { + let specifiers_for_typecheck = entry + .specifiers + .iter() + .map(|(s, _)| s) + .chain(entry.snippet_file_specifiers.iter().flatten()) + .cloned() + .collect::>(); + if specifiers_for_typecheck.is_empty() { + continue; + } + if let Err(err) = entry + .main_graph_container + .check_specifiers(&specifiers_for_typecheck, None) + .await + { + match err { + MaybeDiagnostics::Diagnostics(Diagnostics(d)) => { + diagnostics.extend(d) + } + MaybeDiagnostics::Other(err) => all_errors.push(err), + } + } + } + if !diagnostics.is_empty() { + all_errors.push(AnyError::from(Diagnostics(diagnostics))); + } + if !all_errors.is_empty() { + return Err(anyhow!( + "{}", + all_errors + .into_iter() + .map(|e| e.to_string()) + .collect::>() + .join("\n\n"), + )); + } + Ok(()) + } + + pub fn has_specifiers(&self) -> bool { + self.entries.iter().any(|e| !e.specifiers.is_empty()) + } +} diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 657d8c1f2c..53b89f21b7 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -1,7 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::collections::BTreeMap; -use std::collections::BTreeSet; use std::collections::HashSet; use std::collections::VecDeque; use std::error::Error; @@ -12,8 +11,8 @@ use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_config::glob::FilePatterns; use deno_config::glob::PathOrPattern; -use deno_core::anyhow::anyhow; use deno_core::error::AnyError; +use deno_core::futures::FutureExt; use deno_graph::Module; use deno_graph::ModuleGraph; use deno_runtime::deno_node::NodeResolver; @@ -22,14 +21,11 @@ use once_cell::sync::Lazy; use regex::Regex; use crate::args::check_warn_tsconfig; -use crate::args::discover_npmrc_from_workspace; use crate::args::CheckFlags; -use crate::args::CliLockfile; use crate::args::CliOptions; use crate::args::ConfigFlag; use crate::args::FileFlags; use crate::args::Flags; -use crate::args::ScopeOptions; use crate::args::TsConfig; use crate::args::TsConfigType; use crate::args::TsTypeLib; @@ -39,6 +35,7 @@ use crate::cache::Caches; use crate::cache::FastInsecureHasher; use crate::cache::TypeCheckCache; use crate::factory::CliFactory; +use crate::factory::WorkspaceFileContainer; use crate::graph_util::BuildFastCheckGraphOptions; use crate::graph_util::ModuleGraphBuilder; use crate::npm::CliNpmResolver; @@ -101,99 +98,27 @@ pub async fn check( .map(PathOrPattern::RemoteUrl), ); } - let all_scopes = Arc::new( - by_workspace_directory - .iter() - .filter(|(_, (d, _))| d.has_deno_or_pkg_json()) - .map(|(s, (_, _))| s.clone()) - .collect::>(), - ); - let dir_count = by_workspace_directory.len(); - let mut diagnostics = vec![]; - let mut all_errors = vec![]; - let mut found_specifiers = false; - for (dir_url, (workspace_directory, patterns)) in by_workspace_directory { - let (npmrc, _) = - discover_npmrc_from_workspace(&workspace_directory.workspace)?; - let lockfile = - CliLockfile::discover(&flags, &workspace_directory.workspace)?; - let scope_options = (dir_count > 1).then(|| ScopeOptions { - scope: workspace_directory - .has_deno_or_pkg_json() - .then(|| dir_url.clone()), - all_scopes: all_scopes.clone(), - }); - let cli_options = CliOptions::new( - flags.clone(), - cli_options.initial_cwd().to_path_buf(), - lockfile.map(Arc::new), - npmrc, - workspace_directory, - false, - scope_options.map(Arc::new), - )?; - let specifiers = collect_specifiers( - patterns, - cli_options.vendor_dir_path().map(ToOwned::to_owned), - |e| is_script_ext(e.path), - )?; - if specifiers.is_empty() { - continue; - } else { - found_specifiers = true; - } - let factory = CliFactory::from_cli_options(Arc::new(cli_options)); - let main_graph_container = factory.main_module_graph_container().await?; - let specifiers_for_typecheck = if check_flags.doc || check_flags.doc_only - { - let file_fetcher = factory.file_fetcher()?; - let root_permissions = factory.root_permissions_container()?; - let mut specifiers_for_typecheck = if check_flags.doc { - specifiers.clone() - } else { - vec![] - }; - for s in specifiers { - let file = file_fetcher.fetch(&s, root_permissions).await?; - let snippet_files = extract::extract_snippet_files(file)?; - for snippet_file in snippet_files { - specifiers_for_typecheck.push(snippet_file.specifier.clone()); - file_fetcher.insert_memory_files(snippet_file); - } + let container = WorkspaceFileContainer::from_workspace_dirs_with_files( + by_workspace_directory.into_values().collect(), + |patterns, cli_options, _| { + async move { + 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, ())).collect()) } - specifiers_for_typecheck - } else { - specifiers - }; - if let Err(err) = main_graph_container - .check_specifiers(&specifiers_for_typecheck, None) - .await - { - match err { - MaybeDiagnostics::Diagnostics(Diagnostics(d)) => { - diagnostics.extend(d) - } - MaybeDiagnostics::Other(err) => all_errors.push(err), - } - } - } - if !found_specifiers { + .boxed_local() + }, + cli_options, + check_flags.doc || check_flags.doc_only, + ) + .await?; + if !container.has_specifiers() { log::warn!("{} No matching files found.", colors::yellow("Warning")); } - if !diagnostics.is_empty() { - all_errors.push(AnyError::from(Diagnostics(diagnostics))); - } - if !all_errors.is_empty() { - return Err(anyhow!( - "{}", - all_errors - .into_iter() - .map(|e| e.to_string()) - .collect::>() - .join("\n\n"), - )); - } - return Ok(()); + return container.check().await; } let factory = CliFactory::from_flags(flags);