Skip to content

Commit 202dbe7

Browse files
committed
Metadata collection lint: Basic lint collection
WIP-2021-02-01 WIP-2021-02-01 WIP-2021-02-13
1 parent 534a13d commit 202dbe7

File tree

8 files changed

+230
-1
lines changed

8 files changed

+230
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ out
2929

3030
# gh pages docs
3131
util/gh-pages/lints.json
32+
**/metadata_collection.json
3233

3334
# rustfmt backups
3435
*.rs.bk

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }
5050
deny-warnings = []
5151
integration = ["tempfile"]
5252
internal-lints = ["clippy_lints/internal-lints"]
53+
metadata-collector-lint = ["clippy_lints/metadata-collector-lint"]
5354

5455
[package.metadata.rust-analyzer]
5556
# This package uses #[feature(rustc_private)]

clippy_lints/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pulldown-cmark = { version = "0.8", default-features = false }
2020
quine-mc_cluskey = "0.2.2"
2121
regex-syntax = "0.6"
2222
serde = { version = "1.0", features = ["derive"] }
23+
serde_json = { version = "1.0", optional = true }
2324
smallvec = { version = "1", features = ["union"] }
2425
toml = "0.5.3"
2526
unicode-normalization = "0.1"
@@ -35,6 +36,7 @@ syn = { version = "1", features = ["full"] }
3536
deny-warnings = []
3637
# build clippy with internal lints enabled, off by default
3738
internal-lints = ["clippy_utils/internal-lints"]
39+
metadata-collector-lint = ["serde_json"]
3840

3941
[package.metadata.rust-analyzer]
4042
# This crate uses #[feature(rustc_private)]

clippy_lints/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
557557
&utils::internal_lints::PRODUCE_ICE,
558558
#[cfg(feature = "internal-lints")]
559559
&utils::internal_lints::UNNECESSARY_SYMBOL_STR,
560+
// TODO xFrednet 2021-02-13: Update `cargo dev update_lints` to support this vvvv
561+
#[cfg(feature = "metadata-collector-lint")]
562+
&utils::internal_lints::metadata_collector::INTERNAL_METADATA_COLLECTOR,
560563
&approx_const::APPROX_CONSTANT,
561564
&arithmetic::FLOAT_ARITHMETIC,
562565
&arithmetic::INTEGER_ARITHMETIC,
@@ -1022,6 +1025,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10221025
store.register_late_pass(|| box utils::internal_lints::MatchTypeOnDiagItem);
10231026
store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass);
10241027
}
1028+
#[cfg(feature = "metadata-collector-lint")]
1029+
store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::default());
1030+
10251031
store.register_late_pass(|| box utils::author::Author);
10261032
store.register_late_pass(|| box await_holding_invalid::AwaitHolding);
10271033
store.register_late_pass(|| box serde_api::SerdeApi);
@@ -1437,6 +1443,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14371443
LintId::of(&utils::internal_lints::OUTER_EXPN_EXPN_DATA),
14381444
LintId::of(&utils::internal_lints::PRODUCE_ICE),
14391445
LintId::of(&utils::internal_lints::UNNECESSARY_SYMBOL_STR),
1446+
LintId::of(&utils::internal_lints::metadata_collector::INTERNAL_METADATA_COLLECTOR),
14401447
]);
14411448

14421449
store.register_group(true, "clippy::all", Some("clippy"), vec![

clippy_lints/src/utils/internal_lints.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ use rustc_typeck::hir_ty_to_ty;
2727

2828
use std::borrow::{Borrow, Cow};
2929

30+
#[cfg(feature = "metadata-collector-lint")]
31+
pub mod metadata_collector;
32+
3033
declare_clippy_lint! {
3134
/// **What it does:** Checks for various things we like to keep tidy in clippy.
3235
///
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
//! This lint is used to collect metadata about clippy lints. This metadata is exported as a json
2+
//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html)
3+
//!
4+
//! This module and therefor the entire lint is guarded by a feature flag called
5+
//! `internal_metadata_lint`
6+
//!
7+
//! The metadata currently contains:
8+
//! - [ ] TODO The lint declaration line for [#1303](https://github.com/rust-lang/rust-clippy/issues/1303)
9+
//! and [#6492](https://github.com/rust-lang/rust-clippy/issues/6492)
10+
//! - [ ] TODO The Applicability for each lint for [#4310](https://github.com/rust-lang/rust-clippy/issues/4310)
11+
12+
// # Applicability
13+
// - TODO xFrednet 2021-01-17: Find all methods that take and modify applicability or predefine
14+
// them?
15+
// - TODO xFrednet 2021-01-17: Find lint emit and collect applicability
16+
// # NITs
17+
// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames
18+
19+
use if_chain::if_chain;
20+
use rustc_hir::{ExprKind, Item, ItemKind, Mutability};
21+
use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
22+
use rustc_session::{declare_tool_lint, impl_lint_pass};
23+
use rustc_span::{sym, Loc, Span};
24+
use serde::Serialize;
25+
use std::fs::OpenOptions;
26+
use std::io::prelude::*;
27+
28+
use crate::utils::internal_lints::is_lint_ref_type;
29+
use crate::utils::span_lint;
30+
31+
const OUTPUT_FILE: &str = "metadata_collection.json";
32+
const BLACK_LISTED_LINTS: [&str; 2] = ["lint_author", "deep_code_inspection"];
33+
34+
declare_clippy_lint! {
35+
/// **What it does:** Collects metadata about clippy lints for the website.
36+
///
37+
/// This lint will be used to report problems of syntax parsing. You should hopefully never
38+
/// see this but never say never I guess ^^
39+
///
40+
/// **Why is this bad?** This is not a bad thing but definitely a hacky way to do it. See
41+
/// issue [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) for a discussion
42+
/// about the implementation.
43+
///
44+
/// **Known problems:** Hopefully none. It would be pretty uncool to have a problem here :)
45+
///
46+
/// **Example output:**
47+
/// ```json,ignore
48+
/// {
49+
/// "id": "internal_metadata_collector",
50+
/// "id_span": {
51+
/// "path": "clippy_lints/src/utils/internal_lints/metadata_collector.rs",
52+
/// "line": 1
53+
/// },
54+
/// "group": "clippy::internal",
55+
/// "docs": " **What it does:** Collects metadata about clippy lints for the website. [...] "
56+
/// }
57+
/// ```
58+
pub INTERNAL_METADATA_COLLECTOR,
59+
internal,
60+
"A busy bee collection metadata about lints"
61+
}
62+
63+
impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
64+
65+
#[allow(clippy::module_name_repetitions)]
66+
#[derive(Debug, Clone, Default)]
67+
pub struct MetadataCollector {
68+
lints: Vec<LintMetadata>,
69+
}
70+
71+
impl Drop for MetadataCollector {
72+
fn drop(&mut self) {
73+
// You might ask: How hacky is this?
74+
// My answer: YES
75+
let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap();
76+
writeln!(file, "{}", serde_json::to_string_pretty(&self.lints).unwrap()).unwrap();
77+
}
78+
}
79+
80+
#[derive(Debug, Clone, Serialize)]
81+
struct LintMetadata {
82+
id: String,
83+
id_span: SerializableSpan,
84+
group: String,
85+
docs: String,
86+
}
87+
88+
#[derive(Debug, Clone, Serialize)]
89+
struct SerializableSpan {
90+
path: String,
91+
line: usize,
92+
}
93+
94+
impl SerializableSpan {
95+
fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self {
96+
Self::from_span(cx, item.ident.span)
97+
}
98+
99+
fn from_span(cx: &LateContext<'_>, span: Span) -> Self {
100+
let loc: Loc = cx.sess().source_map().lookup_char_pos(span.lo());
101+
102+
Self {
103+
path: format!("{}", loc.file.name),
104+
line: 1,
105+
}
106+
}
107+
}
108+
109+
impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
110+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
111+
if_chain! {
112+
if let ItemKind::Static(ref ty, Mutability::Not, body_id) = item.kind;
113+
if is_lint_ref_type(cx, ty);
114+
let expr = &cx.tcx.hir().body(body_id).value;
115+
if let ExprKind::AddrOf(_, _, ref inner_exp) = expr.kind;
116+
if let ExprKind::Struct(_, _, _) = inner_exp.kind;
117+
then {
118+
let lint_name = item.ident.name.as_str().to_string().to_ascii_lowercase();
119+
if BLACK_LISTED_LINTS.contains(&lint_name.as_str()) {
120+
return;
121+
}
122+
123+
let group: String;
124+
let result = cx.lint_store.check_lint_name(lint_name.as_str(), Some(sym::clippy));
125+
if let CheckLintNameResult::Tool(Ok(lint_lst)) = result {
126+
if let Some(group_some) = get_lint_group(cx, lint_lst[0]) {
127+
group = group_some;
128+
} else {
129+
lint_collection_error(cx, item, "Unable to determine lint group");
130+
return;
131+
}
132+
} else {
133+
lint_collection_error(cx, item, "Unable to find lint in lint_store");
134+
return;
135+
}
136+
137+
let docs: String;
138+
if let Some(docs_some) = extract_attr_docs(item) {
139+
docs = docs_some;
140+
} else {
141+
lint_collection_error(cx, item, "could not collect the lint documentation");
142+
return;
143+
};
144+
145+
self.lints.push(LintMetadata {
146+
id: lint_name,
147+
id_span: SerializableSpan::from_item(cx, item),
148+
group,
149+
docs,
150+
});
151+
}
152+
}
153+
}
154+
}
155+
156+
/// This function collects all documentation that has been added to an item using
157+
/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks
158+
///
159+
/// ```ignore
160+
/// #[doc = r"Hello world!"]
161+
/// #[doc = r"=^.^="]
162+
/// struct SomeItem {}
163+
/// ```
164+
///
165+
/// Would result in `Hello world!\n=^.^=\n`
166+
fn extract_attr_docs(item: &Item<'_>) -> Option<String> {
167+
item.attrs
168+
.iter()
169+
.filter_map(|ref x| x.doc_str())
170+
.fold(None, |acc, sym| {
171+
let mut doc_str = sym.as_str().to_string();
172+
doc_str.push('\n');
173+
174+
#[allow(clippy::option_if_let_else)] // See clippy#6737
175+
if let Some(mut x) = acc {
176+
x.push_str(&doc_str);
177+
Some(x)
178+
} else {
179+
Some(doc_str)
180+
}
181+
182+
// acc.map_or(Some(doc_str), |mut x| {
183+
// x.push_str(&doc_str);
184+
// Some(x)
185+
// })
186+
})
187+
}
188+
189+
fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
190+
for (group_name, lints, _) in &cx.lint_store.get_lint_groups() {
191+
if lints.iter().any(|x| *x == lint_id) {
192+
return Some((*group_name).to_string());
193+
}
194+
}
195+
196+
None
197+
}
198+
199+
fn lint_collection_error(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
200+
span_lint(
201+
cx,
202+
INTERNAL_METADATA_COLLECTOR,
203+
item.ident.span,
204+
&format!("Metadata collection error for `{}`: {}", item.ident.name, message),
205+
);
206+
}

clippy_lints/src/utils/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pub mod author;
22
pub mod conf;
33
pub mod inspector;
4-
#[cfg(feature = "internal-lints")]
4+
#[cfg(any(feature = "internal-lints", feature = "metadata-collector-lint"))]
55
pub mod internal_lints;
66

77
pub use clippy_utils::*;

tests/dogfood.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
//! This test is a part of quality control and makes clippy eat what it produces. Awesome lints and
2+
//! long error messages
3+
//!
4+
//! See [Eating your own dog food](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) for context
5+
16
// Dogfood cannot run on Windows
27
#![cfg(not(windows))]
38
#![feature(once_cell)]
@@ -36,6 +41,10 @@ fn dogfood_clippy() {
3641
command.args(&["-D", "clippy::internal"]);
3742
}
3843

44+
if cfg!(feature = "metadata-collector-lint") {
45+
command.args(&["-D", "clippy::internal"]);
46+
}
47+
3948
let output = command.output().unwrap();
4049

4150
println!("status: {}", output.status);

0 commit comments

Comments
 (0)