mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
refactor(cli,linter): move the lint runner from cli to linter (#764)
This commit is contained in:
parent
1781318f44
commit
324acfccc8
5 changed files with 141 additions and 142 deletions
|
|
@ -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<Linter>,
|
||||
diagnostic_service: DiagnosticService,
|
||||
}
|
||||
|
||||
impl IsolatedLintHandler {
|
||||
pub(super) fn new(warning_options: &WarningOptions, linter: Arc<Linter>) -> 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<AtomicUsize>) {
|
||||
let (tx_path, rx_path) = mpsc::channel::<Box<Path>>();
|
||||
|
||||
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<Error>)> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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::<Box<Path>>();
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
60
crates/oxc_linter/src/service.rs
Normal file
60
crates/oxc_linter/src/service.rs
Normal file
|
|
@ -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<Linter>,
|
||||
}
|
||||
|
||||
impl LintService {
|
||||
pub fn new(linter: Arc<Linter>) -> Self {
|
||||
Self { linter }
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
pub fn run(&self, path: &Path, source_text: &str) -> Option<Vec<Error>> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue