Skip to content

Commit 997a4a3

Browse files
committed
Add unknown_features lint
1 parent c41916d commit 997a4a3

File tree

10 files changed

+232
-0
lines changed

10 files changed

+232
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,7 @@ Released 2018-09-13
15961596
[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
15971597
[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
15981598
[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
1599+
[`unknown_features`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_features
15991600
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
16001601
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
16011602
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold

clippy_lints/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ smallvec = { version = "1", features = ["union"] }
2929
toml = "0.5.3"
3030
unicode-normalization = "0.1"
3131
semver = "0.9.0"
32+
strsim = "0.10"
3233
# NOTE: cargo requires serde feat in its url dep
3334
# see <https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864>
3435
url = { version = "2.1.0", features = ["serde"] }

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ mod trivially_copy_pass_by_ref;
317317
mod try_err;
318318
mod types;
319319
mod unicode;
320+
mod unknown_features;
320321
mod unnamed_address;
321322
mod unsafe_removed_from_name;
322323
mod unused_io_amount;
@@ -355,6 +356,7 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &Co
355356
});
356357
store.register_pre_expansion_pass(|| box attrs::EarlyAttributes);
357358
store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro);
359+
store.register_pre_expansion_pass(|| box unknown_features::UnknownFeatures::default());
358360
}
359361

360362
#[doc(hidden)]
@@ -835,6 +837,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
835837
&unicode::NON_ASCII_LITERAL,
836838
&unicode::UNICODE_NOT_NFC,
837839
&unicode::ZERO_WIDTH_SPACE,
840+
&unknown_features::UNKNOWN_FEATURES,
838841
&unnamed_address::FN_ADDRESS_COMPARISONS,
839842
&unnamed_address::VTABLE_ADDRESS_COMPARISONS,
840843
&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
@@ -1703,6 +1706,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
17031706
store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![
17041707
LintId::of(&cargo_common_metadata::CARGO_COMMON_METADATA),
17051708
LintId::of(&multiple_crate_versions::MULTIPLE_CRATE_VERSIONS),
1709+
LintId::of(&unknown_features::UNKNOWN_FEATURES),
17061710
LintId::of(&wildcard_dependencies::WILDCARD_DEPENDENCIES),
17071711
]);
17081712

clippy_lints/src/unknown_features.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use rustc_ast::ast::{Attribute, Crate, MacCall, MetaItem, MetaItemKind};
2+
use rustc_data_structures::fx::FxHashSet;
3+
use rustc_errors::Applicability;
4+
use rustc_lint::{EarlyContext, EarlyLintPass};
5+
use rustc_parse::{self, MACRO_ARGUMENTS};
6+
use rustc_session::{declare_tool_lint, impl_lint_pass};
7+
use rustc_span::source_map::DUMMY_SP;
8+
9+
use crate::utils::{span_lint, span_lint_and_then};
10+
use cargo_metadata::MetadataCommand;
11+
use strsim::normalized_damerau_levenshtein;
12+
13+
declare_clippy_lint! {
14+
/// **What it does:** Finds references to features not defined in the cargo manifest file.
15+
///
16+
/// **Why is this bad?** The referred feature will not be recognised and the related item will not be included
17+
/// by the conditional compilation engine.
18+
///
19+
/// **Known problems:** None.
20+
///
21+
/// **Example:**
22+
///
23+
/// ```rust
24+
/// #[cfg(feature = "unknown")]
25+
/// fn example() { }
26+
/// ```
27+
pub UNKNOWN_FEATURES,
28+
cargo,
29+
"usage of features not defined in the cargo manifest file"
30+
}
31+
32+
#[derive(Default)]
33+
pub struct UnknownFeatures {
34+
features: FxHashSet<String>,
35+
}
36+
37+
impl_lint_pass!(UnknownFeatures => [UNKNOWN_FEATURES]);
38+
39+
impl EarlyLintPass for UnknownFeatures {
40+
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
41+
fn transform_feature(name: &str, pkg: &str, local_pkg: &str) -> String {
42+
if pkg == local_pkg {
43+
name.into()
44+
} else {
45+
format!("{}/{}", pkg, name)
46+
}
47+
}
48+
49+
let metadata = if let Ok(metadata) = MetadataCommand::new().exec() {
50+
metadata
51+
} else {
52+
span_lint(cx, UNKNOWN_FEATURES, DUMMY_SP, "could not read cargo metadata");
53+
return;
54+
};
55+
56+
if let Some(local_pkg) = &cx.sess.opts.crate_name {
57+
for pkg in metadata.packages {
58+
self.features.extend(
59+
pkg.features
60+
.keys()
61+
.map(|name| transform_feature(name, &pkg.name, local_pkg)),
62+
);
63+
}
64+
}
65+
}
66+
67+
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
68+
if attr.check_name(sym!(cfg)) {
69+
if let Some(item) = &attr.meta() {
70+
self.walk_cfg_metas(cx, item);
71+
}
72+
}
73+
}
74+
75+
fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
76+
if mac.path == sym!(cfg) {
77+
let tts = mac.args.inner_tokens();
78+
let mut parser = rustc_parse::stream_to_parser(&cx.sess.parse_sess, tts, MACRO_ARGUMENTS);
79+
if let Ok(item) = parser.parse_meta_item() {
80+
self.walk_cfg_metas(cx, &item);
81+
}
82+
}
83+
}
84+
}
85+
86+
impl UnknownFeatures {
87+
fn walk_cfg_metas(&mut self, cx: &EarlyContext<'_>, item: &MetaItem) {
88+
match &item.kind {
89+
MetaItemKind::List(items) => {
90+
for nested in items {
91+
if let Some(item) = nested.meta_item() {
92+
self.walk_cfg_metas(cx, item);
93+
}
94+
}
95+
},
96+
MetaItemKind::NameValue(lit) if item.name_or_empty().as_str() == "feature" => {
97+
if let Some(value) = item.value_str() {
98+
let feature = &*value.as_str();
99+
if !self.features.contains(feature) {
100+
let message = format!("unknown feature `{}`", feature);
101+
span_lint_and_then(cx, UNKNOWN_FEATURES, lit.span, &message, |diag| {
102+
if let Some(similar_name) = self.find_similar_name(feature) {
103+
diag.span_suggestion(
104+
lit.span,
105+
"a feature with a similar name exists",
106+
format!("\"{}\"", similar_name),
107+
Applicability::MaybeIncorrect,
108+
);
109+
}
110+
});
111+
}
112+
}
113+
},
114+
_ => {},
115+
}
116+
}
117+
118+
fn find_similar_name(&self, name: &str) -> Option<String> {
119+
let mut similar: Vec<_> = self
120+
.features
121+
.iter()
122+
.map(|f| (f, normalized_damerau_levenshtein(name, f)))
123+
.filter(|(_, sim)| *sim >= 0.7)
124+
.collect();
125+
126+
similar.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap());
127+
similar.into_iter().next().map(|(f, _)| f.clone())
128+
}
129+
}

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
22572257
deprecation: None,
22582258
module: "attrs",
22592259
},
2260+
Lint {
2261+
name: "unknown_features",
2262+
group: "cargo",
2263+
desc: "usage of features not defined in the cargo manifest file",
2264+
deprecation: None,
2265+
module: "unknown_features",
2266+
},
22602267
Lint {
22612268
name: "unnecessary_cast",
22622269
group: "complexity",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
# Features referenced in the code are not found in this manifest
3+
4+
[package]
5+
name = "unknown_features"
6+
version = "0.1.0"
7+
publish = false
8+
9+
[features]
10+
misspelled = []
11+
another = []
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// compile-flags: --crate-name=unknown_features --cfg feature="misspelled" --cfg feature="another"
2+
#![warn(clippy::unknown_features)]
3+
4+
fn main() {
5+
#[cfg(feature = "mispelled")]
6+
let _ = 42;
7+
8+
#[cfg(feature = "dependency/unknown")]
9+
let _ = 42;
10+
11+
#[cfg(any(not(feature = "misspeled"), feature = "not-found"))]
12+
let _ = 21;
13+
14+
if cfg!(feature = "nothe") {}
15+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
error: unknown feature `mispelled`
2+
--> $DIR/main.rs:5:21
3+
|
4+
LL | #[cfg(feature = "mispelled")]
5+
| ^^^^^^^^^^^ help: a feature with a similar name exists: `"misspelled"`
6+
|
7+
= note: `-D clippy::unknown-features` implied by `-D warnings`
8+
9+
error: unknown feature `dependency/unknown`
10+
--> $DIR/main.rs:8:21
11+
|
12+
LL | #[cfg(feature = "dependency/unknown")]
13+
| ^^^^^^^^^^^^^^^^^^^^
14+
15+
error: unknown feature `misspeled`
16+
--> $DIR/main.rs:11:29
17+
|
18+
LL | #[cfg(any(not(feature = "misspeled"), feature = "not-found"))]
19+
| ^^^^^^^^^^^ help: a feature with a similar name exists: `"misspelled"`
20+
21+
error: unknown feature `not-found`
22+
--> $DIR/main.rs:11:53
23+
|
24+
LL | #[cfg(any(not(feature = "misspeled"), feature = "not-found"))]
25+
| ^^^^^^^^^^^
26+
27+
error: unknown feature `nothe`
28+
--> $DIR/main.rs:14:23
29+
|
30+
LL | if cfg!(feature = "nothe") {}
31+
| ^^^^^^^ help: a feature with a similar name exists: `"another"`
32+
33+
error: aborting due to 5 previous errors
34+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
# Features from this crate and from dependencies are correctly referenced in the code
3+
4+
[package]
5+
name = "unknown_features"
6+
version = "0.1.0"
7+
publish = false
8+
9+
[dependencies]
10+
serde = { version = "1.0.110", features = ["derive"] }
11+
12+
[features]
13+
fancy = []
14+
another = []
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// compile-flags: --crate-name=unknown_features --cfg feature="fancy" --cfg feature="another"
2+
// compile-flags: --cfg feature="serde/derive"
3+
#![warn(clippy::unknown_features)]
4+
5+
fn main() {
6+
#[cfg(feature = "fancy")]
7+
let _ = 42;
8+
9+
#[cfg(feature = "serde/derive")]
10+
let _ = 42;
11+
12+
#[cfg(any(not(feature = "fancy"), feature = "another"))]
13+
let _ = 21;
14+
15+
if cfg!(feature = "fancy") {}
16+
}

0 commit comments

Comments
 (0)