Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 53 additions & 16 deletions crates/zeph-tools/src/filter/clippy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,49 @@ use std::sync::LazyLock;

use regex::Regex;

use super::{FilterResult, OutputFilter, make_result};

pub struct ClippyFilter;
use super::{
ClippyFilterConfig, CommandMatcher, FilterConfidence, FilterResult, OutputFilter, make_result,
};

static CLIPPY_MATCHER: LazyLock<CommandMatcher> = LazyLock::new(|| {
CommandMatcher::Custom(Box::new(|cmd| {
let c = cmd.to_lowercase();
let tokens: Vec<&str> = c.split_whitespace().collect();
tokens.first() == Some(&"cargo") && tokens.iter().skip(1).any(|t| *t == "clippy")
}))
});

static LINT_RULE_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"#\[warn\(([^)]+)\)\]").unwrap());

static LOCATION_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\s*-->\s*(.+:\d+)").unwrap());

pub struct ClippyFilter;

impl ClippyFilter {
#[must_use]
pub fn new(_config: ClippyFilterConfig) -> Self {
Self
}
}

impl OutputFilter for ClippyFilter {
fn matches(&self, command: &str) -> bool {
command.contains("cargo clippy")
fn name(&self) -> &'static str {
"clippy"
}

fn matcher(&self) -> &CommandMatcher {
&CLIPPY_MATCHER
}

fn filter(&self, _command: &str, raw_output: &str, exit_code: i32) -> FilterResult {
let has_error = raw_output.contains("error[") || raw_output.contains("error:");
if has_error && exit_code != 0 {
return make_result(raw_output, raw_output.to_owned());
return make_result(
raw_output,
raw_output.to_owned(),
FilterConfidence::Fallback,
);
}

let mut warnings: BTreeMap<String, Vec<String>> = BTreeMap::new();
Expand All @@ -41,7 +66,11 @@ impl OutputFilter for ClippyFilter {
}

if warnings.is_empty() {
return make_result(raw_output, raw_output.to_owned());
return make_result(
raw_output,
raw_output.to_owned(),
FilterConfidence::Fallback,
);
}

let total: usize = warnings.values().map(Vec::len).sum();
Expand All @@ -59,26 +88,31 @@ impl OutputFilter for ClippyFilter {
}
let _ = write!(output, "{total} warnings total ({rules} rules)");

make_result(raw_output, output)
make_result(raw_output, output, FilterConfidence::Full)
}
}

#[cfg(test)]
mod tests {
use super::*;

fn make_filter() -> ClippyFilter {
ClippyFilter::new(ClippyFilterConfig::default())
}

#[test]
fn matches_clippy() {
let f = ClippyFilter;
assert!(f.matches("cargo clippy --workspace"));
assert!(f.matches("cargo clippy -- -D warnings"));
assert!(!f.matches("cargo build"));
assert!(!f.matches("cargo test"));
let f = make_filter();
assert!(f.matcher().matches("cargo clippy --workspace"));
assert!(f.matcher().matches("cargo clippy -- -D warnings"));
assert!(f.matcher().matches("cargo +nightly clippy"));
assert!(!f.matcher().matches("cargo build"));
assert!(!f.matcher().matches("cargo test"));
}

#[test]
fn filter_groups_warnings() {
let f = ClippyFilter;
let f = make_filter();
let raw = "\
warning: needless pass by value
--> src/foo.rs:12:5
Expand Down Expand Up @@ -113,21 +147,24 @@ warning: `my-crate` (lib) generated 3 warnings
.contains("clippy::unused_imports (1 warning):")
);
assert!(result.output.contains("3 warnings total (2 rules)"));
assert_eq!(result.confidence, FilterConfidence::Full);
}

#[test]
fn filter_error_preserves_full() {
let f = ClippyFilter;
let f = make_filter();
let raw = "error[E0308]: mismatched types\n --> src/main.rs:10:5\nfull details here";
let result = f.filter("cargo clippy", raw, 1);
assert_eq!(result.output, raw);
assert_eq!(result.confidence, FilterConfidence::Fallback);
}

#[test]
fn filter_no_warnings_passthrough() {
let f = ClippyFilter;
let f = make_filter();
let raw = "Checking my-crate v0.1.0\n Finished dev [unoptimized] target(s)";
let result = f.filter("cargo clippy", raw, 0);
assert_eq!(result.output, raw);
assert_eq!(result.confidence, FilterConfidence::Fallback);
}
}
61 changes: 46 additions & 15 deletions crates/zeph-tools/src/filter/dir_listing.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::fmt::Write;
use std::sync::LazyLock;

use super::{FilterResult, OutputFilter, make_result};
use super::{
CommandMatcher, DirListingFilterConfig, FilterConfidence, FilterResult, OutputFilter,
make_result,
};

const NOISE_DIRS: &[&str] = &[
"node_modules",
Expand All @@ -15,12 +19,29 @@ const NOISE_DIRS: &[&str] = &[
".cache",
];

static DIR_LISTING_MATCHER: LazyLock<CommandMatcher> = LazyLock::new(|| {
CommandMatcher::Custom(Box::new(|cmd| {
let c = cmd.trim_start();
c == "ls" || c.starts_with("ls ")
}))
});

pub struct DirListingFilter;

impl DirListingFilter {
#[must_use]
pub fn new(_config: DirListingFilterConfig) -> Self {
Self
}
}

impl OutputFilter for DirListingFilter {
fn matches(&self, command: &str) -> bool {
let cmd = command.trim_start();
cmd == "ls" || cmd.starts_with("ls ")
fn name(&self) -> &'static str {
"dir_listing"
}

fn matcher(&self) -> &CommandMatcher {
&DIR_LISTING_MATCHER
}

fn filter(&self, _command: &str, raw_output: &str, _exit_code: i32) -> FilterResult {
Expand All @@ -39,34 +60,42 @@ impl OutputFilter for DirListingFilter {
}

if hidden.is_empty() {
return make_result(raw_output, raw_output.to_owned());
return make_result(
raw_output,
raw_output.to_owned(),
FilterConfidence::Fallback,
);
}

let mut output = kept.join("\n");
let names = hidden.join(", ");
let _ = write!(output, "\n(+ {} hidden: {names})", hidden.len());

make_result(raw_output, output)
make_result(raw_output, output, FilterConfidence::Full)
}
}

#[cfg(test)]
mod tests {
use super::*;

fn make_filter() -> DirListingFilter {
DirListingFilter::new(DirListingFilterConfig::default())
}

#[test]
fn matches_ls() {
let f = DirListingFilter;
assert!(f.matches("ls"));
assert!(f.matches("ls -la"));
assert!(f.matches("ls /tmp"));
assert!(!f.matches("lsof"));
assert!(!f.matches("cargo build"));
let f = make_filter();
assert!(f.matcher().matches("ls"));
assert!(f.matcher().matches("ls -la"));
assert!(f.matcher().matches("ls /tmp"));
assert!(!f.matcher().matches("lsof"));
assert!(!f.matcher().matches("cargo build"));
}

#[test]
fn filter_hides_noise_dirs() {
let f = DirListingFilter;
let f = make_filter();
let raw = "Cargo.toml\nsrc\ntarget\nnode_modules\nREADME.md\n.git";
let result = f.filter("ls", raw, 0);
assert!(result.output.contains("Cargo.toml"));
Expand All @@ -78,19 +107,21 @@ mod tests {
.output
.contains("(+ 3 hidden: target, node_modules, .git)")
);
assert_eq!(result.confidence, FilterConfidence::Full);
}

#[test]
fn filter_no_noise_passthrough() {
let f = DirListingFilter;
let f = make_filter();
let raw = "Cargo.toml\nsrc\nREADME.md";
let result = f.filter("ls", raw, 0);
assert_eq!(result.output, raw);
assert_eq!(result.confidence, FilterConfidence::Fallback);
}

#[test]
fn filter_ls_la_format() {
let f = DirListingFilter;
let f = make_filter();
let raw = "\
drwxr-xr-x 5 user staff 160 Jan 1 12:00 src
drwxr-xr-x 20 user staff 640 Jan 1 12:00 node_modules
Expand Down
Loading
Loading