diff options
Diffstat (limited to 'cli/tools')
-rw-r--r-- | cli/tools/bench.rs | 245 | ||||
-rw-r--r-- | cli/tools/bundle.rs | 203 | ||||
-rw-r--r-- | cli/tools/fmt.rs | 147 | ||||
-rw-r--r-- | cli/tools/lint.rs | 258 | ||||
-rw-r--r-- | cli/tools/run.rs | 66 | ||||
-rw-r--r-- | cli/tools/test.rs | 305 |
6 files changed, 540 insertions, 684 deletions
diff --git a/cli/tools/bench.rs b/cli/tools/bench.rs index a7b75d8be..318ac4f36 100644 --- a/cli/tools/bench.rs +++ b/cli/tools/bench.rs @@ -1,17 +1,19 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use crate::args::BenchOptions; +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::factory::CliFactoryBuilder; use crate::graph_util::graph_valid_with_cli_options; +use crate::graph_util::has_graph_root_local_dependent_changed; use crate::module_loader::ModuleLoadPreparer; use crate::ops; use crate::tools::test::format_test_error; use crate::tools::test::TestFilter; use crate::util::file_watcher; -use crate::util::file_watcher::ResolutionResult; use crate::util::fs::collect_specifiers; use crate::util::path::is_supported_ext; use crate::version::get_user_agent; @@ -22,7 +24,6 @@ use deno_core::error::AnyError; 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::located_script_name; use deno_core::serde_v8; @@ -40,7 +41,6 @@ use serde::Deserialize; use serde::Serialize; use std::collections::HashSet; use std::path::Path; -use std::path::PathBuf; use std::sync::Arc; use tokio::sync::mpsc::unbounded_channel; use tokio::sync::mpsc::UnboundedSender; @@ -630,9 +630,11 @@ fn is_supported_bench_path(path: &Path) -> bool { } pub async fn run_benchmarks( - cli_options: CliOptions, - bench_options: BenchOptions, + flags: Flags, + bench_flags: BenchFlags, ) -> Result<(), AnyError> { + let cli_options = CliOptions::from_flags(flags)?; + let bench_options = cli_options.resolve_bench_options(bench_flags)?; let factory = CliFactory::from_cli_options(Arc::new(cli_options)); let cli_options = factory.cli_options(); // Various bench files should not share the same permissions in terms of @@ -679,168 +681,101 @@ pub async fn run_benchmarks( // TODO(bartlomieju): heavy duplication of code with `cli/tools/test.rs` pub async fn run_benchmarks_with_watch( - cli_options: CliOptions, - bench_options: BenchOptions, + flags: Flags, + bench_flags: BenchFlags, ) -> Result<(), AnyError> { - let factory = CliFactory::from_cli_options(Arc::new(cli_options)); - let cli_options = factory.cli_options(); - let module_graph_builder = factory.module_graph_builder().await?; - let file_watcher = factory.file_watcher()?; - let module_load_preparer = factory.module_load_preparer().await?; - // 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 permissions = - Permissions::from_options(&cli_options.permissions_options())?; - let graph_kind = cli_options.type_check_mode().as_graph_kind(); - - let resolver = |changed: Option<Vec<PathBuf>>| { - let paths_to_watch = bench_options.files.include.clone(); - let paths_to_watch_clone = paths_to_watch.clone(); - let files_changed = changed.is_some(); - let bench_options = &bench_options; - let module_graph_builder = module_graph_builder.clone(); - let cli_options = cli_options.clone(); - - async move { - let bench_modules = - collect_specifiers(&bench_options.files, is_supported_bench_path)?; - - let mut paths_to_watch = paths_to_watch_clone; - let mut modules_to_reload = if files_changed { - Vec::new() - } else { - bench_modules.clone() - }; - let graph = module_graph_builder - .create_graph(graph_kind, bench_modules.clone()) - .await?; - graph_valid_with_cli_options(&graph, &bench_modules, &cli_options)?; - - // TODO(@kitsonk) - This should be totally derivable from the graph. - for specifier in bench_modules { - fn get_dependencies<'a>( - graph: &'a deno_graph::ModuleGraph, - maybe_module: Option<&'a deno_graph::Module>, - // This needs to be accessible to skip getting dependencies if they're already there, - // otherwise this will cause a stack overflow with circular dependencies - output: &mut HashSet<&'a ModuleSpecifier>, - ) { - if let Some(module) = maybe_module.and_then(|m| m.esm()) { - for dep in module.dependencies.values() { - if let Some(specifier) = &dep.get_code() { - if !output.contains(specifier) { - output.insert(specifier); - get_dependencies(graph, graph.get(specifier), output); - } - } - if let Some(specifier) = &dep.get_type() { - if !output.contains(specifier) { - output.insert(specifier); - get_dependencies(graph, graph.get(specifier), output); - } - } - } - } + let clear_screen = !flags.no_clear_screen; + file_watcher::watch_func( + flags, + file_watcher::PrintConfig { + job_name: "Bench".to_string(), + clear_screen, + }, + move |flags, sender, changed_paths| { + let bench_flags = bench_flags.clone(); + Ok(async move { + let factory = CliFactoryBuilder::new() + .with_watcher(sender.clone()) + .build_from_flags(flags) + .await?; + let cli_options = factory.cli_options(); + let bench_options = cli_options.resolve_bench_options(bench_flags)?; + + if let Some(watch_paths) = cli_options.watch_paths() { + let _ = sender.send(watch_paths); } + let _ = sender.send(bench_options.files.include.clone()); - // This bench module and all it's dependencies - let mut modules = HashSet::new(); - modules.insert(&specifier); - get_dependencies(&graph, graph.get(&specifier), &mut modules); + let graph_kind = cli_options.type_check_mode().as_graph_kind(); + let module_graph_builder = factory.module_graph_builder().await?; + let module_load_preparer = factory.module_load_preparer().await?; - paths_to_watch.extend( - modules - .iter() - .filter_map(|specifier| specifier.to_file_path().ok()), - ); + let bench_modules = + collect_specifiers(&bench_options.files, is_supported_bench_path)?; - if let Some(changed) = &changed { - for path in changed - .iter() - .filter_map(|path| ModuleSpecifier::from_file_path(path).ok()) - { - if modules.contains(&path) { - modules_to_reload.push(specifier); - break; - } - } - } - } + // 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 permissions = + Permissions::from_options(&cli_options.permissions_options())?; - Ok((paths_to_watch, modules_to_reload)) - } - .map(move |result| { - if files_changed - && matches!(result, Ok((_, ref modules)) if modules.is_empty()) - { - ResolutionResult::Ignore - } else { - match result { - Ok((paths_to_watch, modules_to_reload)) => { - ResolutionResult::Restart { - paths_to_watch, - result: Ok(modules_to_reload), + let graph = module_graph_builder + .create_graph(graph_kind, bench_modules.clone()) + .await?; + graph_valid_with_cli_options(&graph, &bench_modules, cli_options)?; + + let bench_modules_to_reload = if let Some(changed_paths) = changed_paths + { + let changed_specifiers = changed_paths + .into_iter() + .filter_map(|p| ModuleSpecifier::from_file_path(p).ok()) + .collect::<HashSet<_>>(); + let mut result = Vec::new(); + for bench_module_specifier in bench_modules { + if has_graph_root_local_dependent_changed( + &graph, + &bench_module_specifier, + &changed_specifiers, + ) { + result.push(bench_module_specifier.clone()); } } - Err(e) => ResolutionResult::Restart { - paths_to_watch, - result: Err(e), - }, - } - } - }) - }; + result + } else { + bench_modules.clone() + }; - let create_cli_main_worker_factory = - factory.create_cli_main_worker_factory_func().await?; - let operation = |modules_to_reload: Vec<ModuleSpecifier>| { - let permissions = &permissions; - let bench_options = &bench_options; - file_watcher.reset(); - let module_load_preparer = module_load_preparer.clone(); - let cli_options = cli_options.clone(); - let create_cli_main_worker_factory = create_cli_main_worker_factory.clone(); - - async move { - let worker_factory = Arc::new(create_cli_main_worker_factory()); - let specifiers = - collect_specifiers(&bench_options.files, is_supported_bench_path)? - .into_iter() - .filter(|specifier| modules_to_reload.contains(specifier)) - .collect::<Vec<ModuleSpecifier>>(); - - check_specifiers(&cli_options, &module_load_preparer, specifiers.clone()) - .await?; + let worker_factory = + Arc::new(factory.create_cli_main_worker_factory().await?); - if bench_options.no_run { - return Ok(()); - } + let specifiers = + collect_specifiers(&bench_options.files, is_supported_bench_path)? + .into_iter() + .filter(|specifier| bench_modules_to_reload.contains(specifier)) + .collect::<Vec<ModuleSpecifier>>(); - let log_level = cli_options.log_level(); - bench_specifiers( - worker_factory, - permissions, - specifiers, - BenchSpecifierOptions { - filter: TestFilter::from_flag(&bench_options.filter), - json: bench_options.json, - log_level, - }, - ) - .await?; + check_specifiers(cli_options, module_load_preparer, specifiers.clone()) + .await?; - Ok(()) - } - }; + if bench_options.no_run { + return Ok(()); + } - let clear_screen = !cli_options.no_clear_screen(); - file_watcher::watch_func( - resolver, - operation, - file_watcher::PrintConfig { - job_name: "Bench".to_string(), - clear_screen, + let log_level = cli_options.log_level(); + bench_specifiers( + worker_factory, + &permissions, + specifiers, + BenchSpecifierOptions { + filter: TestFilter::from_flag(&bench_options.filter), + json: bench_options.json, + log_level, + }, + ) + .await?; + + Ok(()) + }) }, ) .await?; diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index f38948776..1800d03cc 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -1,10 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use std::path::PathBuf; -use std::sync::Arc; use deno_core::error::AnyError; -use deno_core::futures::FutureExt; use deno_graph::Module; use deno_runtime::colors; @@ -13,17 +11,15 @@ use crate::args::CliOptions; use crate::args::Flags; use crate::args::TsConfigType; use crate::factory::CliFactory; +use crate::factory::CliFactoryBuilder; use crate::graph_util::error_for_any_npm_specifier; use crate::util; use crate::util::display; -use crate::util::file_watcher::ResolutionResult; pub async fn bundle( flags: Flags, bundle_flags: BundleFlags, ) -> Result<(), AnyError> { - let cli_options = Arc::new(CliOptions::from_flags(flags)?); - log::info!( "{} \"deno bundle\" is deprecated and will be removed in the future.", colors::yellow("Warning"), @@ -32,119 +28,112 @@ pub async fn bundle( "Use alternative bundlers like \"deno_emit\", \"esbuild\" or \"rollup\" instead." ); - let module_specifier = cli_options.resolve_main_module()?; - - let resolver = |_| { - let cli_options = cli_options.clone(); - let module_specifier = &module_specifier; - async move { - log::debug!(">>>>> bundle START"); - let factory = CliFactory::from_cli_options(cli_options); - let module_graph_builder = factory.module_graph_builder().await?; - let cli_options = factory.cli_options(); - - let graph = module_graph_builder - .create_graph_and_maybe_check(vec![module_specifier.clone()]) - .await?; - - let mut paths_to_watch: Vec<PathBuf> = graph - .specifiers() - .filter_map(|(_, r)| { - r.ok().and_then(|module| match module { - Module::Esm(m) => m.specifier.to_file_path().ok(), - Module::Json(m) => m.specifier.to_file_path().ok(), - // nothing to watch - Module::Node(_) | Module::Npm(_) | Module::External(_) => None, - }) - }) - .collect(); - - if let Ok(Some(import_map_path)) = cli_options - .resolve_import_map_specifier() - .map(|ms| ms.and_then(|ref s| s.to_file_path().ok())) - { - paths_to_watch.push(import_map_path); - } - - Ok((paths_to_watch, graph, cli_options.clone())) - } - .map(move |result| match result { - Ok((paths_to_watch, graph, ps)) => ResolutionResult::Restart { - paths_to_watch, - result: Ok((ps, graph)), - }, - Err(e) => ResolutionResult::Restart { - paths_to_watch: vec![module_specifier.to_file_path().unwrap()], - result: Err(e), + if flags.watch.is_some() { + let clear_screen = !flags.no_clear_screen; + util::file_watcher::watch_func( + flags, + util::file_watcher::PrintConfig { + job_name: "Bundle".to_string(), + clear_screen, }, - }) - }; - - let operation = - |(cli_options, graph): (Arc<CliOptions>, Arc<deno_graph::ModuleGraph>)| { - let out_file = &bundle_flags.out_file; - async move { - // at the moment, we don't support npm specifiers in deno bundle, so show an error - error_for_any_npm_specifier(&graph)?; - - let bundle_output = bundle_module_graph(graph.as_ref(), &cli_options)?; - log::debug!(">>>>> bundle END"); - - if let Some(out_file) = out_file { - let output_bytes = bundle_output.code.as_bytes(); - let output_len = output_bytes.len(); - util::fs::write_file(out_file, output_bytes, 0o644)?; - log::info!( - "{} {:?} ({})", - colors::green("Emit"), - out_file, - colors::gray(display::human_size(output_len as f64)) - ); - if let Some(bundle_map) = bundle_output.maybe_map { - let map_bytes = bundle_map.as_bytes(); - let map_len = map_bytes.len(); - let ext = if let Some(curr_ext) = out_file.extension() { - format!("{}.map", curr_ext.to_string_lossy()) - } else { - "map".to_string() - }; - let map_out_file = out_file.with_extension(ext); - util::fs::write_file(&map_out_file, map_bytes, 0o644)?; - log::info!( - "{} {:?} ({})", - colors::green("Emit"), - map_out_file, - colors::gray(display::human_size(map_len as f64)) - ); + move |flags, sender, _changed_paths| { + let bundle_flags = bundle_flags.clone(); + Ok(async move { + let factory = CliFactoryBuilder::new() + .with_watcher(sender.clone()) + .build_from_flags(flags) + .await?; + let cli_options = factory.cli_options(); + + if let Some(watch_paths) = cli_options.watch_paths() { + let _ = sender.send(watch_paths); } - } else { - println!("{}", bundle_output.code); - } - Ok(()) - } - }; + bundle_action(factory, &bundle_flags).await?; - if cli_options.watch_paths().is_some() { - util::file_watcher::watch_func( - resolver, - operation, - util::file_watcher::PrintConfig { - job_name: "Bundle".to_string(), - clear_screen: !cli_options.no_clear_screen(), + Ok(()) + }) }, ) .await?; } else { - let module_graph = - if let ResolutionResult::Restart { result, .. } = resolver(None).await { - result? + let factory = CliFactory::from_flags(flags).await?; + bundle_action(factory, &bundle_flags).await?; + } + + Ok(()) +} + +async fn bundle_action( + factory: CliFactory, + bundle_flags: &BundleFlags, +) -> Result<(), AnyError> { + let cli_options = factory.cli_options(); + let module_specifier = cli_options.resolve_main_module()?; + log::debug!(">>>>> bundle START"); + let module_graph_builder = factory.module_graph_builder().await?; + let cli_options = factory.cli_options(); + + let graph = module_graph_builder + .create_graph_and_maybe_check(vec![module_specifier.clone()]) + .await?; + + let mut paths_to_watch: Vec<PathBuf> = graph + .specifiers() + .filter_map(|(_, r)| { + r.ok().and_then(|module| match module { + Module::Esm(m) => m.specifier.to_file_path().ok(), + Module::Json(m) => m.specifier.to_file_path().ok(), + // nothing to watch + Module::Node(_) | Module::Npm(_) | Module::External(_) => None, + }) + }) + .collect(); + + if let Ok(Some(import_map_path)) = cli_options + .resolve_import_map_specifier() + .map(|ms| ms.and_then(|ref s| s.to_file_path().ok())) + { + paths_to_watch.push(import_map_path); + } + + // at the moment, we don't support npm specifiers in deno bundle, so show an error + error_for_any_npm_specifier(&graph)?; + + let bundle_output = bundle_module_graph(graph.as_ref(), cli_options)?; + log::debug!(">>>>> bundle END"); + let out_file = &bundle_flags.out_file; + + if let Some(out_file) = out_file { + let output_bytes = bundle_output.code.as_bytes(); + let output_len = output_bytes.len(); + util::fs::write_file(out_file, output_bytes, 0o644)?; + log::info!( + "{} {:?} ({})", + colors::green("Emit"), + out_file, + colors::gray(display::human_size(output_len as f64)) + ); + if let Some(bundle_map) = bundle_output.maybe_map { + let map_bytes = bundle_map.as_bytes(); + let map_len = map_bytes.len(); + let ext = if let Some(curr_ext) = out_file.extension() { + format!("{}.map", curr_ext.to_string_lossy()) } else { - unreachable!(); + "map".to_string() }; - operation(module_graph).await?; + let map_out_file = out_file.with_extension(ext); + util::fs::write_file(&map_out_file, map_bytes, 0o644)?; + log::info!( + "{} {:?} ({})", + colors::green("Emit"), + map_out_file, + colors::gray(display::human_size(map_len as f64)) + ); + } + } else { + println!("{}", bundle_output.code); } - Ok(()) } diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index f2fec9302..7116c78cc 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -9,6 +9,8 @@ use crate::args::CliOptions; use crate::args::FilesConfig; +use crate::args::Flags; +use crate::args::FmtFlags; use crate::args::FmtOptions; use crate::args::FmtOptionsConfig; use crate::args::ProseWrap; @@ -16,7 +18,6 @@ use crate::colors; use crate::factory::CliFactory; use crate::util::diff::diff; use crate::util::file_watcher; -use crate::util::file_watcher::ResolutionResult; use crate::util::fs::FileCollector; use crate::util::path::get_extension; use crate::util::text_encoding; @@ -46,11 +47,10 @@ use std::sync::Arc; use crate::cache::IncrementalCache; /// Format JavaScript/TypeScript files. -pub async fn format( - cli_options: CliOptions, - fmt_options: FmtOptions, -) -> Result<(), AnyError> { - if fmt_options.is_stdin { +pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { + if fmt_flags.is_stdin() { + let cli_options = CliOptions::from_flags(flags)?; + let fmt_options = cli_options.resolve_fmt_options(fmt_flags)?; return format_stdin( fmt_options, cli_options @@ -61,90 +61,93 @@ pub async fn format( ); } - let files = fmt_options.files; - let check = fmt_options.check; - let fmt_config_options = fmt_options.options; - - let resolver = |changed: Option<Vec<PathBuf>>| { - let files_changed = changed.is_some(); - - let result = collect_fmt_files(&files).map(|files| { - let refmt_files = if let Some(paths) = changed { - if check { - files - .iter() - .any(|path| paths.contains(path)) - .then_some(files) - .unwrap_or_else(|| [].to_vec()) - } else { - files - .into_iter() - .filter(|path| paths.contains(path)) - .collect::<Vec<_>>() - } - } else { - files - }; - (refmt_files, fmt_config_options.clone()) - }); - - let paths_to_watch = files.include.clone(); - async move { - if files_changed - && matches!(result, Ok((ref files, _)) if files.is_empty()) - { - ResolutionResult::Ignore - } else { - ResolutionResult::Restart { - paths_to_watch, - result, - } - } - } - }; - let factory = CliFactory::from_cli_options(Arc::new(cli_options)); - let cli_options = factory.cli_options(); - let caches = factory.caches()?; - let operation = |(paths, fmt_options): (Vec<PathBuf>, FmtOptionsConfig)| async { - let incremental_cache = Arc::new(IncrementalCache::new( - caches.fmt_incremental_cache_db(), - &fmt_options, - &paths, - )); - if check { - check_source_files(paths, fmt_options, incremental_cache.clone()).await?; - } else { - format_source_files(paths, fmt_options, incremental_cache.clone()) - .await?; - } - incremental_cache.wait_completion().await; - Ok(()) - }; - - if cli_options.watch_paths().is_some() { + if flags.watch.is_some() { + let clear_screen = !flags.no_clear_screen; file_watcher::watch_func( - resolver, - operation, + flags, file_watcher::PrintConfig { job_name: "Fmt".to_string(), - clear_screen: !cli_options.no_clear_screen(), + clear_screen, + }, + move |flags, sender, changed_paths| { + let fmt_flags = fmt_flags.clone(); + Ok(async move { + let factory = CliFactory::from_flags(flags).await?; + let cli_options = factory.cli_options(); + let fmt_options = cli_options.resolve_fmt_options(fmt_flags)?; + let files = + collect_fmt_files(&fmt_options.files).and_then(|files| { + if files.is_empty() { + Err(generic_error("No target files found.")) + } else { + Ok(files) + } + })?; + _ = sender.send(files.clone()); + let refmt_files = if let Some(paths) = changed_paths { + if fmt_options.check { + // check all files on any changed (https://github.com/denoland/deno/issues/12446) + files + .iter() + .any(|path| paths.contains(path)) + .then_some(files) + .unwrap_or_else(|| [].to_vec()) + } else { + files + .into_iter() + .filter(|path| paths.contains(path)) + .collect::<Vec<_>>() + } + } else { + files + }; + format_files(factory, fmt_options, refmt_files).await?; + + Ok(()) + }) }, ) .await?; } else { - let files = collect_fmt_files(&files).and_then(|files| { + let factory = CliFactory::from_flags(flags).await?; + let cli_options = factory.cli_options(); + let fmt_options = cli_options.resolve_fmt_options(fmt_flags)?; + let files = collect_fmt_files(&fmt_options.files).and_then(|files| { if files.is_empty() { Err(generic_error("No target files found.")) } else { Ok(files) } })?; - operation((files, fmt_config_options)).await?; + format_files(factory, fmt_options, files).await?; } Ok(()) } +async fn format_files( + factory: CliFactory, + fmt_options: FmtOptions, + paths: Vec<PathBuf>, +) -> Result<(), AnyError> { + let caches = factory.caches()?; + let check = fmt_options.check; + let incremental_cache = Arc::new(IncrementalCache::new( + caches.fmt_incremental_cache_db(), + &fmt_options.options, + &paths, + )); + if check { + check_source_files(paths, fmt_options.options, incremental_cache.clone()) + .await?; + } else { + format_source_files(paths, fmt_options.options, incremental_cache.clone()) + .await?; + } + incremental_cache.wait_completion().await; + Ok(()) +} + fn collect_fmt_files(files: &FilesConfig) -> Result<Vec<PathBuf>, AnyError> { FileCollector::new(is_supported_ext_fmt) .ignore_git_folder() diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs index 40c37ce77..9bfe1fd16 100644 --- a/cli/tools/lint.rs +++ b/cli/tools/lint.rs @@ -2,12 +2,9 @@ //! This module provides file linting utilities using //! [`deno_lint`](https://github.com/denoland/deno_lint). -//! -//! At the moment it is only consumed using CLI but in -//! the future it can be easily extended to provide -//! the same functions as ops available in JS runtime. -use crate::args::CliOptions; use crate::args::FilesConfig; +use crate::args::Flags; +use crate::args::LintFlags; use crate::args::LintOptions; use crate::args::LintReporterKind; use crate::args::LintRulesConfig; @@ -15,9 +12,9 @@ use crate::colors; use crate::factory::CliFactory; use crate::tools::fmt::run_parallelized; use crate::util::file_watcher; -use crate::util::file_watcher::ResolutionResult; use crate::util::fs::FileCollector; use crate::util::path::is_supported_ext; +use crate::util::sync::AtomicFlag; use deno_ast::MediaType; use deno_core::anyhow::bail; use deno_core::error::generic_error; @@ -38,8 +35,6 @@ use std::io::stdin; use std::io::Read; use std::path::Path; use std::path::PathBuf; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::Mutex; @@ -55,133 +50,70 @@ fn create_reporter(kind: LintReporterKind) -> Box<dyn LintReporter + Send> { } } -pub async fn lint( - cli_options: CliOptions, - lint_options: LintOptions, -) -> Result<(), AnyError> { - // Try to get lint rules. If none were set use recommended rules. - let lint_rules = get_configured_rules(lint_options.rules); - - if lint_rules.is_empty() { - bail!("No rules have been configured") - } - - let files = lint_options.files; - let reporter_kind = lint_options.reporter_kind; - - let resolver = |changed: Option<Vec<PathBuf>>| { - let files_changed = changed.is_some(); - let result = collect_lint_files(&files).map(|files| { - if let Some(paths) = changed { - files - .iter() - .any(|path| paths.contains(path)) - .then_some(files) - .unwrap_or_else(|| [].to_vec()) - } else { - files - } - }); - - let paths_to_watch = files.include.clone(); - - async move { - if files_changed && matches!(result, Ok(ref files) if files.is_empty()) { - ResolutionResult::Ignore - } else { - ResolutionResult::Restart { - paths_to_watch, - result, - } - } - } - }; - - let has_error = Arc::new(AtomicBool::new(false)); - let factory = CliFactory::from_cli_options(Arc::new(cli_options)); - let cli_options = factory.cli_options(); - let caches = factory.caches()?; - let operation = |paths: Vec<PathBuf>| async { - let incremental_cache = Arc::new(IncrementalCache::new( - caches.lint_incremental_cache_db(), - // use a hash of the rule names in order to bust the cache - &{ - // ensure this is stable by sorting it - let mut names = lint_rules.iter().map(|r| r.code()).collect::<Vec<_>>(); - names.sort_unstable(); - names - }, - &paths, - )); - let target_files_len = paths.len(); - let reporter_lock = - Arc::new(Mutex::new(create_reporter(reporter_kind.clone()))); - - run_parallelized(paths, { - let has_error = has_error.clone(); - let lint_rules = lint_rules.clone(); - let reporter_lock = reporter_lock.clone(); - let incremental_cache = incremental_cache.clone(); - move |file_path| { - let file_text = fs::read_to_string(&file_path)?; - - // don't bother rechecking this file if it didn't have any diagnostics before - if incremental_cache.is_file_same(&file_path, &file_text) { - return Ok(()); - } - - let r = lint_file(&file_path, file_text, lint_rules); - if let Ok((file_diagnostics, file_text)) = &r { - if file_diagnostics.is_empty() { - // update the incremental cache if there were no diagnostics - incremental_cache.update_file(&file_path, file_text) - } - } - - handle_lint_result( - &file_path.to_string_lossy(), - r, - reporter_lock.clone(), - has_error, - ); - - Ok(()) - } - }) - .await?; - incremental_cache.wait_completion().await; - reporter_lock.lock().unwrap().close(target_files_len); - - Ok(()) - }; - if cli_options.watch_paths().is_some() { - if lint_options.is_stdin { +pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { + if flags.watch.is_some() { + if lint_flags.is_stdin() { return Err(generic_error( "Lint watch on standard input is not supported.", )); } + let clear_screen = !flags.no_clear_screen; file_watcher::watch_func( - resolver, - operation, + flags, file_watcher::PrintConfig { job_name: "Lint".to_string(), - clear_screen: !cli_options.no_clear_screen(), + clear_screen, + }, + move |flags, sender, changed_paths| { + let lint_flags = lint_flags.clone(); + Ok(async move { + let factory = CliFactory::from_flags(flags).await?; + let cli_options = factory.cli_options(); + let lint_options = cli_options.resolve_lint_options(lint_flags)?; + let files = + collect_lint_files(&lint_options.files).and_then(|files| { + if files.is_empty() { + Err(generic_error("No target files found.")) + } else { + Ok(files) + } + })?; + _ = sender.send(files.clone()); + + let lint_paths = if let Some(paths) = changed_paths { + // lint all files on any changed (https://github.com/denoland/deno/issues/12446) + files + .iter() + .any(|path| paths.contains(path)) + .then_some(files) + .unwrap_or_else(|| [].to_vec()) + } else { + files + }; + + lint_files(factory, lint_options, lint_paths).await?; + Ok(()) + }) }, ) .await?; } else { - if lint_options.is_stdin { + let factory = CliFactory::from_flags(flags).await?; + let cli_options = factory.cli_options(); + let is_stdin = lint_flags.is_stdin(); + let lint_options = cli_options.resolve_lint_options(lint_flags)?; + let files = &lint_options.files; + let success = if is_stdin { + let reporter_kind = lint_options.reporter_kind; let reporter_lock = Arc::new(Mutex::new(create_reporter(reporter_kind))); + let lint_rules = get_config_rules_err_empty(lint_options.rules)?; let r = lint_stdin(lint_rules); - handle_lint_result( - STDIN_FILE_NAME, - r, - reporter_lock.clone(), - has_error.clone(), - ); + let success = + handle_lint_result(STDIN_FILE_NAME, r, reporter_lock.clone()); reporter_lock.lock().unwrap().close(1); + success } else { - let target_files = collect_lint_files(&files).and_then(|files| { + let target_files = collect_lint_files(files).and_then(|files| { if files.is_empty() { Err(generic_error("No target files found.")) } else { @@ -189,10 +121,9 @@ pub async fn lint( } })?; debug!("Found {} files", target_files.len()); - operation(target_files).await?; + lint_files(factory, lint_options, target_files).await? }; - let has_error = has_error.load(Ordering::Relaxed); - if has_error { + if !success { std::process::exit(1); } } @@ -200,6 +131,70 @@ pub async fn lint( Ok(()) } +async fn lint_files( + factory: CliFactory, + lint_options: LintOptions, + paths: Vec<PathBuf>, +) -> Result<bool, AnyError> { + let caches = factory.caches()?; + let lint_rules = get_config_rules_err_empty(lint_options.rules)?; + let incremental_cache = Arc::new(IncrementalCache::new( + caches.lint_incremental_cache_db(), + // use a hash of the rule names in order to bust the cache + &{ + // ensure this is stable by sorting it + let mut names = lint_rules.iter().map(|r| r.code()).collect::<Vec<_>>(); + names.sort_unstable(); + names + }, + &paths, + )); + let target_files_len = paths.len(); + let reporter_kind = lint_options.reporter_kind; + let reporter_lock = + Arc::new(Mutex::new(create_reporter(reporter_kind.clone()))); + let has_error = Arc::new(AtomicFlag::default()); + + run_parallelized(paths, { + let has_error = has_error.clone(); + let lint_rules = lint_rules.clone(); + let reporter_lock = reporter_lock.clone(); + let incremental_cache = incremental_cache.clone(); + move |file_path| { + let file_text = fs::read_to_string(&file_path)?; + + // don't bother rechecking this file if it didn't have any diagnostics before + if incremental_cache.is_file_same(&file_path, &file_text) { + return Ok(()); + } + + let r = lint_file(&file_path, file_text, lint_rules); + if let Ok((file_diagnostics, file_text)) = &r { + if file_diagnostics.is_empty() { + // update the incremental cache if there were no diagnostics + incremental_cache.update_file(&file_path, file_text) + } + } + + let success = handle_lint_result( + &file_path.to_string_lossy(), + r, + reporter_lock.clone(), + ); + if !success { + has_error.raise(); + } + + Ok(()) + } + }) + .await?; + incremental_cache.wait_completion().await; + reporter_lock.lock().unwrap().close(target_files_len); + + Ok(!has_error.is_raised()) +} + fn collect_lint_files(files: &FilesConfig) -> Result<Vec<PathBuf>, AnyError> { FileCollector::new(is_supported_ext) .ignore_git_folder() @@ -286,21 +281,20 @@ fn handle_lint_result( file_path: &str, result: Result<(Vec<LintDiagnostic>, String), AnyError>, reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>, - has_error: Arc<AtomicBool>, -) { +) -> bool { let mut reporter = reporter_lock.lock().unwrap(); match result { Ok((mut file_diagnostics, source)) => { sort_diagnostics(&mut file_diagnostics); for d in file_diagnostics.iter() { - has_error.store(true, Ordering::Relaxed); reporter.visit_diagnostic(d, source.split('\n').collect()); } + file_diagnostics.is_empty() } Err(err) => { - has_error.store(true, Ordering::Relaxed); reporter.visit_error(file_path, &err); + false } } } @@ -534,6 +528,16 @@ fn sort_diagnostics(diagnostics: &mut [LintDiagnostic]) { }); } +fn get_config_rules_err_empty( + rules: LintRulesConfig, +) -> Result<Vec<&'static dyn LintRule>, AnyError> { + let lint_rules = get_configured_rules(rules); + if lint_rules.is_empty() { + bail!("No rules have been configured") + } + Ok(lint_rules) +} + pub fn get_configured_rules( rules: LintRulesConfig, ) -> Vec<&'static dyn LintRule> { diff --git a/cli/tools/run.rs b/cli/tools/run.rs index 4805ea704..cbc9c3eae 100644 --- a/cli/tools/run.rs +++ b/cli/tools/run.rs @@ -3,7 +3,6 @@ use std::io::Read; use deno_ast::MediaType; -use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; use deno_runtime::permissions::Permissions; use deno_runtime::permissions::PermissionsContainer; @@ -98,45 +97,42 @@ pub async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> { // TODO(bartlomieju): this function is not handling `exit_code` set by the runtime // code properly. async fn run_with_watch(flags: Flags) -> Result<i32, AnyError> { - let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); - let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone()) - .build_from_flags(flags) - .await?; - let file_watcher = factory.file_watcher()?; - let cli_options = factory.cli_options(); - let clear_screen = !cli_options.no_clear_screen(); - let main_module = cli_options.resolve_main_module()?; - - maybe_npm_install(&factory).await?; - - let create_cli_main_worker_factory = - factory.create_cli_main_worker_factory_func().await?; - let operation = |main_module: ModuleSpecifier| { - file_watcher.reset(); - let permissions = PermissionsContainer::new(Permissions::from_options( - &cli_options.permissions_options(), - )?); - let create_cli_main_worker_factory = create_cli_main_worker_factory.clone(); - - Ok(async move { - let worker = create_cli_main_worker_factory() - .create_main_worker(main_module, permissions) - .await?; - worker.run_for_watcher().await?; - - Ok(()) - }) - }; + let clear_screen = !flags.no_clear_screen; - util::file_watcher::watch_func2( - receiver, - operation, - main_module, + util::file_watcher::watch_func( + flags, util::file_watcher::PrintConfig { job_name: "Process".to_string(), clear_screen, }, + move |flags, sender, _changed_paths| { + Ok(async move { + let factory = CliFactoryBuilder::new() + .with_watcher(sender.clone()) + .build_from_flags(flags) + .await?; + let cli_options = factory.cli_options(); + let main_module = cli_options.resolve_main_module()?; + + maybe_npm_install(&factory).await?; + + if let Some(watch_paths) = cli_options.watch_paths() { + let _ = sender.send(watch_paths); + } + + let permissions = PermissionsContainer::new(Permissions::from_options( + &cli_options.permissions_options(), + )?); + let worker = factory + .create_cli_main_worker_factory() + .await? + .create_main_worker(main_module, permissions) + .await?; + worker.run_for_watcher().await?; + + Ok(()) + }) + }, ) .await?; diff --git a/cli/tools/test.rs b/cli/tools/test.rs index 6f32d69e4..159de8ec8 100644 --- a/cli/tools/test.rs +++ b/cli/tools/test.rs @@ -2,18 +2,20 @@ use crate::args::CliOptions; use crate::args::FilesConfig; -use crate::args::TestOptions; +use crate::args::Flags; +use crate::args::TestFlags; use crate::colors; use crate::display; use crate::factory::CliFactory; +use crate::factory::CliFactoryBuilder; use crate::file_fetcher::File; use crate::file_fetcher::FileFetcher; use crate::graph_util::graph_valid_with_cli_options; +use crate::graph_util::has_graph_root_local_dependent_changed; use crate::module_loader::ModuleLoadPreparer; use crate::ops; use crate::util::checksum; use crate::util::file_watcher; -use crate::util::file_watcher::ResolutionResult; use crate::util::fs::collect_specifiers; use crate::util::path::get_extension; use crate::util::path::is_supported_ext; @@ -62,7 +64,6 @@ use std::io::Read; 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; @@ -1641,11 +1642,12 @@ async fn fetch_specifiers_with_test_mode( } pub async fn run_tests( - cli_options: CliOptions, - test_options: TestOptions, + flags: Flags, + test_flags: TestFlags, ) -> Result<(), AnyError> { - let factory = CliFactory::from_cli_options(Arc::new(cli_options)); + let factory = CliFactory::from_flags(flags).await?; let cli_options = factory.cli_options(); + let test_options = cli_options.resolve_test_options(test_flags)?; let file_fetcher = factory.file_fetcher()?; let module_load_preparer = factory.module_load_preparer().await?; // Various test files should not share the same permissions in terms of @@ -1708,186 +1710,9 @@ pub async fn run_tests( } pub async fn run_tests_with_watch( - cli_options: CliOptions, - test_options: TestOptions, + flags: Flags, + test_flags: TestFlags, ) -> Result<(), AnyError> { - let factory = CliFactory::from_cli_options(Arc::new(cli_options)); - let cli_options = factory.cli_options(); - let module_graph_builder = factory.module_graph_builder().await?; - let module_load_preparer = factory.module_load_preparer().await?; - let file_fetcher = factory.file_fetcher()?; - let file_watcher = factory.file_watcher()?; - // 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 permissions = - Permissions::from_options(&cli_options.permissions_options())?; - let graph_kind = cli_options.type_check_mode().as_graph_kind(); - let log_level = cli_options.log_level(); - - let resolver = |changed: Option<Vec<PathBuf>>| { - let paths_to_watch = test_options.files.include.clone(); - let paths_to_watch_clone = paths_to_watch.clone(); - let files_changed = changed.is_some(); - let test_options = &test_options; - let cli_options = cli_options.clone(); - let module_graph_builder = module_graph_builder.clone(); - - async move { - let test_modules = if test_options.doc { - collect_specifiers(&test_options.files, is_supported_test_ext) - } else { - collect_specifiers(&test_options.files, is_supported_test_path) - }?; - - let mut paths_to_watch = paths_to_watch_clone; - let mut modules_to_reload = if files_changed { - Vec::new() - } else { - test_modules.clone() - }; - let graph = module_graph_builder - .create_graph(graph_kind, test_modules.clone()) - .await?; - graph_valid_with_cli_options(&graph, &test_modules, &cli_options)?; - - // TODO(@kitsonk) - This should be totally derivable from the graph. - for specifier in test_modules { - fn get_dependencies<'a>( - graph: &'a deno_graph::ModuleGraph, - maybe_module: Option<&'a deno_graph::Module>, - // This needs to be accessible to skip getting dependencies if they're already there, - // otherwise this will cause a stack overflow with circular dependencies - output: &mut HashSet<&'a ModuleSpecifier>, - ) { - if let Some(module) = maybe_module.and_then(|m| m.esm()) { - for dep in module.dependencies.values() { - if let Some(specifier) = &dep.get_code() { - if !output.contains(specifier) { - output.insert(specifier); - get_dependencies(graph, graph.get(specifier), output); - } - } - if let Some(specifier) = &dep.get_type() { - if !output.contains(specifier) { - output.insert(specifier); - get_dependencies(graph, graph.get(specifier), output); - } - } - } - } - } - - // This test module and all it's dependencies - let mut modules = HashSet::new(); - modules.insert(&specifier); - get_dependencies(&graph, graph.get(&specifier), &mut modules); - - paths_to_watch.extend( - modules - .iter() - .filter_map(|specifier| specifier.to_file_path().ok()), - ); - - if let Some(changed) = &changed { - for path in changed - .iter() - .filter_map(|path| ModuleSpecifier::from_file_path(path).ok()) - { - if modules.contains(&path) { - modules_to_reload.push(specifier); - break; - } - } - } - } - - Ok((paths_to_watch, modules_to_reload)) - } - .map(move |result| { - if files_changed - && matches!(result, Ok((_, ref modules)) if modules.is_empty()) - { - ResolutionResult::Ignore - } else { - match result { - Ok((paths_to_watch, modules_to_reload)) => { - ResolutionResult::Restart { - paths_to_watch, - result: Ok(modules_to_reload), - } - } - Err(e) => ResolutionResult::Restart { - paths_to_watch, - result: Err(e), - }, - } - } - }) - }; - - let create_cli_main_worker_factory = - factory.create_cli_main_worker_factory_func().await?; - let operation = |modules_to_reload: Vec<ModuleSpecifier>| { - let permissions = &permissions; - let test_options = &test_options; - file_watcher.reset(); - let cli_options = cli_options.clone(); - let file_fetcher = file_fetcher.clone(); - let module_load_preparer = module_load_preparer.clone(); - let create_cli_main_worker_factory = create_cli_main_worker_factory.clone(); - - async move { - let worker_factory = Arc::new(create_cli_main_worker_factory()); - let specifiers_with_mode = fetch_specifiers_with_test_mode( - &file_fetcher, - &test_options.files, - &test_options.doc, - ) - .await? - .into_iter() - .filter(|(specifier, _)| modules_to_reload.contains(specifier)) - .collect::<Vec<(ModuleSpecifier, TestMode)>>(); - - check_specifiers( - &cli_options, - &file_fetcher, - &module_load_preparer, - specifiers_with_mode.clone(), - ) - .await?; - - if test_options.no_run { - return Ok(()); - } - - test_specifiers( - worker_factory, - permissions, - specifiers_with_mode - .into_iter() - .filter_map(|(s, m)| match m { - TestMode::Documentation => None, - _ => Some(s), - }) - .collect(), - TestSpecifiersOptions { - concurrent_jobs: test_options.concurrent_jobs, - fail_fast: test_options.fail_fast, - log_level, - specifier: TestSpecifierOptions { - filter: TestFilter::from_flag(&test_options.filter), - shuffle: test_options.shuffle, - trace_ops: test_options.trace_ops, - }, - }, - ) - .await?; - - Ok(()) - } - }; - // On top of the sigint handlers which are added and unbound for each test // run, a process-scoped basic exit handler is required due to a tokio // limitation where it doesn't unbind its own handler for the entire process @@ -1901,14 +1726,118 @@ pub async fn run_tests_with_watch( } }); - let clear_screen = !cli_options.no_clear_screen(); + let clear_screen = !flags.no_clear_screen; file_watcher::watch_func( - resolver, - operation, + flags, file_watcher::PrintConfig { job_name: "Test".to_string(), clear_screen, }, + move |flags, sender, changed_paths| { + let test_flags = test_flags.clone(); + Ok(async move { + let factory = CliFactoryBuilder::new() + .with_watcher(sender.clone()) + .build_from_flags(flags) + .await?; + let cli_options = factory.cli_options(); + let test_options = cli_options.resolve_test_options(test_flags)?; + + if let Some(watch_paths) = cli_options.watch_paths() { + let _ = sender.send(watch_paths); + } + let _ = sender.send(test_options.files.include.clone()); + + 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_builder = factory.module_graph_builder().await?; + let file_fetcher = factory.file_fetcher()?; + let test_modules = if test_options.doc { + collect_specifiers(&test_options.files, is_supported_test_ext) + } else { + collect_specifiers(&test_options.files, is_supported_test_path) + }?; + let permissions = + Permissions::from_options(&cli_options.permissions_options())?; + + let graph = module_graph_builder + .create_graph(graph_kind, test_modules.clone()) + .await?; + graph_valid_with_cli_options(&graph, &test_modules, &cli_options)?; + + let test_modules_to_reload = if let Some(changed_paths) = changed_paths + { + let changed_specifiers = changed_paths + .into_iter() + .filter_map(|p| ModuleSpecifier::from_file_path(p).ok()) + .collect::<HashSet<_>>(); + let mut result = Vec::new(); + for test_module_specifier in test_modules { + if has_graph_root_local_dependent_changed( + &graph, + &test_module_specifier, + &changed_specifiers, + ) { + result.push(test_module_specifier.clone()); + } + } + result + } else { + test_modules.clone() + }; + + let worker_factory = + Arc::new(factory.create_cli_main_worker_factory().await?); + let module_load_preparer = factory.module_load_preparer().await?; + let specifiers_with_mode = fetch_specifiers_with_test_mode( + file_fetcher, + &test_options.files, + &test_options.doc, + ) + .await? + .into_iter() + .filter(|(specifier, _)| test_modules_to_reload.contains(specifier)) + .collect::<Vec<(ModuleSpecifier, TestMode)>>(); + + check_specifiers( + &cli_options, + file_fetcher, + module_load_preparer, + specifiers_with_mode.clone(), + ) + .await?; + + if test_options.no_run { + return Ok(()); + } + + test_specifiers( + worker_factory, + &permissions, + specifiers_with_mode + .into_iter() + .filter_map(|(s, m)| match m { + TestMode::Documentation => None, + _ => Some(s), + }) + .collect(), + TestSpecifiersOptions { + concurrent_jobs: test_options.concurrent_jobs, + fail_fast: test_options.fail_fast, + log_level, + specifier: TestSpecifierOptions { + filter: TestFilter::from_flag(&test_options.filter), + shuffle: test_options.shuffle, + trace_ops: test_options.trace_ops, + }, + }, + ) + .await?; + + Ok(()) + }) + }, ) .await?; |