Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new lint: unknown features #5643

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1596,6 +1596,7 @@ Released 2018-09-13
[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
[`unknown_features`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_features
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ smallvec = { version = "1", features = ["union"] }
toml = "0.5.3"
unicode-normalization = "0.1"
semver = "0.9.0"
strsim = "0.10"
# NOTE: cargo requires serde feat in its url dep
# see <https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864>
url = { version = "2.1.0", features = ["serde"] }
Expand Down
4 changes: 4 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ mod trivially_copy_pass_by_ref;
mod try_err;
mod types;
mod unicode;
mod unknown_features;
mod unnamed_address;
mod unsafe_removed_from_name;
mod unused_io_amount;
Expand Down Expand Up @@ -355,6 +356,7 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &Co
});
store.register_pre_expansion_pass(|| box attrs::EarlyAttributes);
store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro);
store.register_pre_expansion_pass(|| box unknown_features::UnknownFeatures::default());
}

#[doc(hidden)]
Expand Down Expand Up @@ -835,6 +837,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&unicode::NON_ASCII_LITERAL,
&unicode::UNICODE_NOT_NFC,
&unicode::ZERO_WIDTH_SPACE,
&unknown_features::UNKNOWN_FEATURES,
&unnamed_address::FN_ADDRESS_COMPARISONS,
&unnamed_address::VTABLE_ADDRESS_COMPARISONS,
&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
Expand Down Expand Up @@ -1703,6 +1706,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![
LintId::of(&cargo_common_metadata::CARGO_COMMON_METADATA),
LintId::of(&multiple_crate_versions::MULTIPLE_CRATE_VERSIONS),
LintId::of(&unknown_features::UNKNOWN_FEATURES),
LintId::of(&wildcard_dependencies::WILDCARD_DEPENDENCIES),
]);

Expand Down
129 changes: 129 additions & 0 deletions clippy_lints/src/unknown_features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use rustc_ast::ast::{Attribute, Crate, MacCall, MetaItem, MetaItemKind};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_parse::{self, MACRO_ARGUMENTS};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::DUMMY_SP;

use crate::utils::{span_lint, span_lint_and_then};
use cargo_metadata::MetadataCommand;
use strsim::normalized_damerau_levenshtein;

declare_clippy_lint! {
/// **What it does:** Finds references to features not defined in the cargo manifest file.
///
/// **Why is this bad?** The referred feature will not be recognised and the related item will not be included
/// by the conditional compilation engine.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// #[cfg(feature = "unknown")]
/// fn example() { }
/// ```
pub UNKNOWN_FEATURES,
cargo,
"usage of features not defined in the cargo manifest file"
}

#[derive(Default)]
pub struct UnknownFeatures {
features: FxHashSet<String>,
}

impl_lint_pass!(UnknownFeatures => [UNKNOWN_FEATURES]);

impl EarlyLintPass for UnknownFeatures {
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
fn transform_feature(name: &str, pkg: &str, local_pkg: &str) -> String {
if pkg == local_pkg {
name.into()
} else {
format!("{}/{}", pkg, name)
}
}

let metadata = if let Ok(metadata) = MetadataCommand::new().exec() {
metadata
} else {
span_lint(cx, UNKNOWN_FEATURES, DUMMY_SP, "could not read cargo metadata");
return;
};

if let Some(local_pkg) = &cx.sess.opts.crate_name {
for pkg in metadata.packages {
self.features.extend(
pkg.features
.keys()
.map(|name| transform_feature(name, &pkg.name, local_pkg)),
);
}
}
}

fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
if attr.check_name(sym!(cfg)) {
if let Some(item) = &attr.meta() {
self.walk_cfg_metas(cx, item);
}
}
}

fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
if mac.path == sym!(cfg) {
let tts = mac.args.inner_tokens();
let mut parser = rustc_parse::stream_to_parser(&cx.sess.parse_sess, tts, MACRO_ARGUMENTS);
if let Ok(item) = parser.parse_meta_item() {
self.walk_cfg_metas(cx, &item);
}
}
}
}

impl UnknownFeatures {
fn walk_cfg_metas(&mut self, cx: &EarlyContext<'_>, item: &MetaItem) {
match &item.kind {
MetaItemKind::List(items) => {
for nested in items {
if let Some(item) = nested.meta_item() {
self.walk_cfg_metas(cx, item);
}
}
},
MetaItemKind::NameValue(lit) if item.name_or_empty().as_str() == "feature" => {
if let Some(value) = item.value_str() {
let feature = &*value.as_str();
if !self.features.contains(feature) {
let message = format!("unknown feature `{}`", feature);
span_lint_and_then(cx, UNKNOWN_FEATURES, lit.span, &message, |diag| {
if let Some(similar_name) = self.find_similar_name(feature) {
diag.span_suggestion(
lit.span,
"a feature with a similar name exists",
format!("\"{}\"", similar_name),
Applicability::MaybeIncorrect,
);
}
});
}
}
},
_ => {},
}
}

fn find_similar_name(&self, name: &str) -> Option<String> {
let mut similar: Vec<_> = self
.features
.iter()
.map(|f| (f, normalized_damerau_levenshtein(name, f)))
.filter(|(_, sim)| *sim >= 0.7)
.collect();

similar.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap());
similar.into_iter().next().map(|(f, _)| f.clone())
}
}
7 changes: 7 additions & 0 deletions src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2257,6 +2257,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
deprecation: None,
module: "attrs",
},
Lint {
name: "unknown_features",
group: "cargo",
desc: "usage of features not defined in the cargo manifest file",
deprecation: None,
module: "unknown_features",
},
Lint {
name: "unnecessary_cast",
group: "complexity",
Expand Down
5 changes: 4 additions & 1 deletion tests/compile-test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,11 @@ fn run_ui_cargo(config: &mut compiletest::Config) {
}

let src_path = case.path().join("src");
env::set_current_dir(&src_path)?;
if !src_path.exists() {
continue;
}

env::set_current_dir(&src_path)?;
for file in fs::read_dir(&src_path)? {
let file = file?;
if file.file_type()?.is_dir() {
Expand Down
11 changes: 11 additions & 0 deletions tests/ui-cargo/unknown_features/fail/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

# Features referenced in the code are not found in this manifest

[package]
name = "unknown_features"
version = "0.1.0"
publish = false

[features]
misspelled = []
another = []
15 changes: 15 additions & 0 deletions tests/ui-cargo/unknown_features/fail/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// compile-flags: --crate-name=unknown_features --cfg feature="misspelled" --cfg feature="another"
#![warn(clippy::unknown_features)]

fn main() {
#[cfg(feature = "mispelled")]
let _ = 42;

#[cfg(feature = "dependency/unknown")]
let _ = 42;

#[cfg(any(not(feature = "misspeled"), feature = "not-found"))]
let _ = 21;

if cfg!(feature = "nothe") {}
}
34 changes: 34 additions & 0 deletions tests/ui-cargo/unknown_features/fail/src/main.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
error: unknown feature `mispelled`
--> $DIR/main.rs:5:21
|
LL | #[cfg(feature = "mispelled")]
| ^^^^^^^^^^^ help: a feature with a similar name exists: `"misspelled"`
|
= note: `-D clippy::unknown-features` implied by `-D warnings`

error: unknown feature `dependency/unknown`
--> $DIR/main.rs:8:21
|
LL | #[cfg(feature = "dependency/unknown")]
| ^^^^^^^^^^^^^^^^^^^^

error: unknown feature `misspeled`
--> $DIR/main.rs:11:29
|
LL | #[cfg(any(not(feature = "misspeled"), feature = "not-found"))]
| ^^^^^^^^^^^ help: a feature with a similar name exists: `"misspelled"`

error: unknown feature `not-found`
--> $DIR/main.rs:11:53
|
LL | #[cfg(any(not(feature = "misspeled"), feature = "not-found"))]
| ^^^^^^^^^^^

error: unknown feature `nothe`
--> $DIR/main.rs:14:23
|
LL | if cfg!(feature = "nothe") {}
| ^^^^^^^ help: a feature with a similar name exists: `"another"`

error: aborting due to 5 previous errors

14 changes: 14 additions & 0 deletions tests/ui-cargo/unknown_features/pass/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

# Features from this crate and from dependencies are correctly referenced in the code

[package]
name = "unknown_features"
version = "0.1.0"
publish = false

[dependencies]
serde = { version = "1.0.110", features = ["derive"] }

[features]
fancy = []
another = []
16 changes: 16 additions & 0 deletions tests/ui-cargo/unknown_features/pass/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// compile-flags: --crate-name=unknown_features --cfg feature="fancy" --cfg feature="another"
// compile-flags: --cfg feature="serde/derive"
#![warn(clippy::unknown_features)]

fn main() {
#[cfg(feature = "fancy")]
let _ = 42;

#[cfg(feature = "serde/derive")]
let _ = 42;

#[cfg(any(not(feature = "fancy"), feature = "another"))]
let _ = 21;

if cfg!(feature = "fancy") {}
}