fix: reload config files on watcher restarts (#19487)

Closes #19468
This commit is contained in:
David Sherret 2023-06-14 18:29:19 -04:00 committed by GitHub
parent 48c6f71787
commit 84c793275b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 917 additions and 1244 deletions

View file

@ -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,169 +681,102 @@ 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);
}
}
}
}
}
// This bench 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 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?;
if bench_options.no_run {
return Ok(());
}
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(())
}
};
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: "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());
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?;
let bench_modules =
collect_specifiers(&bench_options.files, is_supported_bench_path)?;
// 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 = 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());
}
}
result
} else {
bench_modules.clone()
};
let worker_factory =
Arc::new(factory.create_cli_main_worker_factory().await?);
let specifiers =
collect_specifiers(&bench_options.files, is_supported_bench_path)?
.into_iter()
.filter(|specifier| bench_modules_to_reload.contains(specifier))
.collect::<Vec<ModuleSpecifier>>();
check_specifiers(cli_options, module_load_preparer, specifiers.clone())
.await?;
if bench_options.no_run {
return Ok(());
}
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?;

View file

@ -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,122 +28,115 @@ 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),
},
})
};
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))
);
}
} else {
println!("{}", bundle_output.code);
}
Ok(())
}
};
if cli_options.watch_paths().is_some() {
if flags.watch.is_some() {
let clear_screen = !flags.no_clear_screen;
util::file_watcher::watch_func(
resolver,
operation,
flags,
util::file_watcher::PrintConfig {
job_name: "Bundle".to_string(),
clear_screen: !cli_options.no_clear_screen(),
clear_screen,
},
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);
}
bundle_action(factory, &bundle_flags).await?;
Ok(())
})
},
)
.await?;
} else {
let module_graph =
if let ResolutionResult::Restart { result, .. } = resolver(None).await {
result?
} else {
unreachable!();
};
operation(module_graph).await?;
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 {
"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))
);
}
} else {
println!("{}", bundle_output.code);
}
Ok(())
}
fn bundle_module_graph(
graph: &deno_graph::ModuleGraph,
cli_options: &CliOptions,

View file

@ -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()

View file

@ -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> {

View file

@ -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()?;
let clear_screen = !flags.no_clear_screen;
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(())
})
};
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?;

View file

@ -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?;