refactor(cli,linter): move path processing logic from cli to linter (#766)

This commit is contained in:
Boshen 2023-08-20 15:12:08 +08:00 committed by GitHub
parent 3b3babed2e
commit a9a6bb800c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 106 additions and 106 deletions

4
Cargo.lock generated
View file

@ -1525,7 +1525,6 @@ version = "0.0.0"
dependencies = [
"bpaf",
"codespan-reporting",
"dashmap",
"ignore",
"jemallocator",
"miette",
@ -1535,11 +1534,9 @@ dependencies = [
"oxc_index",
"oxc_linter",
"oxc_parser",
"oxc_semantic",
"oxc_span",
"oxc_type_synthesis",
"rayon",
"rustc-hash",
]
[[package]]
@ -1641,6 +1638,7 @@ dependencies = [
"oxc_span",
"oxc_syntax",
"phf",
"rayon",
"regex",
"rust-lapper",
"rustc-hash",

View file

@ -28,17 +28,14 @@ oxc_diagnostics = { workspace = true }
oxc_index = { workspace = true }
oxc_linter = { workspace = true }
oxc_parser = { workspace = true }
oxc_semantic = { workspace = true }
oxc_span = { workspace = true }
oxc_type_synthesis = { workspace = true }
# TODO temp, for type check output, replace with Miette
codespan-reporting = "0.11.1"
dashmap = { workspace = true }
ignore = { workspace = true, features = ["simd-accel"] }
miette = { workspace = true, features = ["fancy-no-backtrace"] }
rayon = { workspace = true }
rustc-hash = { workspace = true }
bpaf = { workspace = true, features = ["derive", "autocomplete", "bright-color"] }
# git2 = { version = "0.16.1", default_features = false }

View file

@ -1,7 +1,6 @@
mod error;
use std::{
fs,
io::BufWriter,
path::Path,
sync::{
@ -48,10 +47,10 @@ impl Runner for LintRunner {
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 lint_options = LintOptions::default()
.with_filter(filter)
.with_fix(fix_options.fix)
.with_timing(misc_options.timing);
let linter = Arc::new(Linter::from_options(lint_options));
@ -60,6 +59,7 @@ impl Runner for LintRunner {
.with_max_warnings(warning_options.max_warnings);
let number_of_files = Arc::new(AtomicUsize::new(0));
let (tx_path, rx_path) = mpsc::channel::<Box<Path>>();
rayon::spawn({
@ -75,35 +75,12 @@ impl Runner for LintRunner {
}
});
let processing = Arc::new(AtomicUsize::new(0));
rayon::spawn({
let linter = Arc::clone(&linter);
let lint_service = oxc_linter::LintService::new(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();
}
});
lint_service.run_path(path, &tx_error);
}
}
});

View file

@ -7,7 +7,7 @@ mod service;
use std::path::PathBuf;
pub use crate::service::DiagnosticService;
pub use crate::service::{DiagnosticSender, DiagnosticService, DiagnosticTuple};
pub use graphic_reporter::{GraphicalReportHandler, GraphicalTheme};
pub use miette;
pub use thiserror;

View file

@ -23,6 +23,7 @@ oxc_semantic = { workspace = true }
oxc_syntax = { workspace = true }
oxc_formatter = { workspace = true }
rayon = { workspace = true }
lazy_static = { workspace = true } # used in oxc_macros
serde_json = { workspace = true }
regex = { workspace = true }

View file

@ -42,10 +42,6 @@ impl Linter {
.cloned()
.filter(|rule| rule.category() == RuleCategory::Correctness)
.collect::<Vec<_>>();
Self::from_rules(rules)
}
pub fn from_rules(rules: Vec<RuleEnum>) -> Self {
Self { rules, options: LintOptions::default() }
}
@ -54,6 +50,12 @@ impl Linter {
Self { rules, options }
}
#[must_use]
pub fn with_rules(mut self, rules: Vec<RuleEnum>) -> Self {
self.rules = rules;
self
}
pub fn rules(&self) -> &Vec<RuleEnum> {
&self.rules
}
@ -78,27 +80,6 @@ impl Linter {
self
}
pub fn from_json_str(s: &str) -> Self {
let rules = serde_json::from_str(s)
.ok()
.and_then(|v: serde_json::Value| v.get("rules").cloned())
.and_then(|v| v.as_object().cloned())
.map_or_else(
|| RULES.to_vec(),
|rules_config| {
RULES
.iter()
.map(|rule| {
let value = rules_config.get(rule.name());
rule.read_json(value.cloned())
})
.collect()
},
);
Self::from_rules(rules)
}
pub fn run<'a>(&self, ctx: LintContext<'a>) -> Vec<Message<'a>> {
let timing = self.options.timing;
let semantic = Rc::clone(ctx.semantic());

View file

@ -21,6 +21,28 @@ impl Default for LintOptions {
}
}
impl LintOptions {
#[must_use]
pub fn with_filter(mut self, filter: Vec<(AllowWarnDeny, String)>) -> Self {
if !filter.is_empty() {
self.filter = filter;
}
self
}
#[must_use]
pub fn with_fix(mut self, yes: bool) -> Self {
self.fix = yes;
self
}
#[must_use]
pub fn with_timing(mut self, yes: bool) -> Self {
self.timing = yes;
self
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum AllowWarnDeny {
Allow,

View file

@ -1,60 +1,93 @@
use std::{fs, path::Path, rc::Rc, sync::Arc};
use std::{
fs,
path::Path,
rc::Rc,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
use oxc_allocator::Allocator;
use oxc_diagnostics::Error;
use oxc_diagnostics::{DiagnosticSender, DiagnosticService};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
use crate::{Fixer, LintContext, Linter};
use crate::{Fixer, LintContext, Linter, Message};
pub struct LintService {
linter: Arc<Linter>,
processing: Arc<AtomicUsize>,
}
impl LintService {
pub fn new(linter: Arc<Linter>) -> Self {
Self { linter }
Self { linter, processing: Arc::new(AtomicUsize::new(0)) }
}
/// # Panics
pub fn run(&self, path: &Path, source_text: &str) -> Option<Vec<Error>> {
let allocator = Allocator::default();
pub fn run_path(&self, path: Box<Path>, tx_error: &DiagnosticSender) {
self.processing.fetch_add(1, Ordering::Relaxed);
let linter = Arc::clone(&self.linter);
let tx_error = tx_error.clone();
let processing = Arc::clone(&self.processing);
rayon::spawn(move || {
let allocator = Allocator::default();
let source_text =
fs::read_to_string(&path).unwrap_or_else(|_| panic!("Failed to read {path:?}"));
let mut messages = Self::run_source(&linter, &path, &allocator, &source_text, true);
if linter.options().fix {
let fix_result = Fixer::new(&source_text, messages).fix();
fs::write(&path, fix_result.fixed_code.as_bytes()).unwrap();
messages = fix_result.messages;
}
let errors = messages.into_iter().map(|m| m.error).collect();
let diagnostics = DiagnosticService::wrap_diagnostics(&path, &source_text, errors);
if !diagnostics.1.is_empty() {
tx_error.send(Some(diagnostics)).unwrap();
}
processing.fetch_sub(1, Ordering::Relaxed);
if processing.load(Ordering::Relaxed) == 0 {
tx_error.send(None).unwrap();
}
});
}
pub(crate) fn run_source<'a>(
linter: &Linter,
path: &Path,
allocator: &'a Allocator,
source_text: &'a str,
check_syntax_errors: bool,
) -> Vec<Message<'a>> {
let source_type =
SourceType::from_path(path).unwrap_or_else(|_| panic!("Incorrect {path:?}"));
let ret = Parser::new(&allocator, source_text, source_type)
let ret = Parser::new(allocator, source_text, source_type)
.allow_return_outside_function(true)
.parse();
if !ret.errors.is_empty() {
return Some(ret.errors);
return ret.errors.into_iter().map(|err| Message::new(err, None)).collect();
};
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_check_syntax_error(check_syntax_errors)
.with_module_record_builder(true)
.build(program);
if !semantic_ret.errors.is_empty() {
return Some(semantic_ret.errors);
return semantic_ret.errors.into_iter().map(|err| Message::new(err, None)).collect();
};
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())
linter.run(lint_ctx)
}
}

View file

@ -1,13 +1,14 @@
use std::{borrow::Cow, path::PathBuf, rc::Rc};
use std::{
borrow::Cow,
path::{Path, PathBuf},
sync::Arc,
};
use oxc_allocator::Allocator;
use oxc_diagnostics::miette::{GraphicalReportHandler, GraphicalTheme, NamedSource};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
use serde_json::Value;
use crate::{rules::RULES, Fixer, LintContext, Linter, Message};
use crate::{rules::RULES, Fixer, LintOptions, LintService, Linter, Message};
pub struct Tester {
rule_name: &'static str,
@ -123,28 +124,18 @@ impl Tester {
fn run_rules<'a>(
&mut self,
allocator: &'a Allocator,
path: &PathBuf,
path: &Path,
source_text: &'a str,
config: Option<Value>,
is_fix: bool,
) -> Vec<Message<'a>> {
let source_type = SourceType::from_path(path).expect("incorrect {path:?}");
let ret = Parser::new(allocator, source_text, source_type)
.allow_return_outside_function(true)
.parse();
assert!(ret.errors.is_empty(), "{:?}", &ret.errors);
let program = allocator.alloc(ret.program);
let semantic_ret = SemanticBuilder::new(source_text, source_type)
.with_trivias(ret.trivias)
.with_module_record_builder(true)
.build(program);
assert!(semantic_ret.errors.is_empty(), "{:?}", &semantic_ret.errors);
let rule = RULES
.iter()
.find(|rule| rule.name() == self.rule_name)
.unwrap_or_else(|| panic!("Rule not found: {}", &self.rule_name));
let rule = rule.read_json(config);
let lint_context = LintContext::new(&Rc::new(semantic_ret.semantic));
Linter::from_rules(vec![rule]).with_fix(is_fix).run(lint_context)
let options = LintOptions::default().with_fix(is_fix);
let linter = Arc::new(Linter::from_options(options).with_rules(vec![rule]));
LintService::run_source(&linter, path, allocator, source_text, false)
}
}

View file

@ -32,7 +32,7 @@ update:
# --no-vcs-ignores: cargo-watch has a bug loading all .gitignores, including the ones listed in .gitignore
# use .ignore file getting the ignore list
watch command:
cargo watch --no-vcs-ignores -x '{{command}}'
cargo watch --no-vcs-ignores -i '*snap*' -x '{{command}}'
# Format all files
fmt: