diff --git a/crates/oxc_cli/src/command.rs b/crates/oxc_cli/src/command.rs deleted file mode 100644 index 3ba6391eb..000000000 --- a/crates/oxc_cli/src/command.rs +++ /dev/null @@ -1,172 +0,0 @@ -use clap::{builder::ValueParser, Arg, ArgAction, Command}; - -#[must_use] -pub fn command() -> Command { - Command::new("oxc") - .bin_name("oxc") - .version("alpha") - .author("Boshen") - .about("The JavaScript Oxidation Compiler") - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(lint_subcommand()) -} - -fn lint_subcommand() -> Command { - Command::new("lint") - .alias("check") - .about("Lint this repository.") - .arg_required_else_help(true) - .arg( - Arg::new("fix") - .long("fix") - .required(false) - .action(ArgAction::SetTrue) - .help("This option allows you to enable oxc to fix as many issues as possible. If enabled, only unfixed issues are reported in the output") - ) - .arg( - Arg::new("quiet") - .long("quiet") - .required(false) - .action(ArgAction::SetTrue) - .help("This option allows you to disable reporting on warnings. If you enable this option, only errors are reported by oxc_lint.") - ) - .arg( - Arg::new("ignore-path") - .long("ignore-path") - .required(false) - .help("This option allows you to specify the file to use as your .eslintignore.") - ) - .arg( - Arg::new("no-ignore") - .long("no-ignore") - .required(false) - .action(ArgAction::SetTrue) - .help("Disables excluding of files from .eslintignore files, --ignore-path flags, --ignore-pattern flags.") - ) - .arg( - Arg::new("ignore-pattern") - .long("ignore-pattern") - .required(false) - .action(ArgAction::Append) - .help("This option allows you to specify patterns of files to ignore (in addition to those in .eslintignore).") - ) - .arg( - Arg::new("max-warnings") - .long("max-warnings") - .value_parser(clap::value_parser!(usize)) - .required(false) - .help("This option allows you to specify a warning threshold, which can be used to force oxc_lint to exit with an error status if there are too many warning-level rule violations in your project.") - ) - .arg( - Arg::new("path") - .value_name("PATH") - .num_args(1..) - .required(true) - .help("File or Directory paths to scan. Directories are scanned recursively.") - .value_parser(ValueParser::path_buf()), - ) -} - -#[cfg(test)] -mod test { - use std::path::PathBuf; - - use clap::ArgMatches; - - use super::command; - - #[test] - fn verify_command() { - command().debug_assert(); - } - - fn get_lint_matches(arg: &str) -> ArgMatches { - let matches = command().try_get_matches_from(arg.split(' ')).unwrap(); - let matches = matches.subcommand_matches("lint"); - assert!(matches.is_some()); - matches.unwrap().clone() - } - - #[test] - fn test_lint_default() { - let matches = get_lint_matches("oxc lint ."); - assert_eq!(matches.get_one::("path"), Some(&PathBuf::from("."))); - assert_eq!(matches.get_flag("fix"), false); - assert_eq!(matches.get_flag("quiet"), false); - assert_eq!(matches.get_one::("ignore-path"), None); - assert_eq!(matches.get_flag("no-ignore"), false); - assert_eq!(matches.get_one::("ignore-pattern"), None); - assert_eq!(matches.get_one::("max-warnings"), None); - } - - #[test] - fn test_lint_path() { - let matches = get_lint_matches("oxc lint ."); - assert_eq!(matches.get_one::("path"), Some(&PathBuf::from("."))); - } - - #[test] - fn test_lint_multiple_paths() { - let matches = get_lint_matches("oxc lint foo bar baz"); - assert_eq!( - matches.get_many::("path").unwrap().collect::>(), - [&PathBuf::from("foo"), &PathBuf::from("bar"), &PathBuf::from("baz")] - ); - } - - #[test] - fn test_lint_check_path() { - let matches = get_lint_matches("oxc check /path/to/dir"); - assert_eq!(matches.get_one::("path"), Some(&PathBuf::from("/path/to/dir"))); - } - - #[test] - fn test_lint_quiet_true() { - let matches = get_lint_matches("oxc lint foo.js --quiet"); - assert!(matches.get_flag("quiet")); - } - - #[test] - fn test_lint_fix_true() { - let matches = get_lint_matches("oxc lint foo.js --fix"); - assert!(matches.get_flag("fix")); - } - - #[test] - fn test_lint_max_warnings() { - let matches = get_lint_matches("oxc lint --max-warnings 10 foo.js"); - assert_eq!(matches.get_one::("max-warnings"), Some(&10)); - } - - #[test] - fn test_lint_ignore_path() { - let matches = get_lint_matches("oxc lint --ignore-path .gitignore foo.js"); - assert_eq!(matches.get_one::("ignore-path"), Some(&".gitignore".to_string())); - } - - #[test] - fn test_lint_no_ignore() { - let matches = get_lint_matches("oxc lint --no-ignore foo.js"); - assert!(matches.get_flag("no-ignore")); - } - - #[test] - fn test_lint_single_ignore_pattern() { - let matches = get_lint_matches("oxc lint --ignore-pattern \"./test\" foo.js"); - assert_eq!(matches.get_one::("ignore-pattern"), Some(&"\"./test\"".to_string())); - } - - #[test] - fn test_lint_multiple_ignore_pattern() { - let matches = get_lint_matches( - "oxc lint --ignore-pattern \"./test\" --ignore-pattern \"bar.js\" foo.js", - ); - let ignore_pattern = matches.get_many::("ignore-pattern").unwrap(); - let mut compare = vec![]; - for pattern in ignore_pattern { - compare.push(pattern); - } - assert_eq!(compare, vec!["\"./test\"", "\"bar.js\""]); - } -} diff --git a/crates/oxc_cli/src/lib.rs b/crates/oxc_cli/src/lib.rs index 55b13484b..7a7e5412f 100644 --- a/crates/oxc_cli/src/lib.rs +++ b/crates/oxc_cli/src/lib.rs @@ -1,180 +1,25 @@ -mod command; // mod git; -mod options; +mod lint; mod result; mod walk; -use std::{ - fs, - io::{BufWriter, Write}, - path::{Path, PathBuf}, - rc::Rc, - sync::{mpsc, Arc}, +use clap::Command; + +use crate::lint::lint_command; +pub use crate::{ + lint::{LintOptions, LintRunner}, + result::CliRunResult, + walk::Walk, }; -use miette::NamedSource; -use oxc_allocator::Allocator; -use oxc_ast::SourceType; -use oxc_diagnostics::{Error, MinifiedFileError, Severity}; -use oxc_linter::{Fixer, Linter}; -use oxc_parser::Parser; -use oxc_semantic::SemanticBuilder; - -pub use crate::{command::command, options::CliOptions, result::CliRunResult, walk::Walk}; - -pub struct Cli { - pub cli_options: CliOptions, -} - -impl Cli { - #[must_use] - pub fn new(cli_options: CliOptions) -> Self { - Self { cli_options } - } - - /// # Panics - /// - /// * When `mpsc::channel` fails to send. - #[must_use] - pub fn lint(&self) -> CliRunResult { - let now = std::time::Instant::now(); - - let (tx_error, rx_error) = mpsc::channel::<(PathBuf, Vec)>(); - let (tx_path, rx_path) = mpsc::channel::>(); - - let mut number_of_files = 0; - rayon::join( - || { - let paths = self - .cli_options - .paths - .iter() - .flat_map(|path| Walk::new(path, &self.cli_options).iter()) - .filter(|path| { - if self.cli_options.no_ignore { - return true; - } - for pattern in &self.cli_options.ignore_pattern { - if pattern.matches_path(path) { - return false; - } - } - true - }); - for path in paths { - number_of_files += 1; - tx_path.send(path).unwrap(); - } - drop(tx_path); - }, - move || { - let fix = self.cli_options.fix; - while let Ok(path) = rx_path.recv() { - let tx_error = tx_error.clone(); - rayon::spawn(move || { - if let Some(diagnostics) = Self::lint_path(&path, fix) { - tx_error.send(diagnostics).unwrap(); - } - drop(tx_error); - }); - } - }, - ); - - let mut buf_writer = BufWriter::new(std::io::stdout()); - let mut number_of_warnings = 0; - let mut number_of_diagnostics = 0; - - while let Ok((path, diagnostics)) = rx_error.recv() { - number_of_diagnostics += diagnostics.len(); - for diagnostic in diagnostics { - if diagnostic.severity() == Some(Severity::Warning) { - number_of_warnings += 1; - // The --quiet flag follows ESLint's --quiet behavior as documented here: https://eslint.org/docs/latest/use/command-line-interface#--quiet - // Note that it does not disable ALL diagnostics, only Warning diagnostics - if self.cli_options.quiet { - continue; - } - - if let Some(max_warnings) = self.cli_options.max_warnings { - if number_of_warnings > max_warnings { - continue; - } - } - } - - let output = format!("{diagnostic:?}"); - // Skip large output and print only once - if output.lines().any(|line| line.len() >= 400) { - let minified_diagnostic = Error::new(MinifiedFileError(path.clone())); - buf_writer.write_all(format!("{minified_diagnostic:?}").as_bytes()).unwrap(); - break; - } - buf_writer.write_all(output.as_bytes()).unwrap(); - } - } - - buf_writer.flush().unwrap(); - - CliRunResult::LintResult { - duration: now.elapsed(), - number_of_files, - number_of_diagnostics, - number_of_warnings, - max_warnings_exceeded: self - .cli_options - .max_warnings - .map_or(false, |max_warnings| number_of_warnings > max_warnings), - } - } - - fn lint_path(path: &Path, fix: bool) -> Option<(PathBuf, Vec)> { - let source_text = fs::read_to_string(path).unwrap_or_else(|_| panic!("{path:?} not found")); - let allocator = Allocator::default(); - let source_type = - SourceType::from_path(path).unwrap_or_else(|_| panic!("incorrect {path:?}")); - let parser_source_text = source_text.clone(); - let ret = Parser::new(&allocator, &parser_source_text, source_type).parse(); - - if !ret.errors.is_empty() { - return Some(Self::wrap_diagnostics(path, &source_text, ret.errors)); - }; - - let program = allocator.alloc(ret.program); - let trivias = Rc::new(ret.trivias); - let semantic_ret = SemanticBuilder::new(source_type).build(program, &trivias); - - if !semantic_ret.errors.is_empty() { - return Some(Self::wrap_diagnostics(path, &source_text, semantic_ret.errors)); - }; - - let result = Linter::new().with_fix(fix).run(&Rc::new(semantic_ret.semantic), &source_text); - - if result.is_empty() { - return None; - } - - if fix { - let fix_result = Fixer::new(&source_text, result).fix(); - fs::write(path, fix_result.fixed_code.as_bytes()).unwrap(); - let errors = fix_result.messages.into_iter().map(|m| m.error).collect(); - return Some(Self::wrap_diagnostics(path, &source_text, errors)); - } - - let errors = result.into_iter().map(|diagnostic| diagnostic.error).collect(); - Some(Self::wrap_diagnostics(path, &source_text, errors)) - } - - fn wrap_diagnostics( - path: &Path, - source_text: &str, - diagnostics: Vec, - ) -> (PathBuf, Vec) { - let source = Arc::new(NamedSource::new(path.to_string_lossy(), source_text.to_owned())); - let diagnostics = diagnostics - .into_iter() - .map(|diagnostic| diagnostic.with_source_code(Arc::clone(&source))) - .collect(); - (path.to_path_buf(), diagnostics) - } +#[must_use] +pub fn command() -> Command { + Command::new("oxc") + .bin_name("oxc") + .version("alpha") + .author("Boshen") + .about("The JavaScript Oxidation Compiler") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(lint_command()) } diff --git a/crates/oxc_cli/src/lint/command.rs b/crates/oxc_cli/src/lint/command.rs new file mode 100644 index 000000000..56a665e23 --- /dev/null +++ b/crates/oxc_cli/src/lint/command.rs @@ -0,0 +1,59 @@ +use clap::{builder::ValueParser, Arg, ArgAction, Command}; + +pub fn lint_command() -> Command { + Command::new("lint") + .alias("check") + .about("Lint this repository.") + .arg_required_else_help(true) + .arg( + Arg::new("path") + .value_name("PATH") + .num_args(1..) + .required(true) + .value_parser(ValueParser::path_buf()) + .help("File or Directory paths to scan. Directories are scanned recursively.") + ) + .arg( + Arg::new("fix") + .long("fix") + .required(false) + .action(ArgAction::SetTrue) + .help("This option allows you to enable oxc to fix as many issues as possible. If enabled, only unfixed issues are reported in the output") + ) + .arg( + Arg::new("quiet") + .long("quiet") + .required(false) + .action(ArgAction::SetTrue) + .help("This option allows you to disable reporting on warnings. If you enable this option, only errors are reported by oxc_lint.") + ) + .arg( + Arg::new("ignore-path") + .long("ignore-path") + .required(false) + .value_parser(ValueParser::path_buf()) + .help("This option allows you to specify the file to use as your .eslintignore.") + ) + .arg( + Arg::new("no-ignore") + .long("no-ignore") + .required(false) + .action(ArgAction::SetTrue) + .help("Disables excluding of files from .eslintignore files, --ignore-path flags, --ignore-pattern flags.") + ) + .arg( + Arg::new("ignore-pattern") + .long("ignore-pattern") + .required(false) + .action(ArgAction::Append) + .help("This option allows you to specify patterns of files to ignore (in addition to those in .eslintignore).") + ) + .arg( + Arg::new("max-warnings") + .long("max-warnings") + .value_parser(clap::value_parser!(usize)) + .default_value(None) + .required(false) + .help("This option allows you to specify a warning threshold, which can be used to force oxc_lint to exit with an error status if there are too many warning-level rule violations in your project.") + ) +} diff --git a/crates/oxc_cli/src/lint/mod.rs b/crates/oxc_cli/src/lint/mod.rs new file mode 100644 index 000000000..d04a0ac8c --- /dev/null +++ b/crates/oxc_cli/src/lint/mod.rs @@ -0,0 +1,120 @@ +use std::path::PathBuf; +mod command; +mod runner; + +use clap::ArgMatches; + +pub use self::{command::lint_command, runner::LintRunner}; + +pub struct LintOptions { + pub paths: Vec, + pub fix: bool, + pub quiet: bool, + pub ignore_path: PathBuf, + pub no_ignore: bool, + pub ignore_pattern: Vec, + pub max_warnings: Option, +} + +impl<'a> From<&'a ArgMatches> for LintOptions { + fn from(matches: &'a ArgMatches) -> Self { + Self { + paths: matches.get_many("path").map_or_else( + || vec![PathBuf::from(".")], + |paths| paths.into_iter().cloned().collect(), + ), + fix: matches.get_flag("fix"), + quiet: matches.get_flag("quiet"), + ignore_path: matches + .get_one::("ignore-path") + .map_or_else(|| PathBuf::from(".eslintignore"), Clone::clone), + no_ignore: matches.get_flag("no-ignore"), + ignore_pattern: matches + .get_many::("ignore-pattern") + .map(|patterns| patterns.into_iter().cloned().collect()) + .unwrap_or_default(), + max_warnings: matches.get_one("max-warnings").copied(), + } + } +} + +#[cfg(test)] +mod test { + use std::path::PathBuf; + + use super::{lint_command, LintOptions}; + + #[test] + fn verify_command() { + lint_command().debug_assert(); + } + + fn get_lint_options(arg: &str) -> LintOptions { + let matches = lint_command().try_get_matches_from(arg.split(' ')).unwrap(); + LintOptions::from(&matches) + } + + #[test] + fn test_default() { + let options = get_lint_options("lint ."); + assert_eq!(options.paths, vec![PathBuf::from(".")]); + assert!(!options.fix); + assert!(!options.quiet); + assert_eq!(options.ignore_path, PathBuf::from(".eslintignore")); + assert!(!options.no_ignore); + assert!(options.ignore_pattern.is_empty()); + assert_eq!(options.max_warnings, None); + } + + #[test] + fn test_multiple_paths() { + let options = get_lint_options("lint foo bar baz"); + assert_eq!( + options.paths, + [PathBuf::from("foo"), PathBuf::from("bar"), PathBuf::from("baz")] + ); + } + + #[test] + fn test_quiet_true() { + let options = get_lint_options("lint foo.js --quiet"); + assert!(options.quiet); + } + + #[test] + fn test_fix_true() { + let options = get_lint_options("lint foo.js --fix"); + assert!(options.fix); + } + + #[test] + fn test_max_warnings() { + let options = get_lint_options("lint --max-warnings 10 foo.js"); + assert_eq!(options.max_warnings, Some(10)); + } + + #[test] + fn test_ignore_path() { + let options = get_lint_options("lint --ignore-path .xxx foo.js"); + assert_eq!(options.ignore_path, PathBuf::from(".xxx")); + } + + #[test] + fn test_no_ignore() { + let options = get_lint_options("lint --no-ignore foo.js"); + assert!(options.no_ignore); + } + + #[test] + fn test_single_ignore_pattern() { + let options = get_lint_options("lint --ignore-pattern ./test foo.js"); + assert_eq!(options.ignore_pattern, vec![String::from("./test")]); + } + + #[test] + fn test_multiple_ignore_pattern() { + let options = + get_lint_options("lint --ignore-pattern ./test --ignore-pattern bar.js foo.js"); + assert_eq!(options.ignore_pattern, vec![String::from("./test"), String::from("bar.js")]); + } +} diff --git a/crates/oxc_cli/src/lint/runner.rs b/crates/oxc_cli/src/lint/runner.rs new file mode 100644 index 000000000..4af012fe8 --- /dev/null +++ b/crates/oxc_cli/src/lint/runner.rs @@ -0,0 +1,178 @@ +use std::{ + fs, + io::{BufWriter, Write}, + path::{Path, PathBuf}, + rc::Rc, + sync::{mpsc, Arc}, +}; + +use miette::NamedSource; +use oxc_allocator::Allocator; +use oxc_ast::SourceType; +use oxc_diagnostics::{Error, MinifiedFileError, Severity}; +use oxc_linter::{Fixer, Linter}; +use oxc_parser::Parser; +use oxc_semantic::SemanticBuilder; + +use super::LintOptions; +use crate::{CliRunResult, Walk}; + +pub struct LintRunner { + options: LintOptions, +} + +impl LintRunner { + #[must_use] + pub fn new(options: LintOptions) -> Self { + Self { options } + } + + /// # Panics + /// + /// * When `mpsc::channel` fails to send. + #[must_use] + pub fn run(&self) -> CliRunResult { + let now = std::time::Instant::now(); + + let mut number_of_files = 0; + let mut number_of_warnings = 0; + let mut number_of_diagnostics = 0; + + let (tx_error, rx_error) = mpsc::channel::<(PathBuf, Vec)>(); + + self.process_paths(&mut number_of_files, tx_error); + self.process_diagnostics(&mut number_of_warnings, &mut number_of_diagnostics, &rx_error); + + CliRunResult::LintResult { + duration: now.elapsed(), + number_of_files, + number_of_diagnostics, + number_of_warnings, + max_warnings_exceeded: self + .options + .max_warnings + .map_or(false, |max_warnings| number_of_warnings > max_warnings), + } + } + + fn process_paths( + &self, + number_of_files: &mut usize, + tx_error: mpsc::Sender<(PathBuf, Vec)>, + ) { + let (tx_path, rx_path) = mpsc::channel::>(); + let options = &self.options; + let fix = options.fix; + rayon::join( + move || { + options.paths.iter().flat_map(|path| Walk::new(path, options).iter()).for_each( + |path| { + *number_of_files += 1; + tx_path.send(path).unwrap(); + }, + ) + }, + move || { + while let Ok(path) = rx_path.recv() { + let tx_error = tx_error.clone(); + rayon::spawn(move || { + if let Some(diagnostics) = Self::lint_path(&path, fix) { + tx_error.send(diagnostics).unwrap(); + } + drop(tx_error); + }); + } + }, + ); + } + + fn process_diagnostics( + &self, + number_of_warnings: &mut usize, + number_of_diagnostics: &mut usize, + rx_error: &mpsc::Receiver<(PathBuf, Vec)>, + ) { + let mut buf_writer = BufWriter::new(std::io::stdout()); + + while let Ok((path, diagnostics)) = rx_error.recv() { + *number_of_diagnostics += diagnostics.len(); + for diagnostic in diagnostics { + if diagnostic.severity() == Some(Severity::Warning) { + *number_of_warnings += 1; + // The --quiet flag follows ESLint's --quiet behavior as documented here: https://eslint.org/docs/latest/use/command-line-interface#--quiet + // Note that it does not disable ALL diagnostics, only Warning diagnostics + if self.options.quiet { + continue; + } + + if let Some(max_warnings) = self.options.max_warnings { + if *number_of_warnings > max_warnings { + continue; + } + } + } + + let output = format!("{diagnostic:?}"); + // Skip large output and print only once + if output.lines().any(|line| line.len() >= 400) { + let minified_diagnostic = Error::new(MinifiedFileError(path.clone())); + buf_writer.write_all(format!("{minified_diagnostic:?}").as_bytes()).unwrap(); + break; + } + buf_writer.write_all(output.as_bytes()).unwrap(); + } + } + + buf_writer.flush().unwrap(); + } + + fn lint_path(path: &Path, fix: bool) -> Option<(PathBuf, Vec)> { + let source_text = fs::read_to_string(path).unwrap_or_else(|_| panic!("{path:?} not found")); + let allocator = Allocator::default(); + let source_type = + SourceType::from_path(path).unwrap_or_else(|_| panic!("incorrect {path:?}")); + let parser_source_text = source_text.clone(); + let ret = Parser::new(&allocator, &parser_source_text, source_type).parse(); + + if !ret.errors.is_empty() { + return Some(Self::wrap_diagnostics(path, &source_text, ret.errors)); + }; + + let program = allocator.alloc(ret.program); + let trivias = Rc::new(ret.trivias); + let semantic_ret = SemanticBuilder::new(source_type).build(program, &trivias); + + if !semantic_ret.errors.is_empty() { + return Some(Self::wrap_diagnostics(path, &source_text, semantic_ret.errors)); + }; + + let result = Linter::new().with_fix(fix).run(&Rc::new(semantic_ret.semantic), &source_text); + + if result.is_empty() { + return None; + } + + if fix { + let fix_result = Fixer::new(&source_text, result).fix(); + fs::write(path, fix_result.fixed_code.as_bytes()).unwrap(); + let errors = fix_result.messages.into_iter().map(|m| m.error).collect(); + return Some(Self::wrap_diagnostics(path, &source_text, errors)); + } + + let errors = result.into_iter().map(|diagnostic| diagnostic.error).collect(); + Some(Self::wrap_diagnostics(path, &source_text, errors)) + } + + fn wrap_diagnostics( + path: &Path, + source_text: &str, + diagnostics: Vec, + ) -> (PathBuf, Vec) { + let source = Arc::new(NamedSource::new(path.to_string_lossy(), source_text.to_owned())); + let diagnostics = diagnostics + .into_iter() + .map(|diagnostic| diagnostic.with_source_code(Arc::clone(&source))) + .collect(); + (path.to_path_buf(), diagnostics) + } +} diff --git a/crates/oxc_cli/src/main.rs b/crates/oxc_cli/src/main.rs index 51940b1d7..fc4474dcd 100644 --- a/crates/oxc_cli/src/main.rs +++ b/crates/oxc_cli/src/main.rs @@ -8,7 +8,7 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; -use oxc_cli::{command, Cli, CliOptions, CliRunResult}; +use oxc_cli::{command, CliRunResult, LintOptions, LintRunner}; use oxc_diagnostics::miette; fn main() -> CliRunResult { @@ -17,20 +17,16 @@ fn main() -> CliRunResult { miette::set_hook(Box::new(|_| Box::new(miette::MietteHandlerOpts::new().tab_width(4).build()))) .unwrap(); - if let Some((subcommand, matches)) = command().get_matches().subcommand() { - let cli_options = CliOptions::try_from(matches); - if let Ok(cli_options) = cli_options { - // if cli_options.fix { - // Git::new().verify()?; - // } + let matches = command().get_matches(); + let Some((subcommand, matches)) = matches.subcommand() else { + return CliRunResult::None + }; - let cli = Cli::new(cli_options); - - if subcommand == "lint" { - return cli.lint(); - } - return CliRunResult::None; + match subcommand { + "lint" => { + let options = LintOptions::from(matches); + LintRunner::new(options).run() } + _ => CliRunResult::None, } - CliRunResult::None } diff --git a/crates/oxc_cli/src/options.rs b/crates/oxc_cli/src/options.rs deleted file mode 100644 index 6aafecce2..000000000 --- a/crates/oxc_cli/src/options.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::path::PathBuf; - -use clap::ArgMatches; -use glob::Pattern; - -pub struct CliOptions { - pub quiet: bool, - pub fix: bool, - pub max_warnings: Option, - pub paths: Vec, - pub ignore_path: String, - pub no_ignore: bool, - pub ignore_pattern: Vec, -} - -impl<'a> TryFrom<&'a ArgMatches> for CliOptions { - type Error = &'a str; - - fn try_from(matches: &ArgMatches) -> Result { - let mut paths = vec![]; - - for path in matches.get_many::("path").unwrap() { - let glob_result = - glob::glob(&path.to_string_lossy()).map_err(|_| "Failed to read glob pattern")?; - let globbed = glob_result - .map(|path_result| path_result.map_err(|_| "Failed to read path")) - .collect::, &str>>()?; - - if globbed.is_empty() && path.canonicalize().is_err() { - return Err("Unable to find globbed files"); - } - - paths.extend(globbed); - } - - let ignore_path = get_ignore_path(matches); - let no_ignore = matches.get_flag("no-ignore"); - let ignore_pattern = get_ignore_pattern(matches); - - Ok(Self { - quiet: matches.get_flag("quiet"), - fix: matches.get_flag("fix"), - max_warnings: matches.get_one("max-warnings").copied(), - paths, - ignore_path, - no_ignore, - ignore_pattern, - }) - } -} - -fn get_ignore_path(matches: &ArgMatches) -> String { - matches.get_one::("ignore-path").map_or(".eslintignore".to_string(), ToOwned::to_owned) -} - -fn get_ignore_pattern(matches: &ArgMatches) -> Vec { - let mut result = vec![]; - let Some(ignore_pattern) = matches.get_many::("ignore-pattern") else {return result}; - for pattern in ignore_pattern { - if let Ok(pattern) = Pattern::new(pattern) { - result.push(pattern); - } - } - - result -} diff --git a/crates/oxc_cli/src/walk.rs b/crates/oxc_cli/src/walk.rs index 6600d4a9c..8cd2901e6 100644 --- a/crates/oxc_cli/src/walk.rs +++ b/crates/oxc_cli/src/walk.rs @@ -3,14 +3,14 @@ use std::path::Path; use ignore::{DirEntry, WalkBuilder}; use oxc_ast::VALID_EXTENSIONS; -use crate::CliOptions; +use crate::LintOptions; pub struct Walk { inner: ignore::Walk, } impl Walk { - pub fn new>(path: P, options: &CliOptions) -> Self { + pub fn new>(path: P, options: &LintOptions) -> Self { let mut inner = WalkBuilder::new(path); if !options.no_ignore { inner.add_custom_ignore_filename(&options.ignore_path);