refactor(cli,linter): move the lint runner from cli to linter (#764)

This commit is contained in:
Boshen 2023-08-20 00:59:49 +08:00 committed by GitHub
parent 1781318f44
commit 324acfccc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 142 deletions

View file

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

View file

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

View file

@ -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"] }

View file

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

View 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())
}
}