-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 850e0ca
Showing
10 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/target | ||
/Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")] | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")] | ||
| ^^^^^ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
| ^^ |