Skip to content

Commit f345950

Browse files
committed
Test that all interned symbols are referenced in Clippy sources
1 parent b87e90b commit f345950

File tree

3 files changed

+100
-2
lines changed

3 files changed

+100
-2
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ quote = "1.0.25"
5050
syn = { version = "2.0", features = ["full"] }
5151
futures = "0.3"
5252
parking_lot = "0.12"
53-
tokio = { version = "1", features = ["io-util"] }
53+
tokio = { version = "1", features = ["io-util", "fs", "rt-multi-thread", "macros"] }
5454

5555
[build-dependencies]
5656
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }

clippy_utils/src/sym.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ generate! {
132132
enum_glob_use,
133133
enumerate,
134134
err,
135-
error,
136135
exp,
137136
expect_err,
138137
expn_data,

tests/symbols-used.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#![cfg(feature = "internal")]
2+
3+
// This test checks that all symbols defined in Clippy's `sym.rs` file
4+
// are used in Clippy. Otherwise, it will fail with a list of symbols
5+
// which are unused.
6+
//
7+
// This test only runs when the `internal` feature is set, to allow
8+
// developers to temporarily define new, yet unused symbols without
9+
// failing the tests. Also, the test is a no-op if run as part of the
10+
// compiler test suite.
11+
12+
use std::collections::HashSet;
13+
use std::path::Path;
14+
15+
use futures::StreamExt;
16+
use futures::stream::FuturesUnordered;
17+
use regex::Regex;
18+
use walkdir::{DirEntry, WalkDir};
19+
20+
const SYM_FILE: &str = "clippy_utils/src/sym.rs";
21+
22+
type Result<T, E = AnyError> = std::result::Result<T, E>;
23+
type AnyError = Box<dyn std::error::Error>;
24+
25+
async fn load_interned_symbols() -> Result<HashSet<String>> {
26+
let content = tokio::fs::read_to_string(SYM_FILE).await?;
27+
let content = content
28+
.split_once("generate! {")
29+
.ok_or("cannot find symbols start")?
30+
.1
31+
.split_once("\n}\n")
32+
.ok_or("cannot find symbols end")?
33+
.0;
34+
let re = Regex::new(r"(?m)^ (\w+)").unwrap();
35+
Ok(re.captures_iter(content).map(|m| m[1].to_owned()).collect())
36+
}
37+
38+
async fn load_symbols(file: impl AsRef<Path>, re: &Regex) -> Result<Vec<String>> {
39+
let content = tokio::fs::read_to_string(file).await?;
40+
Ok(re.captures_iter(&content).map(|m| m[1].to_owned()).collect())
41+
}
42+
43+
async fn load_paths(file: impl AsRef<Path>, re: &Regex) -> Result<Vec<String>> {
44+
let content = tokio::fs::read_to_string(file).await?;
45+
Ok(re
46+
.captures_iter(&content)
47+
.flat_map(|m| m[1].split("::").map(String::from).collect::<Vec<_>>())
48+
.collect())
49+
}
50+
51+
#[tokio::test]
52+
#[allow(clippy::case_sensitive_file_extension_comparisons)]
53+
async fn all_symbols_are_used() -> Result<()> {
54+
if option_env!("RUSTC_TEST_SUITE").is_some() {
55+
return Ok(());
56+
}
57+
58+
let used_re = &Regex::new(r"\bsym::(\w+)\b").unwrap();
59+
let mut used_symbols_per_file = ["clippy_lints", "clippy_lints_internal", "clippy_utils", "src"]
60+
.iter()
61+
.flat_map(|dir| {
62+
WalkDir::new(dir)
63+
.into_iter()
64+
.filter_entry(|e| e.file_name().to_str().is_some_and(|s| s.ends_with(".rs")) || e.file_type().is_dir())
65+
.flat_map(|e| e.map(DirEntry::into_path))
66+
})
67+
.map(|file| load_symbols(file, used_re))
68+
.collect::<FuturesUnordered<_>>();
69+
let used_symbols = async {
70+
let mut symbols = HashSet::default();
71+
while let Some(syms) = used_symbols_per_file.next().await {
72+
if let Ok(syms) = syms {
73+
symbols.extend(syms);
74+
}
75+
}
76+
symbols
77+
};
78+
79+
let paths_re = Regex::new(r"path!\(([\w:]+)\)").unwrap();
80+
let (interned, mut used, paths, paths_internal) = futures::join!(
81+
load_interned_symbols(),
82+
used_symbols,
83+
load_paths("clippy_utils/src/paths.rs", &paths_re),
84+
load_paths("clippy_lints_internal/src/internal_paths.rs", &paths_re),
85+
);
86+
used.extend(paths?);
87+
used.extend(paths_internal?);
88+
let interned = interned?;
89+
let mut extra = interned.difference(&used).collect::<Vec<_>>();
90+
if !extra.is_empty() {
91+
extra.sort_unstable();
92+
eprintln!("Unused symbols defined in {SYM_FILE}:");
93+
for sym in extra {
94+
eprintln!(" - {sym}");
95+
}
96+
Err(format!("extra symbols found — remove them {SYM_FILE}"))?;
97+
}
98+
Ok(())
99+
}

0 commit comments

Comments
 (0)