diff --git a/Cargo.toml b/Cargo.toml index 2b203e8b796..792326d6c1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ plugin = true [dependencies] unicode-normalization = "0.1" +semver = "0.2.1" [dev-dependencies] compiletest_rs = "0.0.11" diff --git a/README.md b/README.md index f1c28888636..13105087706 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code. [Jump to usage instructions](#usage) ##Lints -There are 91 lints included in this crate: +There are 92 lints included in this crate: name | default | meaning ---------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -24,6 +24,7 @@ name [cmp_owned](https://github.com/Manishearth/rust-clippy/wiki#cmp_owned) | warn | creating owned instances for comparing with others, e.g. `x == "foo".to_string()` [collapsible_if](https://github.com/Manishearth/rust-clippy/wiki#collapsible_if) | warn | two nested `if`-expressions can be collapsed into one, e.g. `if x { if y { foo() } }` can be written as `if x && y { foo() }` [cyclomatic_complexity](https://github.com/Manishearth/rust-clippy/wiki#cyclomatic_complexity) | warn | finds functions that should be split up into multiple functions +[deprecated_semver](https://github.com/Manishearth/rust-clippy/wiki#deprecated_semver) | warn | `Warn` on `#[deprecated(since = "x")]` where x is not semver [duplicate_underscore_argument](https://github.com/Manishearth/rust-clippy/wiki#duplicate_underscore_argument) | warn | Function arguments having names which only differ by an underscore [empty_loop](https://github.com/Manishearth/rust-clippy/wiki#empty_loop) | warn | empty `loop {}` detected [eq_op](https://github.com/Manishearth/rust-clippy/wiki#eq_op) | warn | equal operands on both sides of a comparison or bitwise combination (e.g. `x == x`) diff --git a/src/attrs.rs b/src/attrs.rs index ec2cfcb0efc..853e2ab5910 100644 --- a/src/attrs.rs +++ b/src/attrs.rs @@ -3,9 +3,10 @@ use rustc::lint::*; use rustc_front::hir::*; use reexport::*; +use semver::Version; use syntax::codemap::Span; use syntax::attr::*; -use syntax::ast::{Attribute, MetaList, MetaWord}; +use syntax::ast::{Attribute, Lit, Lit_, MetaList, MetaWord, MetaNameValue}; use utils::{in_macro, match_path, span_lint, BEGIN_UNWIND}; /// **What it does:** This lint `Warn`s on items annotated with `#[inline(always)]`, unless the annotated function is empty or simply panics. @@ -24,17 +25,45 @@ use utils::{in_macro, match_path, span_lint, BEGIN_UNWIND}; declare_lint! { pub INLINE_ALWAYS, Warn, "`#[inline(always)]` is a bad idea in most cases" } +/// **What it does:** This lint `Warn`s on `#[deprecated]` annotations with a `since` field that is not a valid semantic version.. +/// +/// **Why is this bad?** For checking the version of the deprecation, it must be valid semver. Failing that, the contained information is useless. +/// +/// **Known problems:** None +/// +/// **Example:** +/// ``` +/// #[deprecated(since = "forever")] +/// fn something_else(..) { ... } +/// ``` +declare_lint! { pub DEPRECATED_SEMVER, Warn, + "`Warn` on `#[deprecated(since = \"x\")]` where x is not semver" } #[derive(Copy,Clone)] pub struct AttrPass; impl LintPass for AttrPass { fn get_lints(&self) -> LintArray { - lint_array!(INLINE_ALWAYS) + lint_array!(INLINE_ALWAYS, DEPRECATED_SEMVER) } } impl LateLintPass for AttrPass { + fn check_attribute(&mut self, cx: &LateContext, attr: &Attribute) { + if let MetaList(ref name, ref items) = attr.node.value.node { + if items.is_empty() || name != &"deprecated" { + return; + } + for ref item in items { + if let MetaNameValue(ref name, ref lit) = item.node { + if name == &"since" { + check_semver(cx, item.span, lit); + } + } + } + } + } + fn check_item(&mut self, cx: &LateContext, item: &Item) { if is_relevant_item(item) { check_attrs(cx, item.span, &item.name, &item.attrs) @@ -128,3 +157,15 @@ fn check_attrs(cx: &LateContext, span: Span, name: &Name, attrs: &[Attribute]) { } } } + +fn check_semver(cx: &LateContext, span: Span, lit: &Lit) { + if let Lit_::LitStr(ref is, _) = lit.node { + if Version::parse(&*is).is_ok() { + return; + } + } + span_lint(cx, + DEPRECATED_SEMVER, + span, + "the since field must contain a semver-compliant version"); +} diff --git a/src/lib.rs b/src/lib.rs index 45f47fe3d63..f8db15605ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,9 @@ extern crate collections; // for unicode nfc normalization extern crate unicode_normalization; +// for semver check in attrs.rs +extern crate semver; + extern crate rustc_plugin; use rustc_plugin::Registry; @@ -156,6 +159,7 @@ pub fn plugin_registrar(reg: &mut Registry) { reg.register_lint_group("clippy", vec![ approx_const::APPROX_CONSTANT, array_indexing::OUT_OF_BOUNDS_INDEXING, + attrs::DEPRECATED_SEMVER, attrs::INLINE_ALWAYS, bit_mask::BAD_BIT_MASK, bit_mask::INEFFECTIVE_BIT_MASK, diff --git a/tests/compile-fail/attrs.rs b/tests/compile-fail/attrs.rs index ca7a0d5c07b..c7a4af60982 100644 --- a/tests/compile-fail/attrs.rs +++ b/tests/compile-fail/attrs.rs @@ -1,7 +1,7 @@ -#![feature(plugin)] +#![feature(plugin, deprecated)] #![plugin(clippy)] -#![deny(inline_always)] +#![deny(inline_always, deprecated_semver)] #[inline(always)] //~ERROR you have declared `#[inline(always)]` on `test_attr_lint`. fn test_attr_lint() { @@ -24,6 +24,14 @@ fn empty_and_false_positive_stmt() { unreachable!(); } +#[deprecated(since = "forever")] //~ERROR the since field must contain a semver-compliant version +pub const SOME_CONST : u8 = 42; + +#[deprecated(since = "1")] //~ERROR the since field must contain a semver-compliant version +pub const ANOTHER_CONST : u8 = 23; + +#[deprecated(since = "0.1.1")] +pub const YET_ANOTHER_CONST : u8 = 0; fn main() { test_attr_lint();