From 941e28792ccc00c18f314018102d5fb7dba329ee Mon Sep 17 00:00:00 2001 From: Geert-Jan Zwiers Date: Sat, 7 Jan 2023 21:22:09 +0100 Subject: [PATCH] refactor(cli/tools): move flag and config logic to CliOptions (#17008) Co-authored-by: David Sherret --- cli/args/config_file.rs | 72 ++++--- cli/args/flags.rs | 363 +++++++++++++++++++++------------- cli/args/mod.rs | 370 ++++++++++++++++++++++++++++++++--- cli/lsp/analysis.rs | 7 +- cli/lsp/diagnostics.rs | 37 ++-- cli/lsp/language_server.rs | 57 +++--- cli/lsp/testing/execution.rs | 9 +- cli/main.rs | 37 ++-- cli/tools/bench.rs | 116 ++++------- cli/tools/fmt.rs | 176 +++++------------ cli/tools/lint.rs | 241 ++++++----------------- cli/tools/test.rs | 120 ++++-------- cli/tools/vendor/mod.rs | 5 +- cli/util/file_watcher.rs | 12 +- cli/util/fs.rs | 64 +++--- 15 files changed, 914 insertions(+), 772 deletions(-) diff --git a/cli/args/config_file.rs b/cli/args/config_file.rs index 0c0e5d2fae..47f8e9daa4 100644 --- a/cli/args/config_file.rs +++ b/cli/args/config_file.rs @@ -295,43 +295,45 @@ impl SerializedFilesConfig { include: self .include .into_iter() - .map(|p| config_dir.join(&p)) - .collect::, _>>()?, + .map(|p| { + let url = config_dir.join(&p)?; + specifier_to_file_path(&url) + }) + .collect::, _>>()?, exclude: self .exclude .into_iter() - .map(|p| config_dir.join(&p)) - .collect::, _>>()?, + .map(|p| { + let url = config_dir.join(&p)?; + specifier_to_file_path(&url) + }) + .collect::, _>>()?, }) } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct FilesConfig { - pub include: Vec, - pub exclude: Vec, + pub include: Vec, + pub exclude: Vec, } impl FilesConfig { /// Gets if the provided specifier is allowed based on the includes /// and excludes in the configuration file. pub fn matches_specifier(&self, specifier: &ModuleSpecifier) -> bool { + let file_path = match specifier_to_file_path(specifier) { + Ok(file_path) => file_path, + Err(_) => return false, + }; // Skip files which is in the exclude list. - let specifier_text = specifier.as_str(); - if self - .exclude - .iter() - .any(|i| specifier_text.starts_with(i.as_str())) - { + if self.exclude.iter().any(|i| file_path.starts_with(i)) { return false; } // Ignore files not in the include list if it's not empty. self.include.is_empty() - || self - .include - .iter() - .any(|i| specifier_text.starts_with(i.as_str())) + || self.include.iter().any(|i| file_path.starts_with(i)) } } @@ -663,6 +665,16 @@ impl ConfigFile { self.json.import_map.clone() } + pub fn to_fmt_config(&self) -> Result, AnyError> { + if let Some(config) = self.json.fmt.clone() { + let fmt_config: SerializedFmtConfig = serde_json::from_value(config) + .context("Failed to parse \"fmt\" configuration")?; + Ok(Some(fmt_config.into_resolved(&self.specifier)?)) + } else { + Ok(None) + } + } + pub fn to_lint_config(&self) -> Result, AnyError> { if let Some(config) = self.json.lint.clone() { let lint_config: SerializedLintConfig = serde_json::from_value(config) @@ -767,16 +779,6 @@ impl ConfigFile { }) } - pub fn to_fmt_config(&self) -> Result, AnyError> { - if let Some(config) = self.json.fmt.clone() { - let fmt_config: SerializedFmtConfig = serde_json::from_value(config) - .context("Failed to parse \"fmt\" configuration")?; - Ok(Some(fmt_config.into_resolved(&self.specifier)?)) - } else { - Ok(None) - } - } - pub fn resolve_tasks_config( &self, ) -> Result, AnyError> { @@ -1068,13 +1070,10 @@ mod tests { .to_lint_config() .expect("error parsing lint object") .expect("lint object should be defined"); - assert_eq!( - lint_config.files.include, - vec![config_dir.join("src/").unwrap()] - ); + assert_eq!(lint_config.files.include, vec![PathBuf::from("/deno/src/")]); assert_eq!( lint_config.files.exclude, - vec![config_dir.join("src/testdata/").unwrap()] + vec![PathBuf::from("/deno/src/testdata/")] ); assert_eq!( lint_config.rules.include, @@ -1090,13 +1089,10 @@ mod tests { .to_fmt_config() .expect("error parsing fmt object") .expect("fmt object should be defined"); - assert_eq!( - fmt_config.files.include, - vec![config_dir.join("src/").unwrap()] - ); + assert_eq!(fmt_config.files.include, vec![PathBuf::from("/deno/src/")]); assert_eq!( fmt_config.files.exclude, - vec![config_dir.join("src/testdata/").unwrap()] + vec![PathBuf::from("/deno/src/testdata/")], ); assert_eq!(fmt_config.options.use_tabs, Some(true)); assert_eq!(fmt_config.options.line_width, Some(80)); @@ -1190,6 +1186,8 @@ mod tests { let expected_exclude = ModuleSpecifier::from_file_path( testdata.join("fmt/with_config/subdir/b.ts"), ) + .unwrap() + .to_file_path() .unwrap(); assert_eq!(fmt_config.files.exclude, vec![expected_exclude]); diff --git a/cli/args/flags.rs b/cli/args/flags.rs index bae6bb0d58..b5753c00be 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -6,8 +6,6 @@ use clap::ColorChoice; use clap::Command; use clap::ValueHint; use deno_core::error::AnyError; -use deno_core::serde::Deserialize; -use deno_core::serde::Serialize; use deno_core::url::Url; use deno_runtime::permissions::parse_sys_kind; use log::debug; @@ -46,30 +44,35 @@ static SHORT_VERSION: Lazy = Lazy::new(|| { .to_string() }); -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct BenchFlags { +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct FileFlags { pub ignore: Vec, - pub include: Option>, + pub include: Vec, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct BenchFlags { + pub files: FileFlags, pub filter: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct BundleFlags { pub source_file: String, pub out_file: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct CacheFlags { pub files: Vec, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct CheckFlags { pub files: Vec, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct CompileFlags { pub source_file: String, pub output: Option, @@ -77,12 +80,12 @@ pub struct CompileFlags { pub target: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct CompletionsFlags { pub buf: Box<[u8]>, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct CoverageFlags { pub files: Vec, pub output: Option, @@ -92,7 +95,7 @@ pub struct CoverageFlags { pub lcov: bool, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct DocFlags { pub private: bool, pub json: bool, @@ -100,19 +103,18 @@ pub struct DocFlags { pub filter: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct EvalFlags { pub print: bool, pub code: String, pub ext: String, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct FmtFlags { pub check: bool, - pub files: Vec, - pub ignore: Vec, pub ext: String, + pub files: FileFlags, pub use_tabs: Option, pub line_width: Option, pub indent_width: Option, @@ -120,18 +122,18 @@ pub struct FmtFlags { pub prose_wrap: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct InitFlags { pub dir: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct InfoFlags { pub json: bool, pub file: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct InstallFlags { pub module_url: String, pub args: Vec, @@ -140,16 +142,15 @@ pub struct InstallFlags { pub force: bool, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct UninstallFlags { pub name: String, pub root: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct LintFlags { - pub files: Vec, - pub ignore: Vec, + pub files: FileFlags, pub rules: bool, pub maybe_rules_tags: Option>, pub maybe_rules_include: Option>, @@ -158,14 +159,14 @@ pub struct LintFlags { pub compact: bool, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct ReplFlags { pub eval_files: Option>, pub eval: Option, pub is_default_command: bool, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct RunFlags { pub script: String, } @@ -176,27 +177,26 @@ impl RunFlags { } } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct TaskFlags { pub cwd: Option, pub task: String, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct TestFlags { - pub ignore: Vec, pub doc: bool, pub no_run: bool, pub fail_fast: Option, + pub files: FileFlags, pub allow_none: bool, - pub include: Vec, pub filter: Option, pub shuffle: Option, - pub concurrent_jobs: NonZeroUsize, + pub concurrent_jobs: Option, pub trace_ops: bool, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct UpgradeFlags { pub dry_run: bool, pub force: bool, @@ -205,14 +205,14 @@ pub struct UpgradeFlags { pub output: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct VendorFlags { pub specifiers: Vec, pub output_path: Option, pub force: bool, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum DenoSubcommand { Bench(BenchFlags), Bundle(BundleFlags), @@ -455,9 +455,9 @@ impl Flags { pub fn config_path_args(&self) -> Option> { use DenoSubcommand::*; if let Fmt(FmtFlags { files, .. }) = &self.subcommand { - Some(files.clone()) + Some(files.include.clone()) } else if let Lint(LintFlags { files, .. }) = &self.subcommand { - Some(files.clone()) + Some(files.include.clone()) } else if let Run(RunFlags { script }) = &self.subcommand { if let Ok(module_specifier) = deno_core::resolve_url_or_path(script) { if module_specifier.scheme() == "file" @@ -2298,20 +2298,19 @@ fn bench_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } let include = if matches.is_present("files") { - let files: Vec = matches + let files = matches .values_of("files") .unwrap() - .map(String::from) + .map(PathBuf::from) .collect(); - Some(files) + files } else { - None + Vec::new() }; watch_arg_parse(flags, matches, false); flags.subcommand = DenoSubcommand::Bench(BenchFlags { - include, - ignore, + files: FileFlags { include, ignore }, filter, }); } @@ -2493,7 +2492,7 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) { config_args_parse(flags, matches); watch_arg_parse(flags, matches, false); - let files = match matches.values_of("files") { + let include = match matches.values_of("files") { Some(f) => f.map(PathBuf::from).collect(), None => vec![], }; @@ -2544,8 +2543,7 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.subcommand = DenoSubcommand::Fmt(FmtFlags { check: matches.is_present("check"), ext, - files, - ignore, + files: FileFlags { include, ignore }, use_tabs, line_width, indent_width, @@ -2647,12 +2645,15 @@ fn lint_parse(flags: &mut Flags, matches: &clap::ArgMatches) { let json = matches.is_present("json"); let compact = matches.is_present("compact"); flags.subcommand = DenoSubcommand::Lint(LintFlags { - files, + files: FileFlags { + include: files, + ignore, + }, rules, maybe_rules_tags, maybe_rules_include, maybe_rules_exclude, - ignore, + json, compact, }); @@ -2811,12 +2812,9 @@ fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) { let concurrent_jobs = if matches.is_present("parallel") { if let Ok(value) = env::var("DENO_JOBS") { - value - .parse::() - .unwrap_or(NonZeroUsize::new(1).unwrap()) + value.parse::().ok() } else { - std::thread::available_parallelism() - .unwrap_or(NonZeroUsize::new(1).unwrap()) + std::thread::available_parallelism().ok() } } else if matches.is_present("jobs") { // We can't change this to use the log crate because its not configured @@ -2828,20 +2826,19 @@ fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) { crate::colors::yellow("Warning: --jobs flag is deprecated. Use the --parallel flag with possibly the 'DENO_JOBS' environment variable."), ); if let Some(value) = matches.value_of("jobs") { - value.parse().unwrap() + Some(value.parse().unwrap()) } else { - std::thread::available_parallelism() - .unwrap_or(NonZeroUsize::new(1).unwrap()) + std::thread::available_parallelism().ok() } } else { - NonZeroUsize::new(1).unwrap() + None }; - let include: Vec = if matches.is_present("files") { + let include = if matches.is_present("files") { matches .values_of("files") .unwrap() - .map(String::from) + .map(PathBuf::from) .collect::>() } else { Vec::new() @@ -2853,8 +2850,7 @@ fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) { no_run, doc, fail_fast, - include, - ignore, + files: FileFlags { include, ignore }, filter, shuffle, allow_none, @@ -3267,6 +3263,7 @@ mod tests { fn global_flags() { #[rustfmt::skip] let r = flags_from_vec(svec!["deno", "--unstable", "--log-level", "debug", "--quiet", "run", "script.ts"]); + let flags = r.unwrap(); assert_eq!( flags, @@ -3562,13 +3559,15 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Fmt(FmtFlags { - ignore: vec![], check: false, - files: vec![ - PathBuf::from("script_1.ts"), - PathBuf::from("script_2.ts") - ], ext: "ts".to_string(), + files: FileFlags { + include: vec![ + PathBuf::from("script_1.ts"), + PathBuf::from("script_2.ts") + ], + ignore: vec![], + }, use_tabs: None, line_width: None, indent_width: None, @@ -3584,10 +3583,12 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Fmt(FmtFlags { - ignore: vec![], check: true, - files: vec![], ext: "ts".to_string(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, use_tabs: None, line_width: None, indent_width: None, @@ -3603,10 +3604,12 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Fmt(FmtFlags { - ignore: vec![], check: false, - files: vec![], ext: "ts".to_string(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, use_tabs: None, line_width: None, indent_width: None, @@ -3622,10 +3625,12 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Fmt(FmtFlags { - ignore: vec![], check: false, - files: vec![], ext: "ts".to_string(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, use_tabs: None, line_width: None, indent_width: None, @@ -3643,10 +3648,12 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Fmt(FmtFlags { - ignore: vec![], check: false, - files: vec![], ext: "ts".to_string(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, use_tabs: None, line_width: None, indent_width: None, @@ -3671,10 +3678,12 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Fmt(FmtFlags { - ignore: vec![PathBuf::from("bar.js")], check: true, - files: vec![PathBuf::from("foo.ts")], ext: "ts".to_string(), + files: FileFlags { + include: vec![PathBuf::from("foo.ts")], + ignore: vec![PathBuf::from("bar.js")], + }, use_tabs: None, line_width: None, indent_width: None, @@ -3691,10 +3700,12 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Fmt(FmtFlags { - ignore: vec![], check: false, - files: vec![], ext: "ts".to_string(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, use_tabs: None, line_width: None, indent_width: None, @@ -3718,10 +3729,12 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Fmt(FmtFlags { - ignore: vec![], check: false, - files: vec![PathBuf::from("foo.ts")], ext: "ts".to_string(), + files: FileFlags { + include: vec![PathBuf::from("foo.ts")], + ignore: vec![], + }, use_tabs: None, line_width: None, indent_width: None, @@ -3750,10 +3763,12 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Fmt(FmtFlags { - ignore: vec![], check: false, - files: vec![], ext: "ts".to_string(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, use_tabs: Some(true), line_width: Some(NonZeroU32::new(60).unwrap()), indent_width: Some(NonZeroU8::new(4).unwrap()), @@ -3772,17 +3787,19 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Lint(LintFlags { - files: vec![ - PathBuf::from("script_1.ts"), - PathBuf::from("script_2.ts") - ], + files: FileFlags { + include: vec![ + PathBuf::from("script_1.ts"), + PathBuf::from("script_2.ts") + ], + ignore: vec![], + }, rules: false, maybe_rules_tags: None, maybe_rules_include: None, maybe_rules_exclude: None, json: false, compact: false, - ignore: vec![], }), ..Flags::default() } @@ -3799,17 +3816,19 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Lint(LintFlags { - files: vec![ - PathBuf::from("script_1.ts"), - PathBuf::from("script_2.ts") - ], + files: FileFlags { + include: vec![ + PathBuf::from("script_1.ts"), + PathBuf::from("script_2.ts") + ], + ignore: vec![], + }, rules: false, maybe_rules_tags: None, maybe_rules_include: None, maybe_rules_exclude: None, json: false, compact: false, - ignore: vec![], }), watch: Some(vec![]), ..Flags::default() @@ -3828,17 +3847,19 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Lint(LintFlags { - files: vec![ - PathBuf::from("script_1.ts"), - PathBuf::from("script_2.ts") - ], + files: FileFlags { + include: vec![ + PathBuf::from("script_1.ts"), + PathBuf::from("script_2.ts") + ], + ignore: vec![], + }, rules: false, maybe_rules_tags: None, maybe_rules_include: None, maybe_rules_exclude: None, json: false, compact: false, - ignore: vec![], }), watch: Some(vec![]), no_clear_screen: true, @@ -3852,17 +3873,19 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Lint(LintFlags { - files: vec![], + files: FileFlags { + include: vec![], + ignore: vec![ + PathBuf::from("script_1.ts"), + PathBuf::from("script_2.ts") + ], + }, rules: false, maybe_rules_tags: None, maybe_rules_include: None, maybe_rules_exclude: None, json: false, compact: false, - ignore: vec![ - PathBuf::from("script_1.ts"), - PathBuf::from("script_2.ts") - ], }), ..Flags::default() } @@ -3873,14 +3896,16 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Lint(LintFlags { - files: vec![], + files: FileFlags { + include: vec![], + ignore: vec![], + }, rules: true, maybe_rules_tags: None, maybe_rules_include: None, maybe_rules_exclude: None, json: false, compact: false, - ignore: vec![], }), ..Flags::default() } @@ -3897,14 +3922,16 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Lint(LintFlags { - files: vec![], + files: FileFlags { + include: vec![], + ignore: vec![], + }, rules: false, maybe_rules_tags: Some(svec![""]), maybe_rules_include: Some(svec!["ban-untagged-todo", "no-undef"]), maybe_rules_exclude: Some(svec!["no-const-assign"]), json: false, compact: false, - ignore: vec![], }), ..Flags::default() } @@ -3915,14 +3942,16 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Lint(LintFlags { - files: vec![PathBuf::from("script_1.ts")], + files: FileFlags { + include: vec![PathBuf::from("script_1.ts")], + ignore: vec![], + }, rules: false, maybe_rules_tags: None, maybe_rules_include: None, maybe_rules_exclude: None, json: true, compact: false, - ignore: vec![], }), ..Flags::default() } @@ -3940,14 +3969,16 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Lint(LintFlags { - files: vec![PathBuf::from("script_1.ts")], + files: FileFlags { + include: vec![PathBuf::from("script_1.ts")], + ignore: vec![], + }, rules: false, maybe_rules_tags: None, maybe_rules_include: None, maybe_rules_exclude: None, json: true, compact: false, - ignore: vec![], }), config_flag: ConfigFlag::Path("Deno.jsonc".to_string()), ..Flags::default() @@ -3966,14 +3997,16 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Lint(LintFlags { - files: vec![PathBuf::from("script_1.ts")], + files: FileFlags { + include: vec![PathBuf::from("script_1.ts")], + ignore: vec![], + }, rules: false, maybe_rules_tags: None, maybe_rules_include: None, maybe_rules_exclude: None, json: false, compact: true, - ignore: vec![], }), config_flag: ConfigFlag::Path("Deno.jsonc".to_string()), ..Flags::default() @@ -5510,10 +5543,12 @@ mod tests { fail_fast: None, filter: Some("- foo".to_string()), allow_none: true, - include: svec!["dir1/", "dir2/"], - ignore: vec![], + files: FileFlags { + include: vec![PathBuf::from("dir1/"), PathBuf::from("dir2/")], + ignore: vec![], + }, shuffle: None, - concurrent_jobs: NonZeroUsize::new(1).unwrap(), + concurrent_jobs: None, trace_ops: true, }), unstable: true, @@ -5582,9 +5617,11 @@ mod tests { filter: None, allow_none: false, shuffle: None, - include: vec![], - ignore: vec![], - concurrent_jobs: NonZeroUsize::new(4).unwrap(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, + concurrent_jobs: Some(NonZeroUsize::new(4).unwrap()), trace_ops: false, }), type_check_mode: TypeCheckMode::Local, @@ -5610,9 +5647,11 @@ mod tests { filter: None, allow_none: false, shuffle: None, - include: vec![], - ignore: vec![], - concurrent_jobs: NonZeroUsize::new(1).unwrap(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, + concurrent_jobs: None, trace_ops: false, }), type_check_mode: TypeCheckMode::Local, @@ -5642,9 +5681,11 @@ mod tests { filter: None, allow_none: false, shuffle: None, - include: vec![], - ignore: vec![], - concurrent_jobs: NonZeroUsize::new(1).unwrap(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, + concurrent_jobs: None, trace_ops: false, }), no_prompt: true, @@ -5668,9 +5709,11 @@ mod tests { filter: None, allow_none: false, shuffle: Some(1), - include: vec![], - ignore: vec![], - concurrent_jobs: NonZeroUsize::new(1).unwrap(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, + concurrent_jobs: None, trace_ops: false, }), no_prompt: true, @@ -5694,9 +5737,38 @@ mod tests { filter: None, allow_none: false, shuffle: None, - include: vec![], - ignore: vec![], - concurrent_jobs: NonZeroUsize::new(1).unwrap(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, + concurrent_jobs: None, + trace_ops: false, + }), + no_prompt: true, + type_check_mode: TypeCheckMode::Local, + watch: Some(vec![]), + ..Flags::default() + } + ); + } + #[test] + fn test_watch_explicit_cwd() { + let r = flags_from_vec(svec!["deno", "test", "--watch", "./"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Test(TestFlags { + no_run: false, + doc: false, + fail_fast: None, + filter: None, + allow_none: false, + shuffle: None, + files: FileFlags { + include: vec![PathBuf::from("./")], + ignore: vec![], + }, + concurrent_jobs: None, trace_ops: false, }), no_prompt: true, @@ -5721,9 +5793,11 @@ mod tests { filter: None, allow_none: false, shuffle: None, - include: vec![], - ignore: vec![], - concurrent_jobs: NonZeroUsize::new(1).unwrap(), + files: FileFlags { + include: vec![], + ignore: vec![], + }, + concurrent_jobs: None, trace_ops: false, }), watch: Some(vec![]), @@ -6372,8 +6446,10 @@ mod tests { Flags { subcommand: DenoSubcommand::Bench(BenchFlags { filter: Some("- foo".to_string()), - include: Some(svec!["dir1/", "dir2/"]), - ignore: vec![], + files: FileFlags { + include: vec![PathBuf::from("dir1/"), PathBuf::from("dir2/")], + ignore: vec![], + }, }), unstable: true, type_check_mode: TypeCheckMode::Local, @@ -6386,6 +6462,27 @@ mod tests { ); } + #[test] + fn bench_watch() { + let r = flags_from_vec(svec!["deno", "bench", "--watch"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Bench(BenchFlags { + filter: None, + files: FileFlags { + include: vec![], + ignore: vec![], + }, + }), + no_prompt: true, + type_check_mode: TypeCheckMode::Local, + watch: Some(vec![]), + ..Flags::default() + } + ); + } + #[test] fn run_with_check() { let r = flags_from_vec(svec!["deno", "run", "--check", "script.ts",]); diff --git a/cli/args/mod.rs b/cli/args/mod.rs index d677cf8323..0f60d09c37 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -10,23 +10,15 @@ pub use config_file::BenchConfig; pub use config_file::CompilerOptions; pub use config_file::ConfigFile; pub use config_file::EmitConfigOptions; -pub use config_file::FmtConfig; +pub use config_file::FilesConfig; pub use config_file::FmtOptionsConfig; -pub use config_file::IgnoredCompilerOptions; pub use config_file::JsxImportSourceConfig; -pub use config_file::LintConfig; pub use config_file::LintRulesConfig; -pub use config_file::MaybeImportsResult; pub use config_file::ProseWrap; -pub use config_file::TestConfig; pub use config_file::TsConfig; pub use config_file::TsConfigForEmit; pub use config_file::TsConfigType; pub use config_file::TsTypeLib; -use deno_runtime::deno_tls::rustls; -use deno_runtime::deno_tls::rustls_native_certs::load_native_certs; -use deno_runtime::deno_tls::rustls_pemfile; -use deno_runtime::deno_tls::webpki_roots; pub use flags::*; pub use lockfile::Lockfile; pub use lockfile::LockfileError; @@ -40,13 +32,18 @@ use deno_core::normalize_path; use deno_core::parking_lot::Mutex; use deno_core::url::Url; use deno_runtime::colors; +use deno_runtime::deno_tls::rustls; use deno_runtime::deno_tls::rustls::RootCertStore; +use deno_runtime::deno_tls::rustls_native_certs::load_native_certs; +use deno_runtime::deno_tls::rustls_pemfile; +use deno_runtime::deno_tls::webpki_roots; use deno_runtime::inspector_server::InspectorServer; use deno_runtime::permissions::PermissionsOptions; use std::collections::BTreeMap; use std::env; use std::io::BufReader; use std::net::SocketAddr; +use std::num::NonZeroUsize; use std::path::PathBuf; use std::sync::Arc; @@ -54,6 +51,11 @@ use crate::cache::DenoDir; use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::version; +use self::config_file::FmtConfig; +use self::config_file::LintConfig; +use self::config_file::MaybeImportsResult; +use self::config_file::TestConfig; + /// Indicates how cached source files should be handled. #[derive(Debug, Clone, Eq, PartialEq)] pub enum CacheSetting { @@ -95,6 +97,274 @@ impl CacheSetting { } } +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BenchOptions { + pub files: FilesConfig, + pub filter: Option, +} + +impl BenchOptions { + pub fn resolve( + maybe_bench_config: Option, + maybe_bench_flags: Option, + ) -> Result { + let bench_flags = maybe_bench_flags.unwrap_or_default(); + Ok(Self { + files: resolve_files( + maybe_bench_config.map(|c| c.files), + Some(bench_flags.files), + ), + filter: bench_flags.filter, + }) + } +} + +#[derive(Clone, Debug, Default)] +pub struct FmtOptions { + pub is_stdin: bool, + pub check: bool, + pub ext: String, + pub options: FmtOptionsConfig, + pub files: FilesConfig, +} + +impl FmtOptions { + pub fn resolve( + maybe_fmt_config: Option, + mut maybe_fmt_flags: Option, + ) -> Result { + let is_stdin = if let Some(fmt_flags) = maybe_fmt_flags.as_mut() { + let args = &mut fmt_flags.files.include; + if args.len() == 1 && args[0].to_string_lossy() == "-" { + args.pop(); // remove the "-" arg + true + } else { + false + } + } else { + false + }; + let (maybe_config_options, maybe_config_files) = + maybe_fmt_config.map(|c| (c.options, c.files)).unzip(); + + Ok(Self { + is_stdin, + check: maybe_fmt_flags.as_ref().map(|f| f.check).unwrap_or(false), + ext: maybe_fmt_flags + .as_ref() + .map(|f| f.ext.to_string()) + .unwrap_or_else(|| "ts".to_string()), + options: resolve_fmt_options( + maybe_fmt_flags.as_ref(), + maybe_config_options, + ), + files: resolve_files( + maybe_config_files, + maybe_fmt_flags.map(|f| f.files), + ), + }) + } +} + +fn resolve_fmt_options( + fmt_flags: Option<&FmtFlags>, + options: Option, +) -> FmtOptionsConfig { + let mut options = options.unwrap_or_default(); + + if let Some(fmt_flags) = fmt_flags { + if let Some(use_tabs) = fmt_flags.use_tabs { + options.use_tabs = Some(use_tabs); + } + + if let Some(line_width) = fmt_flags.line_width { + options.line_width = Some(line_width.get()); + } + + if let Some(indent_width) = fmt_flags.indent_width { + options.indent_width = Some(indent_width.get()); + } + + if let Some(single_quote) = fmt_flags.single_quote { + options.single_quote = Some(single_quote); + } + + if let Some(prose_wrap) = &fmt_flags.prose_wrap { + options.prose_wrap = Some(match prose_wrap.as_str() { + "always" => ProseWrap::Always, + "never" => ProseWrap::Never, + "preserve" => ProseWrap::Preserve, + // validators in `flags.rs` makes other values unreachable + _ => unreachable!(), + }); + } + } + + options +} + +#[derive(Clone)] +pub struct TestOptions { + pub files: FilesConfig, + pub doc: bool, + pub no_run: bool, + pub fail_fast: Option, + pub allow_none: bool, + pub filter: Option, + pub shuffle: Option, + pub concurrent_jobs: NonZeroUsize, + pub trace_ops: bool, +} + +impl TestOptions { + pub fn resolve( + maybe_test_config: Option, + maybe_test_flags: Option, + ) -> Result { + let test_flags = maybe_test_flags.unwrap_or_default(); + + Ok(Self { + files: resolve_files( + maybe_test_config.map(|c| c.files), + Some(test_flags.files), + ), + allow_none: test_flags.allow_none, + 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, + no_run: test_flags.no_run, + shuffle: test_flags.shuffle, + trace_ops: test_flags.trace_ops, + }) + } +} + +#[derive(Clone, Debug)] +pub enum LintReporterKind { + Pretty, + Json, + Compact, +} + +impl Default for LintReporterKind { + fn default() -> Self { + LintReporterKind::Pretty + } +} + +#[derive(Clone, Debug, Default)] +pub struct LintOptions { + pub rules: LintRulesConfig, + pub files: FilesConfig, + pub is_stdin: bool, + pub reporter_kind: LintReporterKind, +} + +impl LintOptions { + pub fn resolve( + maybe_lint_config: Option, + mut maybe_lint_flags: Option, + ) -> Result { + let is_stdin = if let Some(lint_flags) = maybe_lint_flags.as_mut() { + let args = &mut lint_flags.files.include; + if args.len() == 1 && args[0].to_string_lossy() == "-" { + args.pop(); // remove the "-" arg + true + } else { + false + } + } else { + false + }; + + let mut maybe_reporter_kind = + maybe_lint_flags.as_ref().and_then(|lint_flags| { + if lint_flags.json { + Some(LintReporterKind::Json) + } else if lint_flags.compact { + Some(LintReporterKind::Compact) + } else { + None + } + }); + + if maybe_reporter_kind.is_none() { + // Flag not set, so try to get lint reporter from the config file. + if let Some(lint_config) = &maybe_lint_config { + maybe_reporter_kind = match lint_config.report.as_deref() { + Some("json") => Some(LintReporterKind::Json), + Some("compact") => Some(LintReporterKind::Compact), + Some("pretty") => Some(LintReporterKind::Pretty), + Some(_) => { + bail!("Invalid lint report type in config file") + } + None => None, + } + } + } + + let ( + maybe_file_flags, + maybe_rules_tags, + maybe_rules_include, + maybe_rules_exclude, + ) = maybe_lint_flags + .map(|f| { + ( + f.files, + f.maybe_rules_tags, + f.maybe_rules_include, + f.maybe_rules_exclude, + ) + }) + .unwrap_or_default(); + + let (maybe_config_files, maybe_config_rules) = + maybe_lint_config.map(|c| (c.files, c.rules)).unzip(); + Ok(Self { + reporter_kind: maybe_reporter_kind.unwrap_or_default(), + is_stdin, + files: resolve_files(maybe_config_files, Some(maybe_file_flags)), + rules: resolve_lint_rules_options( + maybe_config_rules, + maybe_rules_tags, + maybe_rules_include, + maybe_rules_exclude, + ), + }) + } +} + +fn resolve_lint_rules_options( + maybe_lint_rules_config: Option, + mut maybe_rules_tags: Option>, + mut maybe_rules_include: Option>, + mut maybe_rules_exclude: Option>, +) -> LintRulesConfig { + if let Some(config_rules) = maybe_lint_rules_config { + // Try to get configured rules. CLI flags take precedence + // over config file, i.e. if there's `rules.include` in config file + // and `--rules-include` CLI flag, only the flag value is taken into account. + if maybe_rules_include.is_none() { + maybe_rules_include = config_rules.include; + } + if maybe_rules_exclude.is_none() { + maybe_rules_exclude = config_rules.exclude; + } + if maybe_rules_tags.is_none() { + maybe_rules_tags = config_rules.tags; + } + } + LintRulesConfig { + exclude: maybe_rules_exclude, + include: maybe_rules_include, + tags: maybe_rules_tags, + } +} + /// Create and populate a root cert store based on the passed options and /// environment. pub fn get_root_cert_store( @@ -394,36 +664,57 @@ impl CliOptions { } } - pub fn to_lint_config(&self) -> Result, AnyError> { - if let Some(config_file) = &self.maybe_config_file { - config_file.to_lint_config() - } else { - Ok(None) - } + pub fn get_maybe_config_file(&self) -> &Option { + &self.maybe_config_file } - pub fn to_test_config(&self) -> Result, AnyError> { - if let Some(config_file) = &self.maybe_config_file { - config_file.to_test_config() + pub fn resolve_fmt_options( + &self, + fmt_flags: FmtFlags, + ) -> Result { + let maybe_fmt_config = if let Some(config_file) = &self.maybe_config_file { + config_file.to_fmt_config()? } else { - Ok(None) - } + None + }; + FmtOptions::resolve(maybe_fmt_config, Some(fmt_flags)) } - pub fn to_bench_config(&self) -> Result, AnyError> { - if let Some(config_file) = &self.maybe_config_file { - config_file.to_bench_config() + pub fn resolve_lint_options( + &self, + lint_flags: LintFlags, + ) -> Result { + let maybe_lint_config = if let Some(config_file) = &self.maybe_config_file { + config_file.to_lint_config()? } else { - Ok(None) - } + None + }; + LintOptions::resolve(maybe_lint_config, Some(lint_flags)) } - pub fn to_fmt_config(&self) -> Result, AnyError> { - if let Some(config) = &self.maybe_config_file { - config.to_fmt_config() + pub fn resolve_test_options( + &self, + test_flags: TestFlags, + ) -> Result { + let maybe_test_config = if let Some(config_file) = &self.maybe_config_file { + config_file.to_test_config()? } else { - Ok(None) - } + None + }; + TestOptions::resolve(maybe_test_config, Some(test_flags)) + } + + pub fn resolve_bench_options( + &self, + bench_flags: BenchFlags, + ) -> Result { + let maybe_bench_config = if let Some(config_file) = &self.maybe_config_file + { + config_file.to_bench_config()? + } else { + None + }; + BenchOptions::resolve(maybe_bench_config, Some(bench_flags)) } /// Vector of user script CLI arguments. @@ -639,6 +930,25 @@ fn resolve_import_map_specifier( Ok(None) } +/// Collect included and ignored files. CLI flags take precedence +/// over config file, i.e. if there's `files.ignore` in config file +/// and `--ignore` CLI flag, only the flag value is taken into account. +fn resolve_files( + maybe_files_config: Option, + maybe_file_flags: Option, +) -> FilesConfig { + let mut result = maybe_files_config.unwrap_or_default(); + if let Some(file_flags) = maybe_file_flags { + if !file_flags.include.is_empty() { + result.include = file_flags.include; + } + if !file_flags.ignore.is_empty() { + result.exclude = file_flags.ignore; + } + } + result +} + /// Resolves the no_prompt value based on the cli flags and environment. pub fn resolve_no_prompt(flags: &Flags) -> bool { flags.no_prompt || { diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index e436dc91a7..ddad05ee3b 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -5,9 +5,7 @@ use super::documents::Documents; use super::language_server; use super::tsc; -use crate::args::LintConfig; use crate::tools::lint::create_linter; -use crate::tools::lint::get_configured_rules; use deno_ast::SourceRange; use deno_ast::SourceRangedForSpanned; @@ -18,10 +16,12 @@ use deno_core::error::AnyError; use deno_core::serde::Deserialize; use deno_core::serde_json::json; use deno_core::ModuleSpecifier; +use deno_lint::rules::LintRule; use once_cell::sync::Lazy; use regex::Regex; use std::cmp::Ordering; use std::collections::HashMap; +use std::sync::Arc; use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types::Position; use tower_lsp::lsp_types::Range; @@ -127,9 +127,8 @@ fn as_lsp_range(range: &deno_lint::diagnostic::Range) -> Range { pub fn get_lint_references( parsed_source: &deno_ast::ParsedSource, - maybe_lint_config: Option<&LintConfig>, + lint_rules: Vec>, ) -> Result, AnyError> { - let lint_rules = get_configured_rules(maybe_lint_config, None, None, None)?; let linter = create_linter(parsed_source.media_type(), lint_rules); let lint_diagnostics = linter.lint_with_ast(parsed_source); diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index c52bde7901..605bd85ac2 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -12,8 +12,9 @@ use super::performance::Performance; use super::tsc; use super::tsc::TsServer; -use crate::args::LintConfig; +use crate::args::LintOptions; use crate::npm::NpmPackageReference; +use crate::tools::lint::get_configured_rules; use deno_ast::MediaType; use deno_core::anyhow::anyhow; @@ -24,6 +25,7 @@ use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::ModuleSpecifier; use deno_graph::Resolved; +use deno_lint::rules::LintRule; use deno_runtime::tokio_util::create_basic_runtime; use log::error; use std::collections::HashMap; @@ -36,7 +38,7 @@ use tokio_util::sync::CancellationToken; use tower_lsp::lsp_types as lsp; pub type SnapshotForDiagnostics = - (Arc, Arc, Option); + (Arc, Arc, LintOptions); pub type DiagnosticRecord = (ModuleSpecifier, Option, Vec); pub type DiagnosticVec = Vec; @@ -198,7 +200,7 @@ impl DiagnosticsServer { match rx.recv().await { // channel has closed None => break, - Some((snapshot, config, maybe_lint_config)) => { + Some((snapshot, config, lint_options)) => { // cancel the previous run token.cancel(); token = CancellationToken::new(); @@ -300,7 +302,7 @@ impl DiagnosticsServer { let diagnostics = generate_lint_diagnostics( &snapshot, &config, - maybe_lint_config, + &lint_options, token.clone(), ) .await; @@ -443,12 +445,12 @@ fn ts_json_to_diagnostics( async fn generate_lint_diagnostics( snapshot: &language_server::StateSnapshot, config: &ConfigSnapshot, - maybe_lint_config: Option, + lint_options: &LintOptions, token: CancellationToken, ) -> DiagnosticVec { let documents = snapshot.documents.documents(true, true); let workspace_settings = config.settings.workspace.clone(); - + let lint_rules = get_configured_rules(lint_options.rules.clone()); let mut diagnostics_vec = Vec::new(); if workspace_settings.lint { for document in documents { @@ -470,7 +472,8 @@ async fn generate_lint_diagnostics( version, generate_document_lint_diagnostics( config, - &maybe_lint_config, + lint_options, + lint_rules.clone(), &document, ), )); @@ -481,23 +484,21 @@ async fn generate_lint_diagnostics( fn generate_document_lint_diagnostics( config: &ConfigSnapshot, - maybe_lint_config: &Option, + lint_options: &LintOptions, + lint_rules: Vec>, document: &Document, ) -> Vec { if !config.specifier_enabled(document.specifier()) { return Vec::new(); } - if let Some(lint_config) = &maybe_lint_config { - if !lint_config.files.matches_specifier(document.specifier()) { - return Vec::new(); - } + if !lint_options.files.matches_specifier(document.specifier()) { + return Vec::new(); } match document.maybe_parsed_source() { Some(Ok(parsed_source)) => { - if let Ok(references) = analysis::get_lint_references( - &parsed_source, - maybe_lint_config.as_ref(), - ) { + if let Ok(references) = + analysis::get_lint_references(&parsed_source, lint_rules) + { references .into_iter() .map(|r| r.to_diagnostic()) @@ -1080,7 +1081,7 @@ let c: number = "a"; let diagnostics = generate_lint_diagnostics( &snapshot, &enabled_config, - None, + &Default::default(), Default::default(), ) .await; @@ -1121,7 +1122,7 @@ let c: number = "a"; let diagnostics = generate_lint_diagnostics( &snapshot, &disabled_config, - None, + &Default::default(), Default::default(), ) .await; diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index f44f8e0711..ec31fea4c7 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -62,8 +62,8 @@ use crate::args::CacheSetting; use crate::args::CliOptions; use crate::args::ConfigFile; use crate::args::Flags; -use crate::args::FmtConfig; -use crate::args::LintConfig; +use crate::args::FmtOptions; +use crate::args::LintOptions; use crate::args::TsConfig; use crate::cache::DenoDir; use crate::file_fetcher::get_source_from_data_url; @@ -122,14 +122,14 @@ pub struct Inner { /// An optional configuration file which has been specified in the client /// options. maybe_config_file: Option, - /// An optional configuration for formatter which has been taken from specified config file. - maybe_fmt_config: Option, /// An optional import map which is used to resolve modules. pub maybe_import_map: Option>, /// The URL for the import map which is used to determine relative imports. maybe_import_map_uri: Option, + /// Configuration for formatter which has been taken from specified config file. + fmt_options: FmtOptions, /// An optional configuration for linter which has been taken from specified config file. - pub maybe_lint_config: Option, + lint_options: LintOptions, /// A lazily create "server" for handling test run requests. maybe_testing_server: Option, /// Resolver for npm packages. @@ -347,8 +347,8 @@ impl Inner { maybe_config_file: None, maybe_import_map: None, maybe_import_map_uri: None, - maybe_lint_config: None, - maybe_fmt_config: None, + fmt_options: Default::default(), + lint_options: Default::default(), maybe_testing_server: None, module_registries, module_registries_location, @@ -713,26 +713,30 @@ impl Inner { fn update_config_file(&mut self) -> Result<(), AnyError> { self.maybe_config_file = None; - self.maybe_fmt_config = None; - self.maybe_lint_config = None; + self.fmt_options = Default::default(); + self.lint_options = Default::default(); if let Some(config_file) = self.get_config_file()? { - let lint_config = config_file + let lint_options = config_file .to_lint_config() + .and_then(|maybe_lint_config| { + LintOptions::resolve(maybe_lint_config, None) + }) .map_err(|err| { anyhow!("Unable to update lint configuration: {:?}", err) - })? - .unwrap_or_default(); - let fmt_config = config_file + })?; + let fmt_options = config_file .to_fmt_config() + .and_then(|maybe_fmt_config| { + FmtOptions::resolve(maybe_fmt_config, None) + }) .map_err(|err| { anyhow!("Unable to update formatter configuration: {:?}", err) - })? - .unwrap_or_default(); + })?; self.maybe_config_file = Some(config_file); - self.maybe_lint_config = Some(lint_config); - self.maybe_fmt_config = Some(fmt_config); + self.lint_options = lint_options; + self.fmt_options = fmt_options; } Ok(()) @@ -1196,19 +1200,14 @@ impl Inner { LspError::invalid_request() })?; - let fmt_options = if let Some(fmt_config) = self.maybe_fmt_config.as_ref() { - // skip formatting any files ignored by the config file - if !fmt_config.files.matches_specifier(&specifier) { - return Ok(None); - } - fmt_config.options.clone() - } else { - Default::default() - }; + // skip formatting any files ignored by the config file + if !self.fmt_options.files.matches_specifier(&specifier) { + return Ok(None); + } let format_result = match document.maybe_parsed_source() { Some(Ok(parsed_source)) => { - format_parsed_source(&parsed_source, fmt_options) + format_parsed_source(&parsed_source, &self.fmt_options.options) } Some(Err(err)) => Err(anyhow!("{}", err)), None => { @@ -1221,7 +1220,7 @@ impl Inner { .map(|ext| file_path.with_extension(ext)) .unwrap_or(file_path); // it's not a js/ts file, so attempt to format its contents - format_file(&file_path, &document.content(), &fmt_options) + format_file(&file_path, &document.content(), &self.fmt_options.options) } }; @@ -2521,7 +2520,7 @@ impl Inner { let snapshot = ( self.snapshot(), self.config.snapshot(), - self.maybe_lint_config.clone(), + self.lint_options.clone(), ); if let Err(err) = self.diagnostics_server.update(snapshot) { error!("Cannot update diagnostics: {}", err); diff --git a/cli/lsp/testing/execution.rs b/cli/lsp/testing/execution.rs index a498df8576..28f15a7ce4 100644 --- a/cli/lsp/testing/execution.rs +++ b/cli/lsp/testing/execution.rs @@ -35,6 +35,7 @@ use deno_runtime::tokio_util::run_local; use indexmap::IndexMap; use std::collections::HashMap; use std::collections::HashSet; +use std::num::NonZeroUsize; use std::sync::Arc; use std::time::Duration; use std::time::Instant; @@ -273,7 +274,13 @@ impl TestRun { let (concurrent_jobs, fail_fast) = if let DenoSubcommand::Test(test_flags) = ps.options.sub_command() { - (test_flags.concurrent_jobs.into(), test_flags.fail_fast) + ( + test_flags + .concurrent_jobs + .unwrap_or_else(|| NonZeroUsize::new(1).unwrap()) + .into(), + test_flags.fail_fast, + ) } else { unreachable!("Should always be Test subcommand."); }; diff --git a/cli/main.rs b/cli/main.rs index f430de3392..08746042cc 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -71,10 +71,13 @@ fn init_v8_flags(v8_flags: &[String]) { async fn run_subcommand(flags: Flags) -> Result { match flags.subcommand.clone() { DenoSubcommand::Bench(bench_flags) => { - if flags.watch.is_some() { - tools::bench::run_benchmarks_with_watch(flags, bench_flags).await?; + let cli_options = CliOptions::from_flags(flags)?; + let bench_options = cli_options.resolve_bench_options(bench_flags)?; + if cli_options.watch_paths().is_some() { + tools::bench::run_benchmarks_with_watch(cli_options, bench_options) + .await?; } else { - tools::bench::run_benchmarks(flags, bench_flags).await?; + tools::bench::run_benchmarks(cli_options, bench_options).await?; } Ok(0) } @@ -109,19 +112,9 @@ async fn run_subcommand(flags: Flags) -> Result { Ok(0) } DenoSubcommand::Fmt(fmt_flags) => { - let config = CliOptions::from_flags(flags)?; - - if fmt_flags.files.len() == 1 - && fmt_flags.files[0].to_string_lossy() == "-" - { - let maybe_fmt_config = config.to_fmt_config()?; - tools::fmt::format_stdin( - fmt_flags, - maybe_fmt_config.map(|c| c.options).unwrap_or_default(), - )?; - } else { - tools::fmt::format(&config, fmt_flags).await?; - } + let cli_options = CliOptions::from_flags(flags)?; + let fmt_options = cli_options.resolve_fmt_options(fmt_flags)?; + tools::fmt::format(cli_options, fmt_options).await?; Ok(0) } DenoSubcommand::Init(init_flags) => { @@ -148,7 +141,9 @@ async fn run_subcommand(flags: Flags) -> Result { if lint_flags.rules { tools::lint::print_rules_list(lint_flags.json); } else { - tools::lint::lint(flags, lint_flags).await?; + let cli_options = CliOptions::from_flags(flags)?; + let lint_options = cli_options.resolve_lint_options(lint_flags)?; + tools::lint::lint(cli_options, lint_options).await?; } Ok(0) } @@ -176,11 +171,13 @@ async fn run_subcommand(flags: Flags) -> Result { PathBuf::from(coverage_dir).canonicalize()?, ); } + let cli_options = CliOptions::from_flags(flags)?; + let test_options = cli_options.resolve_test_options(test_flags)?; - if flags.watch.is_some() { - tools::test::run_tests_with_watch(flags, test_flags).await?; + if cli_options.watch_paths().is_some() { + tools::test::run_tests_with_watch(cli_options, test_options).await?; } else { - tools::test::run_tests(flags, test_flags).await?; + tools::test::run_tests(cli_options, test_options).await?; } Ok(0) diff --git a/cli/tools/bench.rs b/cli/tools/bench.rs index 2d54b260d0..ed788fa7ff 100644 --- a/cli/tools/bench.rs +++ b/cli/tools/bench.rs @@ -1,8 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use crate::args::BenchConfig; -use crate::args::BenchFlags; -use crate::args::Flags; +use crate::args::BenchOptions; +use crate::args::CliOptions; use crate::args::TypeCheckMode; use crate::colors; use crate::graph_util::contains_specifier; @@ -15,7 +14,6 @@ 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::util::path::specifier_to_file_path; use crate::worker::create_main_worker_for_test_or_bench; use deno_core::error::generic_error; @@ -37,6 +35,7 @@ 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; @@ -487,24 +486,18 @@ fn is_supported_bench_path(path: &Path) -> bool { } pub async fn run_benchmarks( - flags: Flags, - bench_flags: BenchFlags, + cli_options: CliOptions, + bench_options: BenchOptions, ) -> Result<(), AnyError> { - let ps = ProcState::build(flags).await?; + let ps = ProcState::from_options(Arc::new(cli_options)).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(&ps.options.permissions_options())?; - let selection = - collect_include_ignore(&bench_flags, ps.options.to_bench_config()?); - - let specifiers = collect_specifiers( - selection.include, - &selection.ignore, - is_supported_bench_path, - )?; + let specifiers = + collect_specifiers(&bench_options.files, is_supported_bench_path)?; if specifiers.is_empty() { return Err(generic_error("No bench modules found")); @@ -517,7 +510,7 @@ pub async fn run_benchmarks( permissions, specifiers, BenchSpecifierOptions { - filter: bench_flags.filter, + filter: bench_options.filter, }, ) .await?; @@ -527,21 +520,22 @@ pub async fn run_benchmarks( // TODO(bartlomieju): heavy duplication of code with `cli/tools/test.rs` pub async fn run_benchmarks_with_watch( - flags: Flags, - bench_flags: BenchFlags, + cli_options: CliOptions, + bench_options: BenchOptions, ) -> Result<(), AnyError> { - let ps = ProcState::build(flags).await?; + let ps = ProcState::from_options(Arc::new(cli_options)).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(&ps.options.permissions_options())?; - let selection = - collect_include_ignore(&bench_flags, ps.options.to_bench_config()?); - - let paths_to_watch: Vec<_> = - selection.include.iter().map(PathBuf::from).collect(); + let paths_to_watch: Vec<_> = bench_options + .files + .include + .iter() + .map(PathBuf::from) + .collect(); let no_check = ps.options.type_check_mode() == TypeCheckMode::None; let resolver = |changed: Option>| { @@ -549,13 +543,11 @@ pub async fn run_benchmarks_with_watch( let paths_to_watch_clone = paths_to_watch.clone(); let files_changed = changed.is_some(); - let include = selection.include.clone(); - let ignore = selection.ignore.clone(); + let files = bench_options.files.clone(); let ps = ps.clone(); async move { - let bench_modules = - collect_specifiers(include.clone(), &ignore, is_supported_bench_path)?; + let bench_modules = collect_specifiers(&files, is_supported_bench_path)?; let mut paths_to_watch = paths_to_watch_clone; let mut modules_to_reload = if files_changed { @@ -615,7 +607,6 @@ pub async fn run_benchmarks_with_watch( } } } - // This bench module and all it's dependencies let mut modules = HashSet::new(); modules.insert(&specifier); @@ -664,27 +655,27 @@ pub async fn run_benchmarks_with_watch( }; let operation = |modules_to_reload: Vec<(ModuleSpecifier, ModuleKind)>| { - let filter = bench_flags.filter.clone(); - let include = selection.include.clone(); - let ignore = selection.ignore.clone(); let permissions = permissions.clone(); let ps = ps.clone(); + let filter = bench_options.filter.clone(); + let files = bench_options.files.clone(); async move { - let specifiers = - collect_specifiers(include.clone(), &ignore, is_supported_bench_path)? - .iter() - .filter(|specifier| contains_specifier(&modules_to_reload, specifier)) - .cloned() - .collect::>(); + let specifiers = collect_specifiers(&files, is_supported_bench_path)? + .iter() + .filter(|specifier| contains_specifier(&modules_to_reload, specifier)) + .cloned() + .collect::>(); check_specifiers(&ps, permissions.clone(), specifiers.clone()).await?; - let specifier_options = BenchSpecifierOptions { - filter: filter.clone(), - }; - bench_specifiers(ps, permissions.clone(), specifiers, specifier_options) - .await?; + bench_specifiers( + ps, + permissions.clone(), + specifiers, + BenchSpecifierOptions { filter }, + ) + .await?; Ok(()) } @@ -702,42 +693,3 @@ pub async fn run_benchmarks_with_watch( Ok(()) } - -struct IncludeIgnoreCollection { - include: Vec, - ignore: Vec, -} - -fn collect_include_ignore( - bench_flags: &BenchFlags, - maybe_bench_config: Option, -) -> IncludeIgnoreCollection { - let mut include = bench_flags.include.clone().unwrap_or_default(); - let mut ignore = bench_flags.ignore.clone(); - - if let Some(bench_config) = maybe_bench_config { - if include.is_empty() { - include = bench_config - .files - .include - .iter() - .map(|s| s.to_string()) - .collect::>(); - } - - if ignore.is_empty() { - ignore = bench_config - .files - .exclude - .iter() - .filter_map(|s| specifier_to_file_path(s).ok()) - .collect::>(); - } - } - - if include.is_empty() { - include.push(".".to_string()); - } - - IncludeIgnoreCollection { include, ignore } -} diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index d146c958ec..56b6bd00b6 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -8,7 +8,8 @@ //! the same functions as ops available in JS runtime. use crate::args::CliOptions; -use crate::args::FmtFlags; +use crate::args::FilesConfig; +use crate::args::FmtOptions; use crate::args::FmtOptionsConfig; use crate::args::ProseWrap; use crate::colors; @@ -17,7 +18,6 @@ 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::path::specifier_to_file_path; use crate::util::text_encoding; use deno_ast::ParsedSource; use deno_core::anyhow::bail; @@ -44,80 +44,41 @@ use crate::cache::IncrementalCache; /// Format JavaScript/TypeScript files. pub async fn format( - config: &CliOptions, - fmt_flags: FmtFlags, + cli_options: CliOptions, + fmt_options: FmtOptions, ) -> Result<(), AnyError> { - let maybe_fmt_config = config.to_fmt_config()?; - let deno_dir = config.resolve_deno_dir()?; - let FmtFlags { - files, - ignore, - check, - .. - } = fmt_flags.clone(); - - // First, prepare final configuration. - // Collect included and ignored files. CLI flags take precendence - // over config file, ie. if there's `files.ignore` in config file - // and `--ignore` CLI flag, only the flag value is taken into account. - let mut include_files = files.clone(); - let mut exclude_files = ignore; - - if let Some(fmt_config) = maybe_fmt_config.as_ref() { - if include_files.is_empty() { - include_files = fmt_config - .files - .include - .iter() - .filter_map(|s| specifier_to_file_path(s).ok()) - .collect::>(); - } - - if exclude_files.is_empty() { - exclude_files = fmt_config - .files - .exclude - .iter() - .filter_map(|s| specifier_to_file_path(s).ok()) - .collect::>(); - } + if fmt_options.is_stdin { + return format_stdin(fmt_options); } - if include_files.is_empty() { - include_files = [std::env::current_dir()?].to_vec(); - } - - // Now do the same for options - let fmt_options = resolve_fmt_options( - &fmt_flags, - maybe_fmt_config.map(|c| c.options).unwrap_or_default(), - ); + let files = fmt_options.files; + let check = fmt_options.check; + let fmt_config_options = fmt_options.options; let resolver = |changed: Option>| { let files_changed = changed.is_some(); - let result = - collect_fmt_files(&include_files, &exclude_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::>() - } + 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 - }; - (refmt_files, fmt_options.clone()) - }); + .into_iter() + .filter(|path| paths.contains(path)) + .collect::>() + } + } else { + files + }; + (refmt_files, fmt_config_options.clone()) + }); - let paths_to_watch = include_files.clone(); + let paths_to_watch = files.include.clone(); async move { if files_changed && matches!(result, Ok((ref files, _)) if files.is_empty()) @@ -131,6 +92,7 @@ pub async fn format( } } }; + let deno_dir = cli_options.resolve_deno_dir()?; let deno_dir = &deno_dir; let operation = |(paths, fmt_options): (Vec, FmtOptionsConfig)| async move { let incremental_cache = Arc::new(IncrementalCache::new( @@ -148,40 +110,36 @@ pub async fn format( Ok(()) }; - if config.watch_paths().is_some() { + if cli_options.watch_paths().is_some() { file_watcher::watch_func( resolver, operation, file_watcher::PrintConfig { job_name: "Fmt".to_string(), - clear_screen: !config.no_clear_screen(), + clear_screen: !cli_options.no_clear_screen(), }, ) .await?; } else { - let files = - collect_fmt_files(&include_files, &exclude_files).and_then(|files| { - if files.is_empty() { - Err(generic_error("No target files found.")) - } else { - Ok(files) - } - })?; - operation((files, fmt_options.clone())).await?; + let files = collect_fmt_files(&files).and_then(|files| { + if files.is_empty() { + Err(generic_error("No target files found.")) + } else { + Ok(files) + } + })?; + operation((files, fmt_config_options)).await?; } Ok(()) } -fn collect_fmt_files( - include_files: &[PathBuf], - exclude_files: &[PathBuf], -) -> Result, AnyError> { +fn collect_fmt_files(files: &FilesConfig) -> Result, AnyError> { FileCollector::new(is_supported_ext_fmt) .ignore_git_folder() .ignore_node_modules() - .add_ignore_paths(exclude_files) - .collect_files(include_files) + .add_ignore_paths(&files.exclude) + .collect_files(&files.include) } /// Formats markdown (using ) and its code blocks @@ -275,11 +233,11 @@ pub fn format_file( pub fn format_parsed_source( parsed_source: &ParsedSource, - fmt_options: FmtOptionsConfig, + fmt_options: &FmtOptionsConfig, ) -> Result, AnyError> { dprint_plugin_typescript::format_parsed_source( parsed_source, - &get_resolved_typescript_config(&fmt_options), + &get_resolved_typescript_config(fmt_options), ) } @@ -501,19 +459,14 @@ fn format_ensure_stable( /// Format stdin and write result to stdout. /// Treats input as TypeScript or as set by `--ext` flag. /// Compatible with `--check` flag. -pub fn format_stdin( - fmt_flags: FmtFlags, - fmt_options: FmtOptionsConfig, -) -> Result<(), AnyError> { +fn format_stdin(fmt_options: FmtOptions) -> Result<(), AnyError> { let mut source = String::new(); if stdin().read_to_string(&mut source).is_err() { bail!("Failed to read from stdin"); } - let file_path = PathBuf::from(format!("_stdin.{}", fmt_flags.ext)); - let fmt_options = resolve_fmt_options(&fmt_flags, fmt_options); - - let formatted_text = format_file(&file_path, &source, &fmt_options)?; - if fmt_flags.check { + let file_path = PathBuf::from(format!("_stdin.{}", fmt_options.ext)); + let formatted_text = format_file(&file_path, &source, &fmt_options.options)?; + if fmt_options.check { if formatted_text.is_some() { println!("Not formatted stdin"); } @@ -531,41 +484,6 @@ fn files_str(len: usize) -> &'static str { } } -fn resolve_fmt_options( - fmt_flags: &FmtFlags, - options: FmtOptionsConfig, -) -> FmtOptionsConfig { - let mut options = options; - - if let Some(use_tabs) = fmt_flags.use_tabs { - options.use_tabs = Some(use_tabs); - } - - if let Some(line_width) = fmt_flags.line_width { - options.line_width = Some(line_width.get()); - } - - if let Some(indent_width) = fmt_flags.indent_width { - options.indent_width = Some(indent_width.get()); - } - - if let Some(single_quote) = fmt_flags.single_quote { - options.single_quote = Some(single_quote); - } - - if let Some(prose_wrap) = &fmt_flags.prose_wrap { - options.prose_wrap = Some(match prose_wrap.as_str() { - "always" => ProseWrap::Always, - "never" => ProseWrap::Never, - "preserve" => ProseWrap::Preserve, - // validators in `flags.rs` makes other values unreachable - _ => unreachable!(), - }); - } - - options -} - fn get_resolved_typescript_config( options: &FmtOptionsConfig, ) -> dprint_plugin_typescript::configuration::Configuration { diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs index 4606375c13..413e3e34b4 100644 --- a/cli/tools/lint.rs +++ b/cli/tools/lint.rs @@ -6,19 +6,19 @@ //! 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::Flags; -use crate::args::LintConfig; -use crate::args::LintFlags; +use crate::args::CliOptions; +use crate::args::FilesConfig; +use crate::args::LintOptions; +use crate::args::LintReporterKind; +use crate::args::LintRulesConfig; use crate::colors; -use crate::proc_state::ProcState; 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::path::specifier_to_file_path; use deno_ast::MediaType; -use deno_core::anyhow::anyhow; +use deno_core::anyhow::bail; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::error::JsStackFrame; @@ -45,13 +45,6 @@ use crate::cache::IncrementalCache; static STDIN_FILE_NAME: &str = "_stdin.ts"; -#[derive(Clone, Debug)] -pub enum LintReporterKind { - Pretty, - Json, - Compact, -} - fn create_reporter(kind: LintReporterKind) -> Box { match kind { LintReporterKind::Pretty => Box::new(PrettyLintReporter::new()), @@ -60,102 +53,35 @@ fn create_reporter(kind: LintReporterKind) -> Box { } } -pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { - let LintFlags { - maybe_rules_tags, - maybe_rules_include, - maybe_rules_exclude, - files: args, - ignore, - json, - compact, - .. - } = lint_flags; - // First, prepare final configuration. - // Collect included and ignored files. CLI flags take precendence - // over config file, i.e. if there's `files.ignore` in config file - // and `--ignore` CLI flag, only the flag value is taken into account. - let mut include_files = args.clone(); - let mut exclude_files = ignore.clone(); - let mut maybe_reporter_kind = if json { - Some(LintReporterKind::Json) - } else if compact { - Some(LintReporterKind::Compact) - } else { - None - }; +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); - let ps = ProcState::build(flags).await?; - let maybe_lint_config = ps.options.to_lint_config()?; - - if let Some(lint_config) = maybe_lint_config.as_ref() { - if include_files.is_empty() { - include_files = lint_config - .files - .include - .iter() - .filter_map(|s| specifier_to_file_path(s).ok()) - .collect::>(); - } - - if exclude_files.is_empty() { - exclude_files = lint_config - .files - .exclude - .iter() - .filter_map(|s| specifier_to_file_path(s).ok()) - .collect::>(); - } - - if maybe_reporter_kind.is_none() { - maybe_reporter_kind = match lint_config.report.as_deref() { - Some("json") => Some(LintReporterKind::Json), - Some("compact") => Some(LintReporterKind::Compact), - Some("pretty") => Some(LintReporterKind::Pretty), - Some(_) => { - return Err(anyhow!("Invalid lint report type in config file")) - } - None => Some(LintReporterKind::Pretty), - } - } + if lint_rules.is_empty() { + bail!("No rules have been configured") } - if include_files.is_empty() { - include_files = [std::env::current_dir()?].to_vec(); - } - - let reporter_kind = match maybe_reporter_kind { - Some(report) => report, - None => LintReporterKind::Pretty, - }; - - let has_error = Arc::new(AtomicBool::new(false)); - // Try to get configured rules. CLI flags take precendence - // over config file, ie. if there's `rules.include` in config file - // and `--rules-include` CLI flag, only the flag value is taken into account. - let lint_rules = get_configured_rules( - maybe_lint_config.as_ref(), - maybe_rules_tags, - maybe_rules_include, - maybe_rules_exclude, - )?; + let files = lint_options.files; + let reporter_kind = lint_options.reporter_kind; let resolver = |changed: Option>| { let files_changed = changed.is_some(); - let result = - collect_lint_files(&include_files, &exclude_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 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 = include_files.clone(); + let paths_to_watch = files.include.clone(); async move { if files_changed && matches!(result, Ok(ref files) if files.is_empty()) { @@ -169,9 +95,12 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { } }; + let has_error = Arc::new(AtomicBool::new(false)); + let deno_dir = cli_options.resolve_deno_dir()?; + let operation = |paths: Vec| async { let incremental_cache = Arc::new(IncrementalCache::new( - &ps.dir.lint_incremental_cache_db_file_path(), + &deno_dir.lint_incremental_cache_db_file_path(), // use a hash of the rule names in order to bust the cache &{ // ensure this is stable by sorting it @@ -221,8 +150,8 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { Ok(()) }; - if ps.options.watch_paths().is_some() { - if args.len() == 1 && args[0].to_string_lossy() == "-" { + if cli_options.watch_paths().is_some() { + if lint_options.is_stdin { return Err(generic_error( "Lint watch on standard input is not supported.", )); @@ -232,12 +161,12 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { operation, file_watcher::PrintConfig { job_name: "Lint".to_string(), - clear_screen: !ps.options.no_clear_screen(), + clear_screen: !cli_options.no_clear_screen(), }, ) .await?; } else { - if args.len() == 1 && args[0].to_string_lossy() == "-" { + if lint_options.is_stdin { let reporter_lock = Arc::new(Mutex::new(create_reporter(reporter_kind.clone()))); let r = lint_stdin(lint_rules); @@ -249,14 +178,13 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { ); reporter_lock.lock().unwrap().close(1); } else { - let target_files = collect_lint_files(&include_files, &exclude_files) - .and_then(|files| { - if files.is_empty() { - Err(generic_error("No target files found.")) - } else { - Ok(files) - } - })?; + let target_files = collect_lint_files(&files).and_then(|files| { + if files.is_empty() { + Err(generic_error("No target files found.")) + } else { + Ok(files) + } + })?; debug!("Found {} files", target_files.len()); operation(target_files).await?; }; @@ -269,15 +197,12 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { Ok(()) } -fn collect_lint_files( - include_files: &[PathBuf], - exclude_files: &[PathBuf], -) -> Result, AnyError> { +fn collect_lint_files(files: &FilesConfig) -> Result, AnyError> { FileCollector::new(is_supported_ext) .ignore_git_folder() .ignore_node_modules() - .add_ignore_paths(exclude_files) - .collect_files(include_files) + .add_ignore_paths(&files.exclude) + .collect_files(&files.include) } pub fn print_rules_list(json: bool) { @@ -606,60 +531,17 @@ fn sort_diagnostics(diagnostics: &mut [LintDiagnostic]) { }); } -pub fn get_configured_rules( - maybe_lint_config: Option<&LintConfig>, - maybe_rules_tags: Option>, - maybe_rules_include: Option>, - maybe_rules_exclude: Option>, -) -> Result>, AnyError> { - if maybe_lint_config.is_none() - && maybe_rules_tags.is_none() - && maybe_rules_include.is_none() - && maybe_rules_exclude.is_none() +pub fn get_configured_rules(rules: LintRulesConfig) -> Vec> { + if rules.tags.is_none() && rules.include.is_none() && rules.exclude.is_none() { - return Ok(rules::get_recommended_rules()); + rules::get_recommended_rules() + } else { + rules::get_filtered_rules( + rules.tags.or_else(|| Some(vec!["recommended".to_string()])), + rules.exclude, + rules.include, + ) } - - let (config_file_tags, config_file_include, config_file_exclude) = - if let Some(lint_config) = maybe_lint_config { - ( - lint_config.rules.tags.clone(), - lint_config.rules.include.clone(), - lint_config.rules.exclude.clone(), - ) - } else { - (None, None, None) - }; - - let maybe_configured_include = if maybe_rules_include.is_some() { - maybe_rules_include - } else { - config_file_include - }; - - let maybe_configured_exclude = if maybe_rules_exclude.is_some() { - maybe_rules_exclude - } else { - config_file_exclude - }; - - let maybe_configured_tags = if maybe_rules_tags.is_some() { - maybe_rules_tags - } else { - config_file_tags - }; - - let configured_rules = rules::get_filtered_rules( - maybe_configured_tags.or_else(|| Some(vec!["recommended".to_string()])), - maybe_configured_exclude, - maybe_configured_include, - ); - - if configured_rules.is_empty() { - return Err(anyhow!("No rules have been configured")); - } - - Ok(configured_rules) } #[cfg(test)] @@ -671,15 +553,12 @@ mod test { #[test] fn recommended_rules_when_no_tags_in_config() { - let lint_config = LintConfig { - rules: LintRulesConfig { - exclude: Some(vec!["no-debugger".to_string()]), - ..Default::default() - }, - ..Default::default() + let rules_config = LintRulesConfig { + exclude: Some(vec!["no-debugger".to_string()]), + include: None, + tags: None, }; - let rules = - get_configured_rules(Some(&lint_config), None, None, None).unwrap(); + let rules = get_configured_rules(rules_config); let mut rule_names = rules .into_iter() .map(|r| r.code().to_string()) diff --git a/cli/tools/test.rs b/cli/tools/test.rs index 9d1774fff7..4cac8a4047 100644 --- a/cli/tools/test.rs +++ b/cli/tools/test.rs @@ -1,7 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use crate::args::Flags; -use crate::args::TestFlags; +use crate::args::CliOptions; +use crate::args::FilesConfig; +use crate::args::TestOptions; use crate::args::TypeCheckMode; use crate::colors; use crate::display; @@ -16,7 +17,6 @@ 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; -use crate::util::path::specifier_to_file_path; use crate::worker::create_main_worker_for_test_or_bench; use deno_ast::swc::common::comments::CommentKind; @@ -1237,15 +1237,13 @@ fn is_supported_test_ext(path: &Path) -> bool { /// - 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( - include: Vec, - ignore: Vec, + files: FilesConfig, include_inline: bool, ) -> Result, AnyError> { - let module_specifiers = - collect_specifiers(include.clone(), &ignore, is_supported_test_path)?; + let module_specifiers = collect_specifiers(&files, is_supported_test_path)?; if include_inline { - return collect_specifiers(include, &ignore, is_supported_test_ext).map( + return collect_specifiers(&files, is_supported_test_ext).map( |specifiers| { specifiers .into_iter() @@ -1281,44 +1279,10 @@ fn collect_specifiers_with_test_mode( /// as well. async fn fetch_specifiers_with_test_mode( ps: &ProcState, - include: Vec, - ignore: Vec, - include_inline: bool, + files: FilesConfig, + doc: bool, ) -> Result, AnyError> { - let maybe_test_config = ps.options.to_test_config()?; - - let mut include_files = include.clone(); - let mut exclude_files = ignore.clone(); - - if let Some(test_config) = maybe_test_config.as_ref() { - if include_files.is_empty() { - include_files = test_config - .files - .include - .iter() - .map(|s| s.to_string()) - .collect::>(); - } - - if exclude_files.is_empty() { - exclude_files = test_config - .files - .exclude - .iter() - .filter_map(|s| specifier_to_file_path(s).ok()) - .collect::>(); - } - } - - if include_files.is_empty() { - include_files.push(".".to_string()); - } - - let mut specifiers_with_mode = collect_specifiers_with_test_mode( - include_files, - exclude_files, - include_inline, - )?; + let mut specifiers_with_mode = collect_specifiers_with_test_mode(files, doc)?; for (specifier, mode) in &mut specifiers_with_mode { let file = ps .file_fetcher @@ -1336,31 +1300,28 @@ async fn fetch_specifiers_with_test_mode( } pub async fn run_tests( - flags: Flags, - test_flags: TestFlags, + cli_options: CliOptions, + test_options: TestOptions, ) -> Result<(), AnyError> { - let ps = ProcState::build(flags).await?; + let ps = ProcState::from_options(Arc::new(cli_options)).await?; // 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(&ps.options.permissions_options())?; - let specifiers_with_mode = fetch_specifiers_with_test_mode( - &ps, - test_flags.include, - test_flags.ignore.clone(), - test_flags.doc, - ) - .await?; - if !test_flags.allow_none && specifiers_with_mode.is_empty() { + let specifiers_with_mode = + fetch_specifiers_with_test_mode(&ps, test_options.files, test_options.doc) + .await?; + + if !test_options.allow_none && specifiers_with_mode.is_empty() { return Err(generic_error("No test modules found")); } check_specifiers(&ps, permissions.clone(), specifiers_with_mode.clone()) .await?; - if test_flags.no_run { + if test_options.no_run { return Ok(()); } @@ -1369,9 +1330,9 @@ pub async fn run_tests( permissions, specifiers_with_mode, TestSpecifierOptions { - concurrent_jobs: test_flags.concurrent_jobs, - fail_fast: test_flags.fail_fast, - filter: TestFilter::from_flag(&test_flags.filter), + concurrent_jobs: test_options.concurrent_jobs, + fail_fast: test_options.fail_fast, + filter: TestFilter::from_flag(&test_options.filter), }, ) .await?; @@ -1380,35 +1341,32 @@ pub async fn run_tests( } pub async fn run_tests_with_watch( - flags: Flags, - test_flags: TestFlags, + cli_options: CliOptions, + test_options: TestOptions, ) -> Result<(), AnyError> { - let ps = ProcState::build(flags).await?; + let ps = ProcState::from_options(Arc::new(cli_options)).await?; // 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(&ps.options.permissions_options())?; - let include = test_flags.include; - let ignore = test_flags.ignore.clone(); - let paths_to_watch: Vec<_> = include.iter().map(PathBuf::from).collect(); + let paths_to_watch: Vec<_> = test_options.files.include.clone(); let no_check = ps.options.type_check_mode() == TypeCheckMode::None; + let test_options = &test_options; let resolver = |changed: Option>| { let paths_to_watch = paths_to_watch.clone(); let paths_to_watch_clone = paths_to_watch.clone(); let files_changed = changed.is_some(); - let include = include.clone(); - let ignore = ignore.clone(); let ps = ps.clone(); async move { - let test_modules = if test_flags.doc { - collect_specifiers(include.clone(), &ignore, is_supported_test_ext) + let test_modules = if test_options.doc { + collect_specifiers(&test_options.files, is_supported_test_ext) } else { - collect_specifiers(include.clone(), &ignore, is_supported_test_path) + collect_specifiers(&test_options.files, is_supported_test_path) }?; let mut paths_to_watch = paths_to_watch_clone; @@ -1517,20 +1475,16 @@ pub async fn run_tests_with_watch( }) }; - let cli_options = ps.options.clone(); let operation = |modules_to_reload: Vec<(ModuleSpecifier, ModuleKind)>| { - let filter = test_flags.filter.clone(); - let include = include.clone(); - let ignore = ignore.clone(); let permissions = permissions.clone(); let ps = ps.clone(); + let test_options = test_options.clone(); async move { let specifiers_with_mode = fetch_specifiers_with_test_mode( &ps, - include.clone(), - ignore.clone(), - test_flags.doc, + test_options.files, + test_options.doc, ) .await? .iter() @@ -1543,7 +1497,7 @@ pub async fn run_tests_with_watch( check_specifiers(&ps, permissions.clone(), specifiers_with_mode.clone()) .await?; - if test_flags.no_run { + if test_options.no_run { return Ok(()); } @@ -1552,9 +1506,9 @@ pub async fn run_tests_with_watch( permissions.clone(), specifiers_with_mode, TestSpecifierOptions { - concurrent_jobs: test_flags.concurrent_jobs, - fail_fast: test_flags.fail_fast, - filter: TestFilter::from_flag(&filter), + concurrent_jobs: test_options.concurrent_jobs, + fail_fast: test_options.fail_fast, + filter: TestFilter::from_flag(&test_options.filter), }, ) .await?; @@ -1568,7 +1522,7 @@ pub async fn run_tests_with_watch( operation, file_watcher::PrintConfig { job_name: "Test".to_string(), - clear_screen: !cli_options.no_clear_screen(), + clear_screen: !ps.options.no_clear_screen(), }, ) .await?; diff --git a/cli/tools/vendor/mod.rs b/cli/tools/vendor/mod.rs index c15c7d1d33..aa306275c1 100644 --- a/cli/tools/vendor/mod.rs +++ b/cli/tools/vendor/mod.rs @@ -153,9 +153,12 @@ fn maybe_update_config_file(output_dir: &Path, ps: &ProcState) -> bool { Some(f) => f, None => return false, }; + let fmt_config = ps .options - .to_fmt_config() + .get_maybe_config_file() + .as_ref() + .and_then(|config| config.to_fmt_config().ok()) .unwrap_or_default() .unwrap_or_default(); let result = update_config_file( diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 741cb5a042..b87782d6b8 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -107,9 +107,13 @@ where log::debug!("File change ignored") } ResolutionResult::Restart { - paths_to_watch, + mut paths_to_watch, result, } => { + // watch the current directory when empty + if paths_to_watch.is_empty() { + paths_to_watch.push(PathBuf::from(".")); + } return (paths_to_watch, result); } } @@ -190,9 +194,13 @@ where print_after_restart(); } ResolutionResult::Restart { - paths_to_watch: paths, + paths_to_watch: mut paths, result, } => { + // watch the current directory when empty + if paths.is_empty() { + paths.push(PathBuf::from(".")); + } paths_to_watch = paths; resolution_result = result; } diff --git a/cli/util/fs.rs b/cli/util/fs.rs index d541b9ba4c..cb9232f392 100644 --- a/cli/util/fs.rs +++ b/cli/util/fs.rs @@ -6,6 +6,7 @@ pub use deno_core::normalize_path; use deno_core::ModuleSpecifier; use deno_runtime::deno_crypto::rand; use deno_runtime::deno_node::PathClean; +use std::borrow::Cow; use std::env::current_dir; use std::fs::OpenOptions; use std::io::Error; @@ -16,6 +17,8 @@ use std::path::PathBuf; use std::time::Duration; use walkdir::WalkDir; +use crate::args::FilesConfig; + use super::path::specifier_to_file_path; pub fn atomic_write_file>( @@ -181,6 +184,7 @@ impl bool> FileCollector { ignore_node_modules: false, } } + pub fn add_ignore_paths(mut self, paths: &[PathBuf]) -> Self { // retain only the paths which exist and ignore the rest self @@ -204,7 +208,13 @@ impl bool> FileCollector { files: &[PathBuf], ) -> Result, AnyError> { let mut target_files = Vec::new(); - for file in files { + let files = if files.is_empty() { + // collect files in the current directory when empty + Cow::Owned(vec![PathBuf::from(".")]) + } else { + Cow::Borrowed(files) + }; + for file in files.iter() { if let Ok(file) = canonicalize_path(file) { // use an iterator like this in order to minimize the number of file system operations let mut iterator = WalkDir::new(&file).into_iter(); @@ -254,18 +264,24 @@ impl bool> FileCollector { /// Specifiers that start with http and https are left intact. /// Note: This ignores all .git and node_modules folders. pub fn collect_specifiers( - include: Vec, - ignore: &[PathBuf], + files: &FilesConfig, predicate: impl Fn(&Path) -> bool, ) -> Result, AnyError> { let mut prepared = vec![]; let file_collector = FileCollector::new(predicate) - .add_ignore_paths(ignore) + .add_ignore_paths(&files.exclude) .ignore_git_folder() .ignore_node_modules(); let root_path = current_dir()?; - for path in include { + let include_files = if files.include.is_empty() { + // collect files in the current directory when empty + Cow::Owned(vec![root_path.clone()]) + } else { + Cow::Borrowed(&files.include) + }; + for path in include_files.iter() { + let path = path.to_string_lossy(); let lowercase_path = path.to_lowercase(); if lowercase_path.starts_with("http://") || lowercase_path.starts_with("https://") @@ -278,7 +294,7 @@ pub fn collect_specifiers( let p = if lowercase_path.starts_with("file://") { specifier_to_file_path(&ModuleSpecifier::parse(&path)?)? } else { - root_path.join(path) + root_path.join(path.as_ref()) }; let p = normalize_path(p); if p.is_dir() { @@ -675,12 +691,14 @@ mod tests { }; let result = collect_specifiers( - vec![ - "http://localhost:8080".to_string(), - root_dir_path.to_str().unwrap().to_string(), - "https://localhost:8080".to_string(), - ], - &[ignore_dir_path], + &FilesConfig { + include: vec![ + PathBuf::from("http://localhost:8080"), + root_dir_path.clone(), + PathBuf::from("https://localhost:8080".to_string()), + ], + exclude: vec![ignore_dir_path], + }, predicate, ) .unwrap(); @@ -713,16 +731,18 @@ mod tests { "file://" }; let result = collect_specifiers( - vec![format!( - "{}{}", - scheme, - root_dir_path - .join("child") - .to_str() - .unwrap() - .replace('\\', "/") - )], - &[], + &FilesConfig { + include: vec![PathBuf::from(format!( + "{}{}", + scheme, + root_dir_path + .join("child") + .to_str() + .unwrap() + .replace('\\', "/") + ))], + exclude: vec![], + }, predicate, ) .unwrap();