From 324acfccc86655d20daa56b2d01c91491897c259 Mon Sep 17 00:00:00 2001 From: Boshen Date: Sun, 20 Aug 2023 00:59:49 +0800 Subject: [PATCH] refactor(cli,linter): move the lint runner from cli to linter (#764) --- crates/oxc_cli/src/lint/isolated_handler.rs | 134 -------------------- crates/oxc_cli/src/lint/mod.rs | 85 ++++++++++++- crates/oxc_linter/Cargo.toml | 2 +- crates/oxc_linter/src/lib.rs | 2 + crates/oxc_linter/src/service.rs | 60 +++++++++ 5 files changed, 141 insertions(+), 142 deletions(-) delete mode 100644 crates/oxc_cli/src/lint/isolated_handler.rs create mode 100644 crates/oxc_linter/src/service.rs diff --git a/crates/oxc_cli/src/lint/isolated_handler.rs b/crates/oxc_cli/src/lint/isolated_handler.rs deleted file mode 100644 index 1696ae022..000000000 --- a/crates/oxc_cli/src/lint/isolated_handler.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::{ - fs, - path::{Path, PathBuf}, - rc::Rc, - sync::{ - atomic::{AtomicUsize, Ordering}, - mpsc, Arc, - }, -}; - -use oxc_allocator::Allocator; -use oxc_diagnostics::{DiagnosticService, Error}; -use oxc_linter::{Fixer, LintContext, Linter}; -use oxc_parser::Parser; -use oxc_semantic::SemanticBuilder; -use oxc_span::SourceType; - -use crate::{CliRunResult, Walk, WarningOptions}; - -pub struct IsolatedLintHandler { - linter: Arc, - diagnostic_service: DiagnosticService, -} - -impl IsolatedLintHandler { - pub(super) fn new(warning_options: &WarningOptions, linter: Arc) -> Self { - let diagnostic_service = DiagnosticService::default() - .with_quiet(warning_options.quiet) - .with_max_warnings(warning_options.max_warnings); - Self { linter, diagnostic_service } - } - - /// # Panics - /// - /// * When `mpsc::channel` fails to send. - pub(super) fn run(&self, walk: Walk) -> CliRunResult { - let now = std::time::Instant::now(); - - let number_of_files = Arc::new(AtomicUsize::new(0)); - - self.process_paths(walk, &number_of_files); - self.diagnostic_service.run(); - - CliRunResult::LintResult { - duration: now.elapsed(), - number_of_rules: self.linter.number_of_rules(), - number_of_files: number_of_files.load(Ordering::Relaxed), - number_of_warnings: self.diagnostic_service.warnings_count(), - number_of_errors: self.diagnostic_service.errors_count(), - max_warnings_exceeded: self.diagnostic_service.max_warnings_exceeded(), - } - } - - fn process_paths(&self, walk: Walk, number_of_files: &Arc) { - let (tx_path, rx_path) = mpsc::channel::>(); - - let number_of_files = Arc::clone(number_of_files); - rayon::spawn(move || { - let mut count = 0; - walk.iter().for_each(|path| { - count += 1; - tx_path.send(path).unwrap(); - }); - number_of_files.store(count, Ordering::Relaxed); - }); - - let mut processing = 0; - let linter = Arc::clone(&self.linter); - let tx_error = self.diagnostic_service.sender().clone(); - rayon::spawn(move || { - while let Ok(path) = rx_path.recv() { - processing += 1; - let tx_error = tx_error.clone(); - let linter = Arc::clone(&linter); - rayon::spawn(move || { - if let Some(diagnostics) = Self::lint_path(&linter, &path) { - tx_error.send(Some(diagnostics)).unwrap(); - } - processing -= 1; - if processing == 0 { - tx_error.send(None).unwrap(); - } - }); - } - }); - } - - fn lint_path(linter: &Linter, path: &Path) -> Option<(PathBuf, Vec)> { - let source_text = - fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read {path:?}")); - let allocator = Allocator::default(); - let source_type = - SourceType::from_path(path).unwrap_or_else(|_| panic!("Incorrect {path:?}")); - let ret = Parser::new(&allocator, &source_text, source_type) - .allow_return_outside_function(true) - .parse(); - - if !ret.errors.is_empty() { - return Some(DiagnosticService::wrap_diagnostics(path, &source_text, ret.errors)); - }; - - let program = allocator.alloc(ret.program); - let semantic_ret = SemanticBuilder::new(&source_text, source_type) - .with_trivias(ret.trivias) - .with_check_syntax_error(true) - .with_module_record_builder(true) - .build(program); - - if !semantic_ret.errors.is_empty() { - return Some(DiagnosticService::wrap_diagnostics( - path, - &source_text, - semantic_ret.errors, - )); - }; - - let lint_ctx = LintContext::new(&Rc::new(semantic_ret.semantic)); - let result = linter.run(lint_ctx); - - if result.is_empty() { - return None; - } - - if linter.options().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(DiagnosticService::wrap_diagnostics(path, &source_text, errors)); - } - - let errors = result.into_iter().map(|diagnostic| diagnostic.error).collect(); - Some(DiagnosticService::wrap_diagnostics(path, &source_text, errors)) - } -} diff --git a/crates/oxc_cli/src/lint/mod.rs b/crates/oxc_cli/src/lint/mod.rs index 478fa7c2d..d1ce61f12 100644 --- a/crates/oxc_cli/src/lint/mod.rs +++ b/crates/oxc_cli/src/lint/mod.rs @@ -1,10 +1,18 @@ mod error; -mod isolated_handler; -use std::{io::BufWriter, sync::Arc}; +use std::{ + fs, + io::BufWriter, + path::Path, + sync::{ + atomic::{AtomicUsize, Ordering}, + mpsc, Arc, + }, +}; -pub use self::{error::Error, isolated_handler::IsolatedLintHandler}; +pub use self::error::Error; +use oxc_diagnostics::DiagnosticService; use oxc_index::assert_impl_all; use oxc_linter::{LintOptions, Linter}; @@ -37,15 +45,78 @@ impl Runner for LintRunner { fix_options, misc_options, } = self.options; - let walk = Walk::new(&paths, &ignore_options); + + let now = std::time::Instant::now(); + let filter = if filter.is_empty() { LintOptions::default().filter } else { filter }; + let lint_options = LintOptions { filter, fix: fix_options.fix, timing: misc_options.timing }; + let linter = Arc::new(Linter::from_options(lint_options)); - let result = IsolatedLintHandler::new(&warning_options, Arc::clone(&linter)).run(walk); - linter.print_execution_times_if_enable(); + let diagnostic_service = DiagnosticService::default() + .with_quiet(warning_options.quiet) + .with_max_warnings(warning_options.max_warnings); - result + let number_of_files = Arc::new(AtomicUsize::new(0)); + let (tx_path, rx_path) = mpsc::channel::>(); + + rayon::spawn({ + let walk = Walk::new(&paths, &ignore_options); + let number_of_files = Arc::clone(&number_of_files); + move || { + let mut count = 0; + walk.iter().for_each(|path| { + count += 1; + tx_path.send(path).unwrap(); + }); + number_of_files.store(count, Ordering::Relaxed); + } + }); + + let processing = Arc::new(AtomicUsize::new(0)); + rayon::spawn({ + let linter = Arc::clone(&linter); + let tx_error = diagnostic_service.sender().clone(); + let processing = Arc::clone(&processing); + move || { + while let Ok(path) = rx_path.recv() { + processing.fetch_add(1, Ordering::Relaxed); + let tx_error = tx_error.clone(); + let linter = Arc::clone(&linter); + let processing = Arc::clone(&processing); + rayon::spawn(move || { + let source_text = fs::read_to_string(&path) + .unwrap_or_else(|_| panic!("Failed to read {path:?}")); + + let diagnostics = oxc_linter::LintService::new(linter) + .run(&path, &source_text) + .map(|errors| { + DiagnosticService::wrap_diagnostics(&path, &source_text, errors) + }); + + if let Some(diagnostics) = diagnostics { + tx_error.send(Some(diagnostics)).unwrap(); + } + processing.fetch_sub(1, Ordering::Relaxed); + if processing.load(Ordering::Relaxed) == 0 { + tx_error.send(None).unwrap(); + } + }); + } + } + }); + + diagnostic_service.run(); + + CliRunResult::LintResult { + duration: now.elapsed(), + number_of_rules: linter.number_of_rules(), + number_of_files: number_of_files.load(Ordering::Relaxed), + number_of_warnings: diagnostic_service.warnings_count(), + number_of_errors: diagnostic_service.errors_count(), + max_warnings_exceeded: diagnostic_service.max_warnings_exceeded(), + } } } diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index 1845e8327..d15b63ffc 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -14,6 +14,7 @@ categories.workspace = true [dependencies] oxc_allocator = { workspace = true } +oxc_parser = { workspace = true } oxc_span = { workspace = true } oxc_ast = { workspace = true } oxc_diagnostics = { workspace = true } @@ -34,7 +35,6 @@ rust-lapper = "1.1.0" once_cell = "1.18.0" [dev-dependencies] -oxc_parser = { workspace = true } miette = { workspace = true, features = ["fancy-no-backtrace"] } insta = { version = "1.31.0", features = ["glob"] } diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index 60008efde..286a8580e 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -13,6 +13,7 @@ mod options; pub mod rule; mod rule_timer; mod rules; +mod service; use std::{self, fs, io::Write, rc::Rc, time::Duration}; @@ -24,6 +25,7 @@ pub use crate::{ context::LintContext, options::{AllowWarnDeny, LintOptions}, rule::RuleCategory, + service::LintService, }; pub(crate) use rules::{RuleEnum, RULES}; diff --git a/crates/oxc_linter/src/service.rs b/crates/oxc_linter/src/service.rs new file mode 100644 index 000000000..998fc00cb --- /dev/null +++ b/crates/oxc_linter/src/service.rs @@ -0,0 +1,60 @@ +use std::{fs, path::Path, rc::Rc, sync::Arc}; + +use oxc_allocator::Allocator; +use oxc_diagnostics::Error; +use oxc_parser::Parser; +use oxc_semantic::SemanticBuilder; +use oxc_span::SourceType; + +use crate::{Fixer, LintContext, Linter}; + +pub struct LintService { + linter: Arc, +} + +impl LintService { + pub fn new(linter: Arc) -> Self { + Self { linter } + } + + /// # Panics + pub fn run(&self, path: &Path, source_text: &str) -> Option> { + let allocator = Allocator::default(); + let source_type = + SourceType::from_path(path).unwrap_or_else(|_| panic!("Incorrect {path:?}")); + let ret = Parser::new(&allocator, source_text, source_type) + .allow_return_outside_function(true) + .parse(); + + if !ret.errors.is_empty() { + return Some(ret.errors); + }; + + let program = allocator.alloc(ret.program); + let semantic_ret = SemanticBuilder::new(source_text, source_type) + .with_trivias(ret.trivias) + .with_check_syntax_error(true) + .with_module_record_builder(true) + .build(program); + + if !semantic_ret.errors.is_empty() { + return Some(semantic_ret.errors); + }; + + let lint_ctx = LintContext::new(&Rc::new(semantic_ret.semantic)); + let result = self.linter.run(lint_ctx); + + if result.is_empty() { + return None; + } + + if self.linter.options().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(errors); + } + + Some(result.into_iter().map(|diagnostic| diagnostic.error).collect()) + } +}