Skip to content

Commit

Permalink
Initial revision
Browse files Browse the repository at this point in the history
  • Loading branch information
samueltardieu committed Jul 15, 2023
0 parents commit 850e0ca
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
/Cargo.lock
24 changes: 24 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "deprecate-until"
description = "Rust attribute to force deprecated item removal at a specified version"
authors = ["Samuel Tardieu <sam@rfc1149.net>"]
repository = "https://github.com/samueltardieu/deprecate-until"
keywords = ["macros"]
version = "0.1.0"
edition = "2021"
license = "Apache-2.0/MIT"
categories = ["development-tools"]
readme = "README.md"
rust-version = "1.60.0"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = { version = "1.0.64", default-features = false }
quote = { version = "1.0.29", default-features = false }
semver = { version = "1.0.17", default-features = false }
syn = { version = "2.0.26", default-features = false, features = ["parsing", "proc-macro"] }

[dev-dependencies]
trybuild = "1.0.81"
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# deprecate-until

[![Current Version](https://img.shields.io/crates/v/deprecate_until.svg)](https://crates.io/crates/deprecate_until)
[![Documentation](https://docs.rs/deprecate_until/badge.svg)](https://docs.rs/deprecate_until)
[![License: Apache-2.0/MIT](https://img.shields.io/crates/l/deprecate_until.svg)](#license)

This crate introduces a new `deprecate_until` attribute which helps
crate authors not to remove to delete obsolete items when some version
is reached. When the specified semver condition is verified, the crate
will not compile anymore and hopefully this will be caught by the CI
or by `cargo install` before the release actually happens.

## Usage

The `deprecate_until` attribute supports the same arguments as
`deprecate`, *i.e.*, `note` and `since`. It also requires a `remove`
argument, which is a [semver](https://semver.org/) requirement
expression in a string.

## Example

The following code

```rust
use deprecate_until::deprecate_until;

#[deprecate_until(remove = ">= 4.x", note = "use `some_new_function` instead")]
fn old_function() {
todo!()
}
```

will give a warning when version 3.8 of the crate is used in a project:

```txt
warning: use of deprecated function `old_function`: use `some_new_function` instead (removal scheduled for version >= 4.x)
|
4 | fn old_function() {
| ^^^^^^^^^^^^
```

It will also cause a compilation error in version 4.0.0 of the crate if you forgot to remove it:

```txt
error: version `4.0.0` matches `>= 4.x`, item should be removed
|
3 | #[deprecate_until(remove = ">= 4.x", note = "use `some_new_function` instead")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
111 changes: 111 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#![forbid(missing_docs)]
#![doc = include_str!("../README.md")]
#![warn(clippy::pedantic)]

use std::collections::HashMap;

use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::parse::Parse;
use syn::{parse_macro_input, Ident, LitStr, Token};

struct Args {
since: Option<String>,
note: Option<String>,
remove: String,
}

impl Parse for Args {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut args = ["since", "note", "remove"]
.into_iter()
.map(|s| (String::from(s), None))
.collect::<HashMap<_, _>>();
while !input.is_empty() {
let (ident, _, value) = (
input.parse::<Ident>()?,
input.parse::<Token![=]>()?,
input.parse::<LitStr>()?,
);
match args.insert(ident.to_string(), Some(value.value())) {
None => {
return Err(syn::Error::new(
ident.span(),
format!("unknown meta item '{ident}'"),
))
}
Some(Some(_)) => {
return Err(syn::Error::new(
ident.span(),
format!("duplicate '{ident}' items"),
))
}
Some(None) => (),
}
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
if let Some(Some(remove)) = args.remove("remove") {
Ok(Args {
since: args.remove("since").unwrap(),
note: args.remove("note").unwrap(),
remove,
})
} else {
Err(syn::Error::new(
Span::call_site(),
"mandatory 'remove' item missing",
))
}
}
}

impl ToTokens for Args {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let removal = format!("removal scheduled for version {}", self.remove);
let note = match &self.note {
Some(n) => format!("{n} ({removal})"),
None => removal,
};
let args = if let Some(since) = &self.since {
quote!(since = #since, note = #note)
} else {
quote!(note = #note)
};
tokens.extend(quote!(#[deprecated(#args)]));
}
}

macro_rules! error {
($fmt:literal $(,$args:expr)*$(,)?) => {{
let error = format!($fmt $(,$args)*);
return quote! { compile_error!(#error); }.into();
}}
}

/// Similar to Rust `deprecated` attribute, with the addition of a mandatory
/// `remove` attribute argument which contains a semver requirement at which
/// the item must be definitely removed from the source code.
///
/// See the [crate level documentation](self) for a complete description.
#[proc_macro_attribute]
pub fn deprecate_until(args: TokenStream, tokens: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as Args);
let remove = match semver::VersionReq::parse(&args.remove) {
Ok(c) => c,
Err(e) => error!("version '{}' cannot be parsed: {e:?}", args.remove),
};
if let Ok(version) = std::env::var("CARGO_PKG_VERSION") {
let version = match semver::Version::parse(&version) {
Ok(v) => v,
Err(e) => error!("unable to parse current version '{version}': {e:?}"),
};
if remove.matches(&version) {
error!("version '{version}' matches '{remove}', item should be removed");
}
}
let tokens: proc_macro2::TokenStream = tokens.into();
quote!(#args #tokens).into()
}
7 changes: 7 additions & 0 deletions tests/pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![allow(deprecated)]

use deprecate_until::deprecate_until;

#[deprecate_until(since = "0.1", note = "Please ignore", remove = ">= 2.0")]
#[test]
fn ok() {}
5 changes: 5 additions & 0 deletions tests/ui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}
27 changes: 27 additions & 0 deletions tests/ui/fail.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#![allow(deprecated)]

use deprecate_until::deprecate_until;

#[deprecate_until(remove = ">= 0.0")]
fn f1() {}

#[deprecate_until(remove = ">= 0.0", note = "Remove me")]
fn f2() {}

#[deprecate_until(remove = ">= 0.0", since = "0.0.0", note = "Remove me")]
fn f3() {}

#[deprecate_until(remove = "x.1")]
fn f4() {}

#[deprecate_until(note = "foo")]
fn f5() {}

#[deprecate_until(remove = "1.x", remove = "2.x")]
fn f6() {}

#[deprecate_until(bogus = "bar")]
fn f7() {}

fn main() {
}
51 changes: 51 additions & 0 deletions tests/ui/fail.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
error: version '0.0.0' matches '>=0.0', item should be removed
--> tests/ui/fail.rs:5:1
|
5 | #[deprecate_until(remove = ">= 0.0")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `deprecate_until` (in Nightly builds, run with -Z macro-backtrace for more info)

error: version '0.0.0' matches '>=0.0', item should be removed
--> tests/ui/fail.rs:8:1
|
8 | #[deprecate_until(remove = ">= 0.0", note = "Remove me")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `deprecate_until` (in Nightly builds, run with -Z macro-backtrace for more info)

error: version '0.0.0' matches '>=0.0', item should be removed
--> tests/ui/fail.rs:11:1
|
11 | #[deprecate_until(remove = ">= 0.0", since = "0.0.0", note = "Remove me")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `deprecate_until` (in Nightly builds, run with -Z macro-backtrace for more info)

error: version 'x.1' cannot be parsed: Error("unexpected character after wildcard in version req")
--> tests/ui/fail.rs:14:1
|
14 | #[deprecate_until(remove = "x.1")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `deprecate_until` (in Nightly builds, run with -Z macro-backtrace for more info)

error: mandatory 'remove' item missing
--> tests/ui/fail.rs:17:1
|
17 | #[deprecate_until(note = "foo")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `deprecate_until` (in Nightly builds, run with -Z macro-backtrace for more info)

error: duplicate 'remove' items
--> tests/ui/fail.rs:20:35
|
20 | #[deprecate_until(remove = "1.x", remove = "2.x")]
| ^^^^^^

error: unknown meta item 'bogus'
--> tests/ui/fail.rs:23:19
|
23 | #[deprecate_until(bogus = "bar")]
| ^^^^^
18 changes: 18 additions & 0 deletions tests/ui/warn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![deny(warnings)]

use deprecate_until::deprecate_until;

#[deprecate_until(remove = ">= 1.0")]
fn f1() {}

#[deprecate_until(remove = ">= 1.0", note = "Remove me")]
fn f2() {}

#[deprecate_until(remove = ">= 1.0", since = "1.0.0", note = "Remove me")]
fn f3() {}

fn main() {
f1();
f2();
f3();
}
24 changes: 24 additions & 0 deletions tests/ui/warn.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
error: use of deprecated function `f1`: removal scheduled for version >= 1.0
--> tests/ui/warn.rs:15:5
|
15 | f1();
| ^^
|
note: the lint level is defined here
--> tests/ui/warn.rs:1:9
|
1 | #![deny(warnings)]
| ^^^^^^^^
= note: `#[deny(deprecated)]` implied by `#[deny(warnings)]`

error: use of deprecated function `f2`: Remove me (removal scheduled for version >= 1.0)
--> tests/ui/warn.rs:16:5
|
16 | f2();
| ^^

error: use of deprecated function `f3`: Remove me (removal scheduled for version >= 1.0)
--> tests/ui/warn.rs:17:5
|
17 | f3();
| ^^

0 comments on commit 850e0ca

Please sign in to comment.