diff --git a/Cargo.lock b/Cargo.lock index 7a6108b2e7..76b561c152 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,8 +1501,7 @@ dependencies = [ [[package]] name = "deno_config" version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a47412627aa0d08414eca0e8329128013ab70bdb2cdfdc5456c2214cf24c8f" +source = "git+https://github.com/denoland/deno_config.git?rev=39be71a5936221bf23e438c11cfcffe56ce54690#39be71a5936221bf23e438c11cfcffe56ce54690" dependencies = [ "boxed_error", "capacity_builder 0.5.0", @@ -1768,8 +1767,7 @@ dependencies = [ [[package]] name = "deno_graph" version = "0.87.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56d4eb4b7c81ae920b6d18c45a1866924f93110caee80bbbc362dc28143f2bb" +source = "git+https://github.com/denoland/deno_graph.git?rev=70ca7ebbb7fe9bb6cf35115ef9531aa58aadec55#70ca7ebbb7fe9bb6cf35115ef9531aa58aadec55" dependencies = [ "async-trait", "capacity_builder 0.5.0", @@ -3644,19 +3642,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generator" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" -dependencies = [ - "cfg-if", - "libc", - "log", - "rustversion", - "windows 0.58.0", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -3732,8 +3717,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata", + "regex-syntax", ] [[package]] @@ -4923,19 +4908,6 @@ dependencies = [ "serde", ] -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - [[package]] name = "lsp-types" version = "0.97.0" @@ -4990,15 +4962,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "matchit" version = "0.7.3" @@ -5111,20 +5074,21 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.10" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +checksum = "9e0d88686dc561d743b40de8269b26eaf0dc58781bde087b0984646602021d08" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", - "loom", + "once_cell", "parking_lot", - "portable-atomic", + "quanta", "rustc_version 0.4.0", "smallvec", "tagptr", "thiserror 1.0.64", + "triomphe", "uuid", ] @@ -5303,16 +5267,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.4" @@ -5570,12 +5524,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "p224" version = "0.13.2" @@ -5910,12 +5858,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" - [[package]] name = "powerfmt" version = "0.2.0" @@ -6133,6 +6075,21 @@ dependencies = [ "unicase", ] +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -6302,6 +6259,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" +[[package]] +name = "raw-cpuid" +version = "11.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "raw-window-handle" version = "0.6.1" @@ -6376,17 +6342,8 @@ checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -6397,15 +6354,9 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.3" @@ -7040,15 +6991,6 @@ dependencies = [ "keccak", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shell-escape" version = "0.1.5" @@ -8380,36 +8322,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] @@ -8671,12 +8583,6 @@ dependencies = [ "wtf8", ] -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "value-trait" version = "0.10.0" @@ -9006,7 +8912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b2b1bf557d947847a30eb73f79aa6cdb3eaf3ce02f5e9599438f77896a62b3c" dependencies = [ "thiserror 1.0.64", - "windows 0.52.0", + "windows", ] [[package]] @@ -9046,17 +8952,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", + "windows-core", "windows-targets 0.52.6", ] @@ -9069,60 +8965,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 4ee2abe993..0b91621304 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,12 +49,17 @@ edition = "2021" license = "MIT" repository = "https://github.com/denoland/deno" +# TODO(nayeemrmn): Use proper version when https://github.com/denoland/deno_graph/pull/565 lands! +[patch.crates-io] +deno_graph = { git = "https://github.com/denoland/deno_graph.git", rev = "70ca7ebbb7fe9bb6cf35115ef9531aa58aadec55" } + [workspace.dependencies] deno_ast = { version = "=0.44.0", features = ["transpiling"] } deno_core = { version = "0.331.0" } deno_bench_util = { version = "0.180.0", path = "./bench_util" } -deno_config = { version = "=0.45.0", features = ["workspace", "sync"] } +# 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", rev = "39be71a5936221bf23e438c11cfcffe56ce54690", features = ["workspace", "sync"] } deno_lockfile = "=0.24.0" deno_media_type = { version = "0.2.4", features = ["module_specifier"] } deno_npm = "=0.27.2" diff --git a/cli/args/flags.rs b/cli/args/flags.rs index f86aa50186..2c7c927940 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -1076,6 +1076,14 @@ impl Flags { Ok(PathOrPatternSet::default()) } } + + pub fn is_discovered_config(&self) -> bool { + match self.config_flag { + ConfigFlag::Discover => true, + ConfigFlag::Path(_) => false, + ConfigFlag::Disabled => false, + } + } } static ENV_VARIABLES_HELP: &str = cstr!( diff --git a/cli/args/mod.rs b/cli/args/mod.rs index e4f332a8bc..c5a0639849 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -8,10 +8,10 @@ mod lockfile; mod package_json; use std::borrow::Cow; +use std::collections::BTreeMap; use std::collections::HashMap; use std::env; use std::net::SocketAddr; -use std::num::NonZeroUsize; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -214,22 +214,6 @@ pub fn ts_config_to_transpile_and_emit_options( )) } -pub struct WorkspaceBenchOptions { - pub filter: Option, - pub json: bool, - pub no_run: bool, -} - -impl WorkspaceBenchOptions { - pub fn resolve(bench_flags: &BenchFlags) -> Self { - Self { - filter: bench_flags.filter.clone(), - json: bench_flags.json, - no_run: bench_flags.no_run, - } - } -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct BenchOptions { pub files: FilePatterns, @@ -325,41 +309,6 @@ fn resolve_fmt_options( options } -#[derive(Clone, Debug)] -pub struct WorkspaceTestOptions { - pub doc: bool, - pub no_run: bool, - pub fail_fast: Option, - pub permit_no_files: bool, - pub filter: Option, - pub shuffle: Option, - pub concurrent_jobs: NonZeroUsize, - pub trace_leaks: bool, - pub reporter: TestReporterConfig, - pub junit_path: Option, - pub hide_stacktraces: bool, -} - -impl WorkspaceTestOptions { - pub fn resolve(test_flags: &TestFlags) -> Self { - Self { - permit_no_files: test_flags.permit_no_files, - concurrent_jobs: test_flags - .concurrent_jobs - .unwrap_or_else(|| NonZeroUsize::new(1).unwrap()), - doc: test_flags.doc, - fail_fast: test_flags.fail_fast, - filter: test_flags.filter.clone(), - no_run: test_flags.no_run, - shuffle: test_flags.shuffle, - trace_leaks: test_flags.trace_leaks, - reporter: test_flags.reporter, - junit_path: test_flags.junit_path.clone(), - hide_stacktraces: test_flags.hide_stacktraces, - } - } -} - #[derive(Debug, Clone)] pub struct TestOptions { pub files: FilePatterns, @@ -608,6 +557,21 @@ struct CliOptionOverrides { import_map_specifier: Option>, } +fn load_external_import_map( + deno_json: &ConfigFile, +) -> Result, AnyError> { + if !deno_json.is_an_import_map() { + if let Some(path) = deno_json.to_import_map_path()? { + let contents = std::fs::read_to_string(&path).with_context(|| { + format!("Unable to read import map at '{}'", path.display()) + })?; + let map = serde_json::from_str(&contents)?; + return Ok(Some((path, map))); + } + } + Ok(None) +} + /// Holds the resolved options of many sources used by subcommands /// and provides some helper function for creating common objects. pub struct CliOptions { @@ -622,6 +586,7 @@ pub struct CliOptions { maybe_external_import_map: Option<(PathBuf, serde_json::Value)>, overrides: CliOptionOverrides, pub start_dir: Arc, + pub all_dirs: BTreeMap, Arc>, pub deno_dir_provider: Arc, } @@ -667,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, @@ -677,6 +645,7 @@ impl CliOptions { main_module_cell: std::sync::OnceLock::new(), maybe_external_import_map, start_dir, + all_dirs, deno_dir_provider, }) } @@ -749,21 +718,6 @@ impl CliOptions { let (npmrc, _) = discover_npmrc_from_workspace(&start_dir.workspace)?; - fn load_external_import_map( - deno_json: &ConfigFile, - ) -> Result, AnyError> { - if !deno_json.is_an_import_map() { - if let Some(path) = deno_json.to_import_map_path()? { - let contents = std::fs::read_to_string(&path).with_context(|| { - format!("Unable to read import map at '{}'", path.display()) - })?; - let map = serde_json::from_str(&contents)?; - return Ok(Some((path, map))); - } - } - Ok(None) - } - let external_import_map = if let Some(deno_json) = start_dir.workspace.root_deno_json() { load_external_import_map(deno_json)? @@ -792,6 +746,17 @@ impl CliOptions { ) } + 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 /// except in the factory structs. Instead, prefer specific methods on `CliOptions` /// that can take all sources of information into account (ex. config files or env vars). @@ -1127,7 +1092,7 @@ impl CliOptions { &self, config_type: TsConfigType, ) -> Result { - self.workspace().resolve_ts_config_for_emit(config_type) + self.start_dir.to_ts_config_for_emit(config_type) } pub fn resolve_inspector_server( @@ -1157,7 +1122,7 @@ impl CliOptions { &self, ) -> Result, serde_json::Error> { self - .workspace() + .start_dir .to_compiler_option_types() .map(|maybe_imports| { maybe_imports @@ -1177,12 +1142,22 @@ impl CliOptions { pub fn resolve_fmt_options_for_members( &self, fmt_flags: &FmtFlags, - ) -> Result, AnyError> { + ) -> Result, FmtOptions)>, AnyError> { let cli_arg_patterns = fmt_flags.files.as_file_patterns(self.initial_cwd())?; - let member_configs = self - .workspace() - .resolve_fmt_config_for_members(&cli_arg_patterns)?; + let member_configs = if self.flags.is_discovered_config() { + self + .workspace() + .resolve_fmt_config_for_members(&cli_arg_patterns)? + .into_iter() + .map(|(d, c)| (Arc::new(d), c)) + .collect::>() + } else { + vec![( + self.start_dir.clone(), + self.start_dir.to_fmt_config(cli_arg_patterns)?, + )] + }; let unstable = self.resolve_config_unstable_fmt_options(); let mut result = Vec::with_capacity(member_configs.len()); for (ctx, config) in member_configs { @@ -1208,15 +1183,46 @@ impl CliOptions { WorkspaceLintOptions::resolve(&lint_config, lint_flags) } + pub fn resolve_file_flags_for_members( + &self, + file_flags: &FileFlags, + ) -> Result, FilePatterns)>, AnyError> { + let cli_arg_patterns = file_flags.as_file_patterns(self.initial_cwd())?; + let member_patterns = if self.flags.is_discovered_config() { + self + .workspace() + .resolve_file_patterns_for_members(&cli_arg_patterns)? + .into_iter() + .map(|(d, p)| (Arc::new(d), p)) + .collect::>() + } else { + vec![( + self.start_dir.clone(), + self.start_dir.to_resolved_file_patterns(cli_arg_patterns)?, + )] + }; + Ok(member_patterns) + } + pub fn resolve_lint_options_for_members( &self, lint_flags: &LintFlags, - ) -> Result, AnyError> { + ) -> Result, LintOptions)>, AnyError> { let cli_arg_patterns = lint_flags.files.as_file_patterns(self.initial_cwd())?; - let member_configs = self - .workspace() - .resolve_lint_config_for_members(&cli_arg_patterns)?; + let member_configs = if self.flags.is_discovered_config() { + self + .workspace() + .resolve_lint_config_for_members(&cli_arg_patterns)? + .into_iter() + .map(|(d, c)| (Arc::new(d), c)) + .collect::>() + } else { + vec![( + self.start_dir.clone(), + self.start_dir.to_lint_config(cli_arg_patterns)?, + )] + }; let mut result = Vec::with_capacity(member_configs.len()); for (ctx, config) in member_configs { let options = LintOptions::resolve(config, lint_flags); @@ -1242,52 +1248,54 @@ impl CliOptions { }) } - pub fn resolve_workspace_test_options( - &self, - test_flags: &TestFlags, - ) -> WorkspaceTestOptions { - WorkspaceTestOptions::resolve(test_flags) - } - pub fn resolve_test_options_for_members( &self, test_flags: &TestFlags, - ) -> Result, AnyError> { + ) -> Result, TestOptions)>, AnyError> { let cli_arg_patterns = test_flags.files.as_file_patterns(self.initial_cwd())?; - let workspace_dir_configs = self - .workspace() - .resolve_test_config_for_members(&cli_arg_patterns)?; - let mut result = Vec::with_capacity(workspace_dir_configs.len()); - for (member_dir, config) in workspace_dir_configs { - let options = TestOptions::resolve(config, test_flags); - result.push((member_dir, options)); - } - Ok(result) - } - - pub fn resolve_workspace_bench_options( - &self, - bench_flags: &BenchFlags, - ) -> WorkspaceBenchOptions { - WorkspaceBenchOptions::resolve(bench_flags) + let member_options = if self.flags.is_discovered_config() { + self + .workspace() + .resolve_test_config_for_members(&cli_arg_patterns)? + .into_iter() + .map(|(d, c)| (Arc::new(d), TestOptions::resolve(c, test_flags))) + .collect::>() + } else { + vec![( + self.start_dir.clone(), + TestOptions::resolve( + self.start_dir.to_test_config(cli_arg_patterns)?, + test_flags, + ), + )] + }; + Ok(member_options) } pub fn resolve_bench_options_for_members( &self, bench_flags: &BenchFlags, - ) -> Result, AnyError> { + ) -> Result, BenchOptions)>, AnyError> { let cli_arg_patterns = bench_flags.files.as_file_patterns(self.initial_cwd())?; - let workspace_dir_configs = self - .workspace() - .resolve_bench_config_for_members(&cli_arg_patterns)?; - let mut result = Vec::with_capacity(workspace_dir_configs.len()); - for (member_dir, config) in workspace_dir_configs { - let options = BenchOptions::resolve(config, bench_flags); - result.push((member_dir, options)); - } - Ok(result) + let member_options = if self.flags.is_discovered_config() { + self + .workspace() + .resolve_bench_config_for_members(&cli_arg_patterns)? + .into_iter() + .map(|(d, c)| (Arc::new(d), BenchOptions::resolve(c, bench_flags))) + .collect::>() + } else { + vec![( + self.start_dir.clone(), + BenchOptions::resolve( + self.start_dir.to_bench_config(cli_arg_patterns)?, + bench_flags, + ), + )] + }; + Ok(member_options) } /// Vector of user script CLI arguments. @@ -1304,7 +1312,7 @@ impl CliOptions { } pub fn check_js(&self) -> bool { - self.workspace().check_js() + self.start_dir.check_js() } pub fn coverage_dir(&self) -> Option { diff --git a/cli/factory.rs b/cli/factory.rs index 91c5d07b75..292548f037 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -1,11 +1,16 @@ // Copyright 2018-2025 the Deno authors. MIT license. +use std::collections::HashSet; use std::future::Future; use std::path::PathBuf; use std::sync::Arc; +use deno_ast::ModuleSpecifier; +use deno_cache_dir::file_fetcher::File; 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::error::AnyError; use deno_core::futures::FutureExt; @@ -20,6 +25,7 @@ use deno_lib::npm::NpmRegistryReadPermissionCheckerMode; use deno_lib::worker::LibMainWorkerFactory; use deno_lib::worker::LibMainWorkerOptions; use deno_npm_cache::NpmCacheSetting; +use deno_path_util::url_to_file_path; use deno_resolver::cjs::IsCjsResolutionMode; use deno_resolver::npm::managed::ManagedInNpmPkgCheckerCreateOptions; use deno_resolver::npm::managed::NpmResolutionCell; @@ -100,6 +106,7 @@ use crate::tools::lint::LintRuleProvider; use crate::tools::run::hmr::HmrRunner; use crate::tsc::TypeCheckingCjsTracker; use crate::util::file_watcher::WatcherCommunicator; +use crate::util::fs::canonicalize_path; use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; @@ -1223,3 +1230,183 @@ impl CliFactory { }) } } + +#[derive(Debug, Copy, Clone)] +pub struct SpecifierInfo { + /// Include as an ES module. + pub include: bool, + /// Type check virtual modules from doc snippets. If this is set but `check` + /// is not, this may be a markdown file for example. + pub include_doc: bool, +} + +struct WorkspaceFile { + specifier: ModuleSpecifier, + doc_snippet_specifiers: Vec, + doc_only: bool, +} + +pub struct CliFactoryWithWorkspaceFiles { + pub inner: CliFactory, + pub cli_options: Arc, + workspace_files: Vec, +} + +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(); + 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 workspace_files = 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 specifiers = collect_specifiers( + files, + cli_options.clone(), + file_fetcher.clone(), + args.clone(), + ) + .await?; + workspace_files.reserve_exact(specifiers.len()); + for (specifier, info) in &specifiers { + let mut doc_snippet_specifiers = Vec::new(); + if info.include_doc { + if let Some(extract_doc_files) = extract_doc_files { + let root_permissions = factory.root_permissions_container()?; + let file = file_fetcher.fetch(specifier, 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); + } + } + } + workspace_files.push(WorkspaceFile { + specifier: specifier.clone(), + doc_snippet_specifiers, + doc_only: !info.include, + }) + } + } + Ok(Self { + inner: factory, + cli_options, + workspace_files, + }) + } + + pub fn found_specifiers(&self) -> bool { + !self.workspace_files.is_empty() + } + + pub fn specifiers(&self) -> impl Iterator { + self.workspace_files.iter().flat_map(|f| { + std::iter::once(&f.specifier) + .filter(|_| !f.doc_only) + .chain(f.doc_snippet_specifiers.iter()) + }) + } + + pub async fn dependent_specifiers( + &self, + canonicalized_dep_paths: &HashSet, + ) -> Result, AnyError> { + let graph_kind = + self.inner.cli_options()?.type_check_mode().as_graph_kind(); + let module_graph_creator = self.inner.module_graph_creator().await?; + let graph = module_graph_creator + .create_graph( + graph_kind, + self + .workspace_files + .iter() + .map(|f| f.specifier.clone()) + .collect(), + crate::graph_util::NpmCachingStrategy::Eager, + ) + .await?; + module_graph_creator.graph_valid(&graph)?; + let dependent_specifiers = self + .workspace_files + .iter() + .filter(|f| { + let mut dependency_specifiers = graph.walk( + std::iter::once(&f.specifier), + deno_graph::WalkOptions { + follow_dynamic: true, + kind: deno_graph::GraphKind::All, + prefer_fast_check_graph: true, + check_js: true, + }, + ); + while let Some((s, _)) = dependency_specifiers.next() { + if let Ok(path) = url_to_file_path(s) { + if let Ok(path) = canonicalize_path(&path) { + if canonicalized_dep_paths.contains(&path) { + return true; + } + } + } else { + // skip walking this remote module's dependencies + dependency_specifiers.skip_previous_dependencies(); + } + } + false + }) + .flat_map(|f| { + std::iter::once(&f.specifier) + .filter(|_| !f.doc_only) + .chain(f.doc_snippet_specifiers.iter()) + }) + .collect(); + Ok(dependent_specifiers) + } + + pub async fn check(&self) -> Result<(), AnyError> { + let main_graph_container = + self.inner.main_module_graph_container().await?.clone(); + let specifiers = self.specifiers().cloned().collect::>(); + if specifiers.is_empty() { + return 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_util.rs b/cli/graph_util.rs index e57fcf8a94..963092d254 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -1,6 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. -use std::collections::HashSet; +use std::collections::BTreeMap; use std::error::Error; use std::path::PathBuf; use std::sync::Arc; @@ -29,7 +29,6 @@ use deno_graph::ModuleLoadError; use deno_graph::ResolutionError; use deno_graph::SpecifierError; use deno_graph::WorkspaceFastCheckOption; -use deno_path_util::url_to_file_path; use deno_resolver::npm::DenoInNpmPackageChecker; use deno_resolver::sloppy_imports::SloppyImportsCachedFs; use deno_resolver::sloppy_imports::SloppyImportsResolutionKind; @@ -64,7 +63,6 @@ use crate::tools::check; use crate::tools::check::CheckError; use crate::tools::check::TypeChecker; use crate::util::file_watcher::WatcherCommunicator; -use crate::util::fs::canonicalize_path; #[derive(Clone)] pub struct GraphValidOptions { @@ -857,14 +855,22 @@ impl ModuleGraphBuilder { &self, ) -> Result { - let jsx_import_source_config = self + let jsx_import_source_config_unscoped = self .cli_options - .workspace() + .start_dir .to_maybe_jsx_import_source_config()?; + let mut jsx_import_source_config_by_scope = BTreeMap::default(); + for (dir_url, dir) in &self.cli_options.all_dirs { + let jsx_import_source_config_unscoped = + dir.to_maybe_jsx_import_source_config()?; + jsx_import_source_config_by_scope + .insert(dir_url.clone(), jsx_import_source_config_unscoped); + } Ok(CliGraphResolver { cjs_tracker: &self.cjs_tracker, resolver: &self.resolver, - jsx_import_source_config, + jsx_import_source_config_unscoped, + jsx_import_source_config_by_scope, }) } } @@ -1087,37 +1093,6 @@ fn get_import_prefix_missing_error(error: &ResolutionError) -> Option<&str> { maybe_specifier.map(|s| s.as_str()) } -/// Gets if any of the specified root's "file:" dependents are in the -/// provided changed set. -pub fn has_graph_root_local_dependent_changed( - graph: &ModuleGraph, - root: &ModuleSpecifier, - canonicalized_changed_paths: &HashSet, -) -> bool { - let mut dependent_specifiers = graph.walk( - std::iter::once(root), - deno_graph::WalkOptions { - follow_dynamic: true, - kind: GraphKind::All, - prefer_fast_check_graph: true, - check_js: true, - }, - ); - while let Some((s, _)) = dependent_specifiers.next() { - if let Ok(path) = url_to_file_path(s) { - if let Ok(path) = canonicalize_path(&path) { - if canonicalized_changed_paths.contains(&path) { - return true; - } - } - } else { - // skip walking this remote module's dependencies - dependent_specifiers.skip_previous_dependencies(); - } - } - false -} - #[derive(Clone, Debug)] pub struct FileWatcherReporter { watcher_communicator: Arc, @@ -1227,28 +1202,45 @@ fn format_deno_graph_error(err: &dyn Error) -> String { struct CliGraphResolver<'a> { cjs_tracker: &'a CliCjsTracker, resolver: &'a CliResolver, - jsx_import_source_config: Option, + jsx_import_source_config_unscoped: Option, + jsx_import_source_config_by_scope: + BTreeMap, Option>, } impl<'a> deno_graph::source::Resolver for CliGraphResolver<'a> { - fn default_jsx_import_source(&self) -> Option { + fn default_jsx_import_source( + &self, + referrer: &ModuleSpecifier, + ) -> Option { self - .jsx_import_source_config - .as_ref() + .jsx_import_source_config_by_scope + .iter() + .rfind(|(s, _)| referrer.as_str().starts_with(s.as_str())) + .map(|(_, c)| c.as_ref()) + .unwrap_or(self.jsx_import_source_config_unscoped.as_ref()) .and_then(|c| c.default_specifier.clone()) } - fn default_jsx_import_source_types(&self) -> Option { + fn default_jsx_import_source_types( + &self, + referrer: &ModuleSpecifier, + ) -> Option { self - .jsx_import_source_config - .as_ref() + .jsx_import_source_config_by_scope + .iter() + .rfind(|(s, _)| referrer.as_str().starts_with(s.as_str())) + .map(|(_, c)| c.as_ref()) + .unwrap_or(self.jsx_import_source_config_unscoped.as_ref()) .and_then(|c| c.default_types_specifier.clone()) } - fn jsx_import_source_module(&self) -> &str { + fn jsx_import_source_module(&self, referrer: &ModuleSpecifier) -> &str { self - .jsx_import_source_config - .as_ref() + .jsx_import_source_config_by_scope + .iter() + .rfind(|(s, _)| referrer.as_str().starts_with(s.as_str())) + .map(|(_, c)| c.as_ref()) + .unwrap_or(self.jsx_import_source_config_unscoped.as_ref()) .map(|c| c.module.as_str()) .unwrap_or(deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE) } diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index a0456c14ed..641e8ad237 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -1425,7 +1425,9 @@ impl ConfigData { ); let ts_config = LspTsConfig::new( - member_dir.workspace.root_deno_json().map(|c| c.as_ref()), + member_dir + .deno_json_for_compiler_options() + .map(|c| c.as_ref()), ); let deno_lint_config = @@ -1672,7 +1674,6 @@ impl ConfigData { ) -> Option { self .member_dir - .workspace .to_maybe_jsx_import_source_config() .ok() .flatten() diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index 1b393ad22b..cc768a4c0f 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -152,7 +152,7 @@ impl LspScopeResolver { let maybe_jsx_import_source_config = config_data.and_then(|d| d.maybe_jsx_import_source_config()); let graph_imports = config_data - .and_then(|d| d.member_dir.workspace.to_compiler_option_types().ok()) + .and_then(|d| d.member_dir.to_compiler_option_types().ok()) .map(|imports| { Arc::new( imports @@ -978,19 +978,25 @@ pub struct SingleReferrerGraphResolver<'a> { } impl<'a> deno_graph::source::Resolver for SingleReferrerGraphResolver<'a> { - fn default_jsx_import_source(&self) -> Option { + fn default_jsx_import_source( + &self, + _referrer: &ModuleSpecifier, + ) -> Option { self .jsx_import_source_config .and_then(|c| c.default_specifier.clone()) } - fn default_jsx_import_source_types(&self) -> Option { + fn default_jsx_import_source_types( + &self, + _referrer: &ModuleSpecifier, + ) -> Option { self .jsx_import_source_config .and_then(|c| c.default_types_specifier.clone()) } - fn jsx_import_source_module(&self) -> &str { + fn jsx_import_source_module(&self, _referrer: &ModuleSpecifier) -> &str { self .jsx_import_source_config .map(|c| c.module.as_str()) diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index a316e60b52..e18cb13183 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::path::Path; +use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -12,6 +13,7 @@ use deno_core::error::CoreError; use deno_core::error::JsError; use deno_core::futures::future; use deno_core::futures::stream; +use deno_core::futures::FutureExt; use deno_core::futures::StreamExt; use deno_core::serde_v8; use deno_core::unsync::spawn; @@ -22,7 +24,6 @@ use deno_core::PollEventLoopOptions; use deno_error::JsErrorBox; use deno_runtime::deno_permissions::Permissions; use deno_runtime::deno_permissions::PermissionsContainer; -use deno_runtime::permissions::RuntimePermissionDescriptorParser; use deno_runtime::tokio_util::create_and_run_current_thread; use deno_runtime::WorkerExecutionMode; use indexmap::IndexMap; @@ -34,11 +35,12 @@ use tokio::sync::mpsc::unbounded_channel; use tokio::sync::mpsc::UnboundedSender; use crate::args::BenchFlags; +use crate::args::CliOptions; use crate::args::Flags; use crate::colors; use crate::display::write_json_to_stdout; -use crate::factory::CliFactory; -use crate::graph_util::has_graph_root_local_dependent_changed; +use crate::factory::CliFactoryWithWorkspaceFiles; +use crate::factory::SpecifierInfo; use crate::ops; use crate::sys::CliSys; use crate::tools::test::format_test_error; @@ -282,20 +284,31 @@ async fn bench_specifier_inner( /// Test a collection of specifiers with test modes concurrently. async fn bench_specifiers( - worker_factory: Arc, - permissions: &Permissions, - permissions_desc_parser: &Arc>, - specifiers: Vec, + factory: &CliFactoryWithWorkspaceFiles, + changed_paths: Option<&HashSet>, options: BenchSpecifierOptions, ) -> Result<(), AnyError> { + let specifiers = if let Some(changed_paths) = changed_paths { + factory.dependent_specifiers(changed_paths).await? + } else { + factory.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.into_iter().map(move |specifier| { + let join_handles = specifiers.into_iter().cloned().map(move |specifier| { let worker_factory = worker_factory.clone(); let permissions_container = PermissionsContainer::new( - permissions_desc_parser.clone(), + permission_desc_parser.clone(), permissions.clone(), ); let sender = sender.clone(); @@ -427,59 +440,52 @@ pub async fn run_benchmarks( flags: Arc, bench_flags: BenchFlags, ) -> Result<(), AnyError> { - let factory = CliFactory::from_flags(flags); - let cli_options = factory.cli_options()?; - let workspace_bench_options = - cli_options.resolve_workspace_bench_options(&bench_flags); - // Various bench files should not share the same permissions in terms of - // `PermissionsContainer` - otherwise granting/revoking permissions in one - // file would have impact on other files, which is undesirable. - let permission_desc_parser = factory.permission_desc_parser()?.clone(); - let permissions = Permissions::from_options( - permission_desc_parser.as_ref(), - &cli_options.permissions_options(), - )?; - - let members_with_bench_options = - cli_options.resolve_bench_options_for_members(&bench_flags)?; - let specifiers = members_with_bench_options - .iter() - .map(|(_, bench_options)| { - collect_specifiers( - bench_options.files.clone(), - cli_options.vendor_dir_path().map(ToOwned::to_owned), - is_supported_bench_path, - ) - }) - .collect::, _>>()? + let log_level = flags.log_level; + let cli_options = CliOptions::from_flags(&CliSys::default(), flags)?; + let workspace_dirs_with_files = cli_options + .resolve_bench_options_for_members(&bench_flags)? .into_iter() - .flatten() + .map(|(d, o)| (d, o.files)) .collect::>(); - - if specifiers.is_empty() { - return Err(anyhow!("No bench modules found")); + let factory = CliFactoryWithWorkspaceFiles::from_workspace_dirs_with_files( + workspace_dirs_with_files, + |patterns, cli_options, _, _| { + async move { + let info = SpecifierInfo { + include: true, + include_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")); } - let main_graph_container = factory.main_module_graph_container().await?; - main_graph_container - .check_specifiers(&specifiers, cli_options.ext_flag().as_ref()) - .await?; + factory.check().await?; - if workspace_bench_options.no_run { + if bench_flags.no_run { return Ok(()); } - let log_level = cli_options.log_level(); - let worker_factory = - Arc::new(factory.create_cli_main_worker_factory().await?); bench_specifiers( - worker_factory, - &permissions, - &permission_desc_parser, - specifiers, + &factory, + None, BenchSpecifierOptions { - filter: TestFilter::from_flag(&workspace_bench_options.filter), - json: workspace_bench_options.json, + filter: TestFilter::from_flag(&bench_flags.filter), + json: bench_flags.json, log_level, }, ) @@ -488,7 +494,6 @@ pub async fn run_benchmarks( Ok(()) } -// TODO(bartlomieju): heavy duplication of code with `cli/tools/test.rs` pub async fn run_benchmarks_with_watch( flags: Arc, bench_flags: BenchFlags, @@ -507,110 +512,50 @@ pub async fn run_benchmarks_with_watch( let bench_flags = bench_flags.clone(); watcher_communicator.show_path_changed(changed_paths.clone()); Ok(async move { - let factory = CliFactory::from_flags_for_watcher( - flags, - watcher_communicator.clone(), - ); - let cli_options = factory.cli_options()?; - let workspace_bench_options = - cli_options.resolve_workspace_bench_options(&bench_flags); - - let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); - - let graph_kind = cli_options.type_check_mode().as_graph_kind(); - let module_graph_creator = factory.module_graph_creator().await?; - let members_with_bench_options = - cli_options.resolve_bench_options_for_members(&bench_flags)?; - let watch_paths = members_with_bench_options - .iter() - .filter_map(|(_, bench_options)| { - bench_options - .files - .include - .as_ref() - .map(|set| set.base_paths()) - }) - .flatten() - .collect::>(); - let _ = watcher_communicator.watch_paths(watch_paths); - let collected_bench_modules = members_with_bench_options - .iter() - .map(|(_, bench_options)| { - collect_specifiers( - bench_options.files.clone(), - cli_options.vendor_dir_path().map(ToOwned::to_owned), - is_supported_bench_path, - ) - }) - .collect::, _>>()? + let log_level: Option = flags.log_level; + let cli_options = CliOptions::from_flags(&CliSys::default(), flags)?; + let workspace_dirs_with_files = cli_options + .resolve_bench_options_for_members(&bench_flags)? .into_iter() - .flatten() + .map(|(d, o)| (d, o.files)) .collect::>(); - - // Various bench files should not share the same permissions in terms of - // `PermissionsContainer` - otherwise granting/revoking permissions in one - // file would have impact on other files, which is undesirable. - let permission_desc_parser = factory.permission_desc_parser()?.clone(); - let permissions = Permissions::from_options( - permission_desc_parser.as_ref(), - &cli_options.permissions_options(), - )?; - - let graph = module_graph_creator - .create_graph( - graph_kind, - collected_bench_modules.clone(), - crate::graph_util::NpmCachingStrategy::Eager, + let factory = + CliFactoryWithWorkspaceFiles::from_workspace_dirs_with_files( + workspace_dirs_with_files, + |patterns, cli_options, _, _| { + async move { + let info = SpecifierInfo { + include: true, + include_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, + Some(&watcher_communicator), ) .await?; - module_graph_creator.graph_valid(&graph)?; - let bench_modules = &graph.roots; - let bench_modules_to_reload = if let Some(changed_paths) = changed_paths - { - let changed_paths = changed_paths.into_iter().collect::>(); - let mut result = IndexSet::with_capacity(bench_modules.len()); - for bench_module_specifier in bench_modules { - if has_graph_root_local_dependent_changed( - &graph, - bench_module_specifier, - &changed_paths, - ) { - result.insert(bench_module_specifier.clone()); - } - } - result - } else { - bench_modules.clone() - }; + factory.check().await?; - let worker_factory = - Arc::new(factory.create_cli_main_worker_factory().await?); - - let specifiers = collected_bench_modules - .into_iter() - .filter(|specifier| bench_modules_to_reload.contains(specifier)) - .collect::>(); - - factory - .main_module_graph_container() - .await? - .check_specifiers(&specifiers, cli_options.ext_flag().as_ref()) - .await?; - - if workspace_bench_options.no_run { + if bench_flags.no_run { return Ok(()); } - let log_level = cli_options.log_level(); bench_specifiers( - worker_factory, - &permissions, - &permission_desc_parser, - specifiers, + &factory, + changed_paths.map(|p| p.into_iter().collect()).as_ref(), BenchSpecifierOptions { - filter: TestFilter::from_flag(&workspace_bench_options.filter), - json: workspace_bench_options.json, + filter: TestFilter::from_flag(&bench_flags.filter), + json: bench_flags.json, log_level, }, ) diff --git a/cli/tools/check.rs b/cli/tools/check.rs index d8128432b3..a5fa1abae3 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,13 +8,16 @@ 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; use deno_graph::Module; 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; @@ -22,6 +26,7 @@ use regex::Regex; use crate::args::check_warn_tsconfig; use crate::args::CheckFlags; use crate::args::CliOptions; +use crate::args::FileFlags; use crate::args::Flags; use crate::args::TsConfig; use crate::args::TsConfigType; @@ -30,7 +35,8 @@ use crate::args::TypeCheckMode; use crate::cache::CacheDBHash; use crate::cache::Caches; use crate::cache::TypeCheckCache; -use crate::factory::CliFactory; +use crate::factory::CliFactoryWithWorkspaceFiles; +use crate::factory::SpecifierInfo; use crate::graph_util::maybe_additional_sloppy_imports_message; use crate::graph_util::BuildFastCheckGraphOptions; use crate::graph_util::ModuleGraphBuilder; @@ -41,50 +47,48 @@ use crate::sys::CliSys; use crate::tsc; use crate::tsc::Diagnostics; use crate::tsc::TypeCheckingCjsTracker; -use crate::util::extract; +use crate::util::extract::extract_snippet_files; +use crate::util::fs::collect_specifiers; +use crate::util::path::is_script_ext; use crate::util::path::to_percent_decoded_str; pub async fn check( flags: Arc, check_flags: CheckFlags, ) -> Result<(), AnyError> { - let factory = CliFactory::from_flags(flags); - - let main_graph_container = factory.main_module_graph_container().await?; - - let specifiers = - main_graph_container.collect_specifiers(&check_flags.files)?; - if specifiers.is_empty() { + let cli_options = CliOptions::from_flags(&CliSys::default(), flags)?; + let workspace_dirs_with_files = + cli_options.resolve_file_flags_for_members(&FileFlags { + ignore: Default::default(), + include: check_flags.files, + })?; + let factory = CliFactoryWithWorkspaceFiles::from_workspace_dirs_with_files( + workspace_dirs_with_files, + |patterns, cli_options, _, (doc, doc_only)| { + async move { + let info = SpecifierInfo { + include: !doc_only, + include_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")); } - - 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.url.clone()); - file_fetcher.insert_memory_files(snippet_file); - } - } - - specifiers_for_typecheck - } else { - specifiers - }; - - main_graph_container - .check_specifiers(&specifiers_for_typecheck, None) - .await + factory.check().await } /// Options for performing a check of a module graph. Note that the decision to @@ -227,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 { @@ -249,103 +242,159 @@ impl TypeChecker { )?; } - 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 graph = Arc::new(graph); + + 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; + + 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 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 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 missing_diagnostics = - missing_diagnostics.filter(filter_remote_diagnostics); + 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()), + ); - 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 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(filter_remote_diagnostics); - - 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)) } @@ -366,6 +415,7 @@ impl TypeChecker { struct TscRoots { roots: Vec<(ModuleSpecifier, MediaType)>, + display_roots: Vec, missing_diagnostics: tsc::Diagnostics, maybe_check_hash: Option, } @@ -386,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, @@ -471,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, }; @@ -501,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() @@ -532,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/lint/mod.rs b/cli/tools/lint/mod.rs index 36ba85f613..49262d8a5c 100644 --- a/cli/tools/lint/mod.rs +++ b/cli/tools/lint/mod.rs @@ -206,7 +206,7 @@ async fn lint_with_watch( } struct PathsWithOptions { - dir: WorkspaceDirectory, + dir: Arc, paths: Vec, options: LintOptions, } @@ -276,7 +276,7 @@ impl WorkspaceLinter { cli_options: &Arc, lint_options: LintOptions, lint_config: DenoLintConfig, - member_dir: WorkspaceDirectory, + member_dir: Arc, paths: Vec, ) -> Result<(), AnyError> { self.file_count += paths.len(); diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index cb49a9de95..266a129016 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -12,6 +12,7 @@ use std::future::poll_fn; use std::io::Write; use std::num::NonZeroUsize; use std::path::Path; +use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; @@ -21,7 +22,6 @@ use std::time::Duration; use std::time::Instant; use deno_ast::MediaType; -use deno_cache_dir::file_fetcher::File; use deno_config::glob::FilePatterns; use deno_config::glob::WalkEntry; use deno_core::anyhow; @@ -54,7 +54,6 @@ use deno_runtime::deno_io::StdioPipe; use deno_runtime::deno_permissions::Permissions; use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::fmt_errors::format_js_error; -use deno_runtime::permissions::RuntimePermissionDescriptorParser; use deno_runtime::tokio_util::create_and_run_current_thread; use deno_runtime::worker::MainWorker; use deno_runtime::WorkerExecutionMode; @@ -74,9 +73,9 @@ use crate::args::TestFlags; use crate::args::TestReporterConfig; use crate::colors; use crate::display; -use crate::factory::CliFactory; +use crate::factory::CliFactoryWithWorkspaceFiles; +use crate::factory::SpecifierInfo; use crate::file_fetcher::CliFileFetcher; -use crate::graph_util::has_graph_root_local_dependent_changed; use crate::ops; use crate::sys::CliSys; use crate::util::extract::extract_doc_tests; @@ -141,32 +140,6 @@ fn get_sanitizer_item_ref( } } -/// The test mode is used to determine how a specifier is to be tested. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum TestMode { - /// Test as documentation, type-checking fenced code blocks. - Documentation, - /// Test as an executable module, loading the module into the isolate and running each test it - /// defines. - Executable, - /// Test as both documentation and an executable module. - Both, -} - -impl TestMode { - /// Returns `true` if the test mode indicates that code snippet extraction is - /// needed. - fn needs_test_extraction(&self) -> bool { - matches!(self, Self::Documentation | Self::Both) - } - - /// Returns `true` if the test mode indicates that the test should be - /// type-checked and run. - fn needs_test_run(&self) -> bool { - matches!(self, Self::Executable | Self::Both) - } -} - #[derive(Clone, Debug, Default)] pub struct TestFilter { pub substring: Option, @@ -1207,21 +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( - worker_factory: Arc, - permissions: &Permissions, - permission_desc_parser: &Arc>, - specifiers: Vec, + factory: &CliFactoryWithWorkspaceFiles, + changed_paths: Option<&HashSet>, options: TestSpecifiersOptions, ) -> Result<(), AnyError> { - let specifiers = if let Some(seed) = options.specifier.shuffle { + let mut specifiers = if let Some(changed_paths) = changed_paths { + factory.dependent_specifiers(changed_paths).await? + } else { + factory.specifiers().collect() + }; + if let Some(seed) = options.specifier.shuffle { let mut rng = SmallRng::seed_from_u64(seed); - let mut specifiers = specifiers; specifiers.sort(); specifiers.shuffle(&mut rng); - specifiers - } else { - specifiers - }; + } + 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; @@ -1235,7 +1214,7 @@ async fn test_specifiers( let reporter = get_test_reporter(&options); let fail_fast_tracker = FailFastTracker::new(options.fail_fast); - let join_handles = specifiers.into_iter().map(move |specifier| { + 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(), @@ -1467,175 +1446,111 @@ fn is_supported_test_ext(path: &Path) -> bool { } } -/// Collects specifiers marking them with the appropriate test mode while maintaining the natural -/// input order. -/// -/// - Specifiers matching the `is_supported_test_ext` predicate are marked as -/// `TestMode::Documentation`. -/// - Specifiers matching the `is_supported_test_path` are marked as `TestMode::Executable`. -/// - Specifiers matching both predicates are marked as `TestMode::Both` -fn collect_specifiers_with_test_mode( - cli_options: &CliOptions, - files: FilePatterns, - include_inline: &bool, -) -> Result, AnyError> { - // todo(dsherret): there's no need to collect twice as it's slow +pub async fn collect_specifiers_for_tests( + patterns: FilePatterns, + cli_options: Arc, + file_fetcher: Arc, + doc: bool, +) -> Result, AnyError> { let vendor_folder = cli_options.vendor_dir_path(); let module_specifiers = collect_specifiers( - files.clone(), + patterns.clone(), vendor_folder.map(ToOwned::to_owned), is_supported_test_path_predicate, )?; - - if *include_inline { - return collect_specifiers( - files, - vendor_folder.map(ToOwned::to_owned), - |e| is_supported_test_ext(e.path), - ) + let mut specifiers = if doc { + collect_specifiers(patterns, vendor_folder.map(ToOwned::to_owned), |e| { + is_supported_test_ext(e.path) + }) .map(|specifiers| { specifiers .into_iter() .map(|specifier| { - let mode = if module_specifiers.contains(&specifier) { - TestMode::Both - } else { - TestMode::Documentation + let info = SpecifierInfo { + include: module_specifiers.contains(&specifier), + include_doc: true, }; - - (specifier, mode) + (specifier, info) }) - .collect() - }); - } - - let specifiers_with_mode = module_specifiers - .into_iter() - .map(|specifier| (specifier, TestMode::Executable)) - .collect(); - - Ok(specifiers_with_mode) -} - -/// Collects module and document specifiers with test modes via -/// `collect_specifiers_with_test_mode` which are then pre-fetched and adjusted -/// based on the media type. -/// -/// Specifiers that do not have a known media type that can be executed as a -/// module are marked as `TestMode::Documentation`. Type definition files -/// cannot be run, and therefore need to be marked as `TestMode::Documentation` -/// as well. -async fn fetch_specifiers_with_test_mode( - cli_options: &CliOptions, - file_fetcher: &CliFileFetcher, - member_patterns: impl Iterator, - doc: &bool, -) -> Result, AnyError> { - let mut specifiers_with_mode = member_patterns - .map(|files| { - collect_specifiers_with_test_mode(cli_options, files.clone(), doc) - }) - .collect::, _>>()? - .into_iter() - .flatten() - .collect::>(); - - for (specifier, mode) in &mut specifiers_with_mode { + .collect::>() + })? + } else { + let info = SpecifierInfo { + include: true, + include_doc: false, + }; + module_specifiers + .into_iter() + .map(|specifier| (specifier, info)) + .collect::>() + }; + for (specifier, info) in &mut specifiers { let file = file_fetcher.fetch_bypass_permissions(specifier).await?; - let (media_type, _) = file.resolve_media_type_and_charset(); if matches!(media_type, MediaType::Unknown | MediaType::Dts) { - *mode = TestMode::Documentation + info.include = false; } } - - Ok(specifiers_with_mode) + Ok(specifiers) } pub async fn run_tests( flags: Arc, test_flags: TestFlags, ) -> Result<(), AnyError> { - let factory = CliFactory::from_flags(flags); - let cli_options = factory.cli_options()?; - let workspace_test_options = - cli_options.resolve_workspace_test_options(&test_flags); - let file_fetcher = factory.file_fetcher()?; - // Various test files should not share the same permissions in terms of - // `PermissionsContainer` - otherwise granting/revoking permissions in one - // file would have impact on other files, which is undesirable. - let permission_desc_parser = factory.permission_desc_parser()?; - let permissions = Permissions::from_options( - permission_desc_parser.as_ref(), - &cli_options.permissions_options(), - )?; - let log_level = cli_options.log_level(); - - let members_with_test_options = - cli_options.resolve_test_options_for_members(&test_flags)?; - let specifiers_with_mode = fetch_specifiers_with_test_mode( + let log_level = flags.log_level; + let cli_options = CliOptions::from_flags(&CliSys::default(), flags)?; + let workspace_dirs_with_files = cli_options + .resolve_test_options_for_members(&test_flags)? + .into_iter() + .map(|(d, o)| (d, o.files)) + .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, - file_fetcher, - members_with_test_options.into_iter().map(|(_, v)| v.files), - &workspace_test_options.doc, + None, ) .await?; - - if !workspace_test_options.permit_no_files && specifiers_with_mode.is_empty() - { + if !test_flags.permit_no_files && !factory.found_specifiers() { return Err(anyhow!("No test modules found")); } - let doc_tests = get_doc_tests(&specifiers_with_mode, file_fetcher).await?; - let specifiers_for_typecheck_and_test = - get_target_specifiers(specifiers_with_mode, &doc_tests); - for doc_test in doc_tests { - file_fetcher.insert_memory_files(doc_test); - } + factory.check().await?; - let main_graph_container = factory.main_module_graph_container().await?; - - // Typecheck - main_graph_container - .check_specifiers( - &specifiers_for_typecheck_and_test, - cli_options.ext_flag().as_ref(), - ) - .await?; - - if workspace_test_options.no_run { + if test_flags.no_run { return Ok(()); } - let worker_factory = - Arc::new(factory.create_cli_main_worker_factory().await?); - - // Run tests + let initial_cwd = factory.cli_options.initial_cwd(); test_specifiers( - worker_factory, - &permissions, - permission_desc_parser, - specifiers_for_typecheck_and_test, + &factory, + None, TestSpecifiersOptions { - cwd: Url::from_directory_path(cli_options.initial_cwd()).map_err( - |_| { - anyhow!( - "Unable to construct URL from the path of cwd: {}", - cli_options.initial_cwd().to_string_lossy(), - ) - }, - )?, - concurrent_jobs: workspace_test_options.concurrent_jobs, - fail_fast: workspace_test_options.fail_fast, + cwd: Url::from_directory_path(initial_cwd).map_err(|_| { + anyhow!( + "Unable to construct URL from the path of cwd: {}", + initial_cwd.to_string_lossy(), + ) + })?, + concurrent_jobs: test_flags + .concurrent_jobs + .unwrap_or_else(|| NonZeroUsize::new(1).unwrap()), + fail_fast: test_flags.fail_fast, log_level, - filter: workspace_test_options.filter.is_some(), - reporter: workspace_test_options.reporter, - junit_path: workspace_test_options.junit_path, - hide_stacktraces: workspace_test_options.hide_stacktraces, + filter: test_flags.filter.is_some(), + reporter: test_flags.reporter, + junit_path: test_flags.junit_path, + hide_stacktraces: test_flags.hide_stacktraces, specifier: TestSpecifierOptions { - filter: TestFilter::from_flag(&workspace_test_options.filter), - shuffle: workspace_test_options.shuffle, - trace_leaks: workspace_test_options.trace_leaks, + filter: TestFilter::from_flag(&test_flags.filter), + shuffle: test_flags.shuffle, + trace_leaks: test_flags.trace_leaks, }, }, ) @@ -1676,148 +1591,62 @@ pub async fn run_tests_with_watch( let test_flags = test_flags.clone(); watcher_communicator.show_path_changed(changed_paths.clone()); Ok(async move { - let factory = CliFactory::from_flags_for_watcher( - flags, - watcher_communicator.clone(), - ); - let cli_options = factory.cli_options()?; - let workspace_test_options = - cli_options.resolve_workspace_test_options(&test_flags); - - let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); - let graph_kind = cli_options.type_check_mode().as_graph_kind(); - let log_level = cli_options.log_level(); - let cli_options = cli_options.clone(); - let module_graph_creator = factory.module_graph_creator().await?; - let file_fetcher = factory.file_fetcher()?; - let members_with_test_options = - cli_options.resolve_test_options_for_members(&test_flags)?; - let watch_paths = members_with_test_options - .iter() - .filter_map(|(_, test_options)| { - test_options - .files - .include - .as_ref() - .map(|set| set.base_paths()) - }) - .flatten() - .collect::>(); - let _ = watcher_communicator.watch_paths(watch_paths); - let test_modules = members_with_test_options - .iter() - .map(|(_, test_options)| { - collect_specifiers( - test_options.files.clone(), - cli_options.vendor_dir_path().map(ToOwned::to_owned), - if workspace_test_options.doc { - Box::new(|e: WalkEntry| is_supported_test_ext(e.path)) - as Box bool> - } else { - Box::new(is_supported_test_path_predicate) - }, - ) - }) - .collect::, _>>()? + let log_level = flags.log_level; + let cli_options = CliOptions::from_flags(&CliSys::default(), flags)?; + let workspace_dirs_with_files = cli_options + .resolve_test_options_for_members(&test_flags)? .into_iter() - .flatten() + .map(|(d, o)| (d, o.files)) .collect::>(); - - let permission_desc_parser = factory.permission_desc_parser()?; - let permissions = Permissions::from_options( - permission_desc_parser.as_ref(), - &cli_options.permissions_options(), - )?; - let graph = module_graph_creator - .create_graph( - graph_kind, - test_modules, - crate::graph_util::NpmCachingStrategy::Eager, - ) - .await?; - module_graph_creator.graph_valid(&graph)?; - let test_modules = &graph.roots; - - let test_modules_to_reload = if let Some(changed_paths) = changed_paths - { - let mut result = IndexSet::with_capacity(test_modules.len()); - let changed_paths = changed_paths.into_iter().collect::>(); - for test_module_specifier in test_modules { - if has_graph_root_local_dependent_changed( - &graph, - test_module_specifier, - &changed_paths, - ) { - result.insert(test_module_specifier.clone()); - } - } - result - } else { - test_modules.clone() - }; - - let specifiers_with_mode = fetch_specifiers_with_test_mode( - &cli_options, - file_fetcher, - members_with_test_options.into_iter().map(|(_, v)| v.files), - &workspace_test_options.doc, - ) - .await? - .into_iter() - .filter(|(specifier, _)| test_modules_to_reload.contains(specifier)) - .collect::>(); - - let doc_tests = - get_doc_tests(&specifiers_with_mode, file_fetcher).await?; - let specifiers_for_typecheck_and_test = - get_target_specifiers(specifiers_with_mode, &doc_tests); - for doc_test in doc_tests { - file_fetcher.insert_memory_files(doc_test); - } - - let main_graph_container = - factory.main_module_graph_container().await?; - - // Typecheck - main_graph_container - .check_specifiers( - &specifiers_for_typecheck_and_test, - cli_options.ext_flag().as_ref(), + 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, + Some(&watcher_communicator), ) .await?; - if workspace_test_options.no_run { + factory.check().await?; + + if test_flags.no_run { return Ok(()); } - let worker_factory = - Arc::new(factory.create_cli_main_worker_factory().await?); - + let initial_cwd = factory.cli_options.initial_cwd(); test_specifiers( - worker_factory, - &permissions, - permission_desc_parser, - specifiers_for_typecheck_and_test, + &factory, + changed_paths.map(|p| p.into_iter().collect()).as_ref(), TestSpecifiersOptions { - cwd: Url::from_directory_path(cli_options.initial_cwd()).map_err( - |_| { - anyhow!( - "Unable to construct URL from the path of cwd: {}", - cli_options.initial_cwd().to_string_lossy(), - ) - }, - )?, - concurrent_jobs: workspace_test_options.concurrent_jobs, - fail_fast: workspace_test_options.fail_fast, + cwd: Url::from_directory_path(initial_cwd).map_err(|_| { + anyhow!( + "Unable to construct URL from the path of cwd: {}", + initial_cwd.to_string_lossy(), + ) + })?, + concurrent_jobs: test_flags + .concurrent_jobs + .unwrap_or_else(|| NonZeroUsize::new(1).unwrap()), + fail_fast: test_flags.fail_fast, log_level, - filter: workspace_test_options.filter.is_some(), - reporter: workspace_test_options.reporter, - junit_path: workspace_test_options.junit_path, - hide_stacktraces: workspace_test_options.hide_stacktraces, + filter: test_flags.filter.is_some(), + reporter: test_flags.reporter, + junit_path: test_flags.junit_path, + hide_stacktraces: test_flags.hide_stacktraces, specifier: TestSpecifierOptions { - filter: TestFilter::from_flag(&workspace_test_options.filter), - shuffle: workspace_test_options.shuffle, - trace_leaks: workspace_test_options.trace_leaks, + filter: TestFilter::from_flag(&test_flags.filter), + shuffle: test_flags.shuffle, + trace_leaks: test_flags.trace_leaks, }, }, ) @@ -1832,38 +1661,6 @@ pub async fn run_tests_with_watch( Ok(()) } -/// Extracts doc tests from files specified by the given specifiers. -async fn get_doc_tests( - specifiers_with_mode: &[(Url, TestMode)], - file_fetcher: &CliFileFetcher, -) -> Result, AnyError> { - let specifiers_needing_extraction = specifiers_with_mode - .iter() - .filter(|(_, mode)| mode.needs_test_extraction()) - .map(|(s, _)| s); - - let mut doc_tests = Vec::new(); - for s in specifiers_needing_extraction { - let file = file_fetcher.fetch_bypass_permissions(s).await?; - doc_tests.extend(extract_doc_tests(file)?); - } - - Ok(doc_tests) -} - -/// Get a list of specifiers that we need to perform typecheck and run tests on. -/// The result includes "pseudo specifiers" for doc tests. -fn get_target_specifiers( - specifiers_with_mode: Vec<(Url, TestMode)>, - doc_tests: &[File], -) -> Vec { - specifiers_with_mode - .into_iter() - .filter_map(|(s, mode)| mode.needs_test_run().then_some(s)) - .chain(doc_tests.iter().map(|d| d.url.clone())) - .collect() -} - /// Tracks failures for the `--fail-fast` argument in /// order to tell when to stop running tests. #[derive(Clone, Default)] diff --git a/cli/tsc/diagnostics.rs b/cli/tsc/diagnostics.rs index 3780f65e77..457f1ad60b 100644 --- a/cli/tsc/diagnostics.rs +++ b/cli/tsc/diagnostics.rs @@ -322,7 +322,7 @@ impl fmt::Display for Diagnostic { #[derive(Clone, Debug, Default, Eq, PartialEq, deno_error::JsError)] #[class(generic)] -pub struct Diagnostics(Vec); +pub struct Diagnostics(pub Vec); impl Diagnostics { #[cfg(test)] diff --git a/tests/specs/check/check_workspace/__test__.jsonc b/tests/specs/check/check_workspace/__test__.jsonc new file mode 100644 index 0000000000..08f50df7db --- /dev/null +++ b/tests/specs/check/check_workspace/__test__.jsonc @@ -0,0 +1,14 @@ +{ + "tests": { + "discover": { + "args": "check --quiet main.ts member/mod.ts", + "output": "check_discover.out", + "exitCode": 1 + }, + "config_flag": { + "args": "check --quiet --config deno.json main.ts member/mod.ts", + "output": "check_config_flag.out", + "exitCode": 1 + } + } +} diff --git a/tests/specs/check/check_workspace/check_config_flag.out b/tests/specs/check/check_workspace/check_config_flag.out new file mode 100644 index 0000000000..a586465d80 --- /dev/null +++ b/tests/specs/check/check_workspace/check_config_flag.out @@ -0,0 +1,11 @@ +error: TS2304 [ERROR]: Cannot find name 'onmessage'. +onmessage; +~~~~~~~~~ + at file:///[WILDCARD]/main.ts:8:1 + +TS2304 [ERROR]: Cannot find name 'onmessage'. +onmessage; +~~~~~~~~~ + at file:///[WILDCARD]/member/mod.ts:5:1 + +Found 2 errors. diff --git a/tests/specs/check/check_workspace/check_discover.out b/tests/specs/check/check_workspace/check_discover.out new file mode 100644 index 0000000000..9a8ce47280 --- /dev/null +++ b/tests/specs/check/check_workspace/check_discover.out @@ -0,0 +1,11 @@ +error: TS2304 [ERROR]: Cannot find name 'onmessage'. +onmessage; +~~~~~~~~~ + at file:///[WILDCARD]/main.ts:8:1 + +TS2304 [ERROR]: Cannot find name 'localStorage'. +localStorage; +~~~~~~~~~~~~ + at file:///[WILDCARD]/member/mod.ts:2:1 + +Found 2 errors. diff --git a/tests/specs/check/check_workspace/deno.json b/tests/specs/check/check_workspace/deno.json new file mode 100644 index 0000000000..c914638468 --- /dev/null +++ b/tests/specs/check/check_workspace/deno.json @@ -0,0 +1,3 @@ +{ + "workspace": ["member"] +} diff --git a/tests/specs/check/check_workspace/main.ts b/tests/specs/check/check_workspace/main.ts new file mode 100644 index 0000000000..713e64febe --- /dev/null +++ b/tests/specs/check/check_workspace/main.ts @@ -0,0 +1,8 @@ +// We shouldn't get diagnostics from this import under this check scope. +import "./member/mod.ts"; + +// Only defined for window. +localStorage; + +// Only defined for worker. +onmessage; diff --git a/tests/specs/check/check_workspace/member/deno.json b/tests/specs/check/check_workspace/member/deno.json new file mode 100644 index 0000000000..00feda1e44 --- /dev/null +++ b/tests/specs/check/check_workspace/member/deno.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "lib": ["deno.worker"] + } +} diff --git a/tests/specs/check/check_workspace/member/mod.ts b/tests/specs/check/check_workspace/member/mod.ts new file mode 100644 index 0000000000..846c13a74a --- /dev/null +++ b/tests/specs/check/check_workspace/member/mod.ts @@ -0,0 +1,5 @@ +// Only defined for window. +localStorage; + +// Only defined for worker. +onmessage; 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'. diff --git a/tests/specs/coverage/filter_doc_testing_urls/test_coverage.out b/tests/specs/coverage/filter_doc_testing_urls/test_coverage.out index 65548061a1..c553b804ba 100644 --- a/tests/specs/coverage/filter_doc_testing_urls/test_coverage.out +++ b/tests/specs/coverage/filter_doc_testing_urls/test_coverage.out @@ -1,9 +1,9 @@ -Check [WILDCARD]/test.ts Check [WILDCARD]/source.ts$[WILDCARD].ts -running 1 test from ./test.ts -add() ... ok ([WILDCARD]) +Check [WILDCARD]/test.ts running 1 test from ./source.ts$[WILDCARD].ts file:///[WILDCARD]/source.ts$[WILDCARD].ts ... ok ([WILDCARD]) +running 1 test from ./test.ts +add() ... ok ([WILDCARD]) ok | 2 passed | 0 failed ([WILDCARD]) diff --git a/tests/specs/workspaces/lockfile/test_root.out b/tests/specs/workspaces/lockfile/test_root.out index 2c62b615b1..da04d1973a 100644 --- a/tests/specs/workspaces/lockfile/test_root.out +++ b/tests/specs/workspaces/lockfile/test_root.out @@ -1,5 +1,4 @@ Check file:///[WILDLINE]/integration.test.ts -Check file:///[WILDLINE]/pkg/mod.test.ts running 1 test from ./integration.test.ts should add ... ok ([WILDLINE]) running 1 test from ./pkg/mod.test.ts diff --git a/tests/specs/workspaces/non_fatal_diagnostics/lint.out b/tests/specs/workspaces/non_fatal_diagnostics/lint.out index 864dc47ac4..8b4ae8edfc 100644 --- a/tests/specs/workspaces/non_fatal_diagnostics/lint.out +++ b/tests/specs/workspaces/non_fatal_diagnostics/lint.out @@ -1,4 +1,4 @@ -Warning "compilerOptions" field can only be specified in the workspace root deno.json file. +Warning "lock" field can only be specified in the workspace root deno.json file. at file:///[WILDLINE]/sub/deno.json Warning "lint.report" field can only be specified in the workspace root deno.json file. at file:///[WILDLINE]/sub/deno.json diff --git a/tests/specs/workspaces/non_fatal_diagnostics/sub/deno.json b/tests/specs/workspaces/non_fatal_diagnostics/sub/deno.json index 0a21df89f7..b0375f79af 100644 --- a/tests/specs/workspaces/non_fatal_diagnostics/sub/deno.json +++ b/tests/specs/workspaces/non_fatal_diagnostics/sub/deno.json @@ -2,7 +2,5 @@ "lint": { "report": "compact" }, - "compilerOptions": { - "strict": true - } + "lock": false }