Skip to content

Commit 8290f72

Browse files
authored
Merge 602d81e into 3e5a02b
2 parents 3e5a02b + 602d81e commit 8290f72

File tree

7 files changed

+391
-75
lines changed

7 files changed

+391
-75
lines changed

.github/workflows/lintcheck.yml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: Lintcheck
2+
3+
on: pull_request
4+
5+
env:
6+
RUST_BACKTRACE: 1
7+
CARGO_INCREMENTAL: 0
8+
9+
concurrency:
10+
# For a given workflow, if we push to the same PR, cancel all previous builds on that PR.
11+
group: "${{ github.workflow }}-${{ github.event.pull_request.number}}"
12+
cancel-in-progress: true
13+
14+
jobs:
15+
base:
16+
runs-on: ubuntu-latest
17+
18+
outputs:
19+
key: ${{ steps.key.outputs.key }}
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
with:
25+
fetch-depth: 0
26+
27+
- name: Checkout merge base
28+
run: git checkout ${{ github.event.pull_request.base.sha }}
29+
30+
- name: Checkout current lintcheck
31+
run: |
32+
rm -rf lintcheck
33+
git checkout ${{ github.sha }} -- lintcheck
34+
35+
- name: Create cache key
36+
id: key
37+
run: echo "key=lintcheck-base-${{ hashfiles('lintcheck/**') }}-${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
38+
39+
- name: Cache results
40+
id: cache
41+
uses: actions/cache@v4
42+
with:
43+
path: lintcheck-logs/base.json
44+
key: ${{ steps.key.outputs.key }}
45+
46+
- name: Run lintcheck
47+
if: steps.cache.outputs.cache-hit != 'true'
48+
run: cargo lintcheck --json
49+
50+
- name: Rename JSON file
51+
if: steps.cache.outputs.cache-hit != 'true'
52+
run: mv lintcheck-logs/lintcheck_crates_logs.json lintcheck-logs/base.json
53+
54+
head:
55+
runs-on: ubuntu-latest
56+
57+
outputs:
58+
key: ${{ steps.key.outputs.key }}
59+
60+
steps:
61+
- name: Checkout
62+
uses: actions/checkout@v4
63+
64+
- name: Create cache key
65+
id: key
66+
run: echo "key=lintcheck-head-${{ github.sha }}" >> "$GITHUB_OUTPUT"
67+
68+
- name: Cache results
69+
id: cache
70+
uses: actions/cache@v4
71+
with:
72+
path: lintcheck-logs/head.json
73+
key: ${{ steps.key.outputs.key }}
74+
75+
- name: Run lintcheck
76+
if: steps.cache.outputs.cache-hit != 'true'
77+
run: cargo lintcheck --json
78+
79+
- name: Rename JSON file
80+
if: steps.cache.outputs.cache-hit != 'true'
81+
run: mv lintcheck-logs/lintcheck_crates_logs.json lintcheck-logs/head.json
82+
83+
diff:
84+
runs-on: ubuntu-latest
85+
86+
needs: [base, head]
87+
88+
steps:
89+
- name: Checkout
90+
uses: actions/checkout@v4
91+
92+
- name: Restore base JSON
93+
uses: actions/cache/restore@v4
94+
with:
95+
key: ${{ needs.base.outputs.key }}
96+
path: lintcheck-logs/base.json
97+
fail-on-cache-miss: true
98+
99+
- name: Restore head JSON
100+
uses: actions/cache/restore@v4
101+
with:
102+
key: ${{ needs.head.outputs.key }}
103+
path: lintcheck-logs/head.json
104+
fail-on-cache-miss: true
105+
106+
- name: Diff results
107+
run: cargo lintcheck diff lintcheck-logs/base.json lintcheck-logs/head.json >> $GITHUB_STEP_SUMMARY

clippy_lints/src/manual_is_ascii_check.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ fn check_is_ascii(
165165
cx,
166166
MANUAL_IS_ASCII_CHECK,
167167
span,
168-
"manual check for common ascii range",
168+
"manual check for common ASCII range",
169169
|diag| {
170170
diag.multipart_suggestion("try", suggestion, app);
171171
},

clippy_lints/src/operators/identity_op.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,15 @@ fn span_ineffective_operation(
239239
Parens::Unneeded => expr_snippet,
240240
};
241241

242-
span_lint_and_sugg(
243-
cx,
244-
IDENTITY_OP,
245-
span,
246-
"this operation has no effect",
247-
"consider reducing it to",
248-
suggestion,
249-
applicability,
250-
);
242+
if false {
243+
span_lint_and_sugg(
244+
cx,
245+
IDENTITY_OP,
246+
span,
247+
"this operation has no effect",
248+
"consider reducing it to",
249+
suggestion,
250+
applicability,
251+
);
252+
}
251253
}

lintcheck/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ cargo_metadata = "0.15.3"
1616
clap = { version = "4.4", features = ["derive", "env"] }
1717
crates_io_api = "0.8.1"
1818
crossbeam-channel = "0.5.6"
19+
diff = "0.1.13"
1920
flate2 = "1.0"
2021
indicatif = "0.17.3"
2122
rayon = "1.5.1"
2223
serde = { version = "1.0", features = ["derive"] }
2324
serde_json = "1.0.85"
25+
strip-ansi-escapes = "0.1.1"
2426
tar = "0.4"
2527
toml = "0.7.3"
2628
ureq = "2.2"

lintcheck/src/config.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use clap::Parser;
1+
use clap::{Parser, Subcommand, ValueEnum};
22
use std::num::NonZero;
33
use std::path::PathBuf;
44

5-
#[derive(Clone, Debug, Parser)]
5+
#[allow(clippy::struct_excessive_bools)]
6+
#[derive(Parser, Clone, Debug)]
67
pub(crate) struct LintcheckConfig {
78
/// Number of threads to use (default: all unless --fix or --recursive)
89
#[clap(
@@ -37,23 +38,60 @@ pub(crate) struct LintcheckConfig {
3738
pub lint_filter: Vec<String>,
3839
/// Change the reports table to use markdown links
3940
#[clap(long)]
40-
pub markdown: bool,
41+
markdown: bool,
42+
/// Output the diagnostics as JSON
43+
#[clap(long, conflicts_with("markdown"))]
44+
json: bool,
45+
#[clap(skip = OutputFormat::Text)]
46+
pub format: OutputFormat,
4147
/// Run clippy on the dependencies of crates specified in crates-toml
4248
#[clap(long, conflicts_with("max_jobs"))]
4349
pub recursive: bool,
50+
#[command(subcommand)]
51+
pub subcommand: Option<Commands>,
52+
}
53+
54+
#[derive(Subcommand, Clone, Debug)]
55+
pub(crate) enum Commands {
56+
Diff { old: PathBuf, new: PathBuf },
57+
}
58+
59+
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
60+
pub(crate) enum OutputFormat {
61+
Text,
62+
Markdown,
63+
Json,
64+
}
65+
66+
impl OutputFormat {
67+
fn file_extension(self) -> &'static str {
68+
match self {
69+
OutputFormat::Text => "txt",
70+
OutputFormat::Markdown => "md",
71+
OutputFormat::Json => "json",
72+
}
73+
}
4474
}
4575

4676
impl LintcheckConfig {
4777
pub fn new() -> Self {
4878
let mut config = LintcheckConfig::parse();
4979

80+
config.format = if config.markdown {
81+
OutputFormat::Markdown
82+
} else if config.json {
83+
OutputFormat::Json
84+
} else {
85+
OutputFormat::Text
86+
};
87+
5088
// for the path where we save the lint results, get the filename without extension (so for
5189
// wasd.toml, use "wasd"...)
5290
let filename: PathBuf = config.sources_toml_path.file_stem().unwrap().into();
5391
config.lintcheck_results_path = PathBuf::from(format!(
5492
"lintcheck-logs/{}_logs.{}",
5593
filename.display(),
56-
if config.markdown { "md" } else { "txt" }
94+
config.format.file_extension(),
5795
));
5896

5997
// look at the --threads arg, if 0 is passed, use the threads count

lintcheck/src/json.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use std::collections::HashMap;
2+
use std::fmt::Write;
3+
use std::fs;
4+
use std::hash::Hash;
5+
use std::path::Path;
6+
7+
use crate::ClippyWarning;
8+
9+
/// Creates the log file output for [`crate::config::OutputFormat::Json`]
10+
pub(crate) fn output(clippy_warnings: &[ClippyWarning]) -> String {
11+
serde_json::to_string(&clippy_warnings).unwrap()
12+
}
13+
14+
fn load_warnings(path: &Path) -> Vec<ClippyWarning> {
15+
let file = fs::read(path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
16+
17+
serde_json::from_slice(&file).unwrap_or_else(|e| panic!("failed to deserialize {}: {e}", path.display()))
18+
}
19+
20+
/// Group warnings by their primary span location + lint name
21+
fn create_map(warnings: &[ClippyWarning]) -> HashMap<impl Eq + Hash + '_, Vec<&ClippyWarning>> {
22+
let mut map = HashMap::<_, Vec<_>>::with_capacity(warnings.len());
23+
24+
for warning in warnings {
25+
let span = warning.span();
26+
let key = (&warning.lint_type, &span.file_name, span.byte_start, span.byte_end);
27+
28+
map.entry(key).or_default().push(warning);
29+
}
30+
31+
map
32+
}
33+
34+
fn print_warnings(title: &str, warnings: &[&ClippyWarning]) {
35+
if warnings.is_empty() {
36+
return;
37+
}
38+
39+
println!("### {title}");
40+
println!("```");
41+
for warning in warnings {
42+
print!("{}", warning.diag);
43+
}
44+
println!("```");
45+
}
46+
47+
fn print_changed_diff(changed: &[(&[&ClippyWarning], &[&ClippyWarning])]) {
48+
fn render(warnings: &[&ClippyWarning]) -> String {
49+
let mut rendered = String::new();
50+
for warning in warnings {
51+
write!(&mut rendered, "{}", warning.diag).unwrap();
52+
}
53+
rendered
54+
}
55+
56+
if changed.is_empty() {
57+
return;
58+
}
59+
60+
println!("### Changed");
61+
println!("```diff");
62+
for &(old, new) in changed {
63+
let old_rendered = render(old);
64+
let new_rendered = render(new);
65+
66+
for change in diff::lines(&old_rendered, &new_rendered) {
67+
use diff::Result::{Both, Left, Right};
68+
69+
match change {
70+
Both(unchanged, _) => {
71+
println!(" {unchanged}");
72+
},
73+
Left(removed) => {
74+
println!("-{removed}");
75+
},
76+
Right(added) => {
77+
println!("+{added}");
78+
},
79+
}
80+
}
81+
}
82+
println!("```");
83+
}
84+
85+
pub(crate) fn diff(old_path: &Path, new_path: &Path) {
86+
let old_warnings = load_warnings(old_path);
87+
let new_warnings = load_warnings(new_path);
88+
89+
let old_map = create_map(&old_warnings);
90+
let new_map = create_map(&new_warnings);
91+
92+
let mut added = Vec::new();
93+
let mut removed = Vec::new();
94+
let mut changed = Vec::new();
95+
96+
for (key, new) in &new_map {
97+
if let Some(old) = old_map.get(key) {
98+
if old != new {
99+
changed.push((old.as_slice(), new.as_slice()));
100+
}
101+
} else {
102+
added.extend(new);
103+
}
104+
}
105+
106+
for (key, old) in &old_map {
107+
if !new_map.contains_key(key) {
108+
removed.extend(old);
109+
}
110+
}
111+
112+
print!(
113+
"{} added, {} removed, {} changed\n\n",
114+
added.len(),
115+
removed.len(),
116+
changed.len()
117+
);
118+
119+
print_warnings("Added", &added);
120+
print_warnings("Removed", &removed);
121+
print_changed_diff(&changed);
122+
}

0 commit comments

Comments
 (0)