1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
//! TODOs you cannot forget!
//!
//! `todo-or-die` provides procedural macros that act as checked reminders.
//!
//! The name was shamelessly stolen from the ruby gem [`todo_or_die`][ruby].
//!
//! # Examples
//!
//! ```
//! // trigger a compile error if we're past a certain date
//! todo_or_die::after_date!(3000, 1, 1); // its the year 3000!
//!
//! // or a GitHub issue has closed
//! todo_or_die::issue_closed!("rust-lang", "rust", 44265); // GATs are here!
//!
//! // or the latest version of a crate matches some expression
//! todo_or_die::crates_io!("serde", ">1.0.9000"); // its over 9000!
//! ```
//!
//! # Feature flags
//!
//! The following optional features are available:
//!
//! - `crate`: Enables checking versions of crates.
//! - `github`: Enables checking if issues or pull requests are closed.
//! - `rust`: Enables checking the current rust version.
//! - `time`: Enables checking things to do with time.
//!
//! Note that _none_ of the features are enabled by default.
//!
//! # Skipping checks
//!
//! If the environment variable `TODO_OR_DIE_SKIP` is set all macros will do
//! nothing and immediately succeed. This can for example be used to skip checks
//! locally and only perform them on CI.
//!
//! # Caching HTTP requests
//!
//! By default HTTP requests will be cached. The behavior can be customized with
//! these environment variables:
//! - `TODO_OR_DIE_HTTP_CACHE_TTL_SECONDS`: How long cached responses will be
//! used. The default is 1 hour.
//! - `TODO_OR_DIE_DISABLE_HTTP_CACHE`: Disables caching if its set.
//! - `TODO_OR_DIE_CLEAR_HTTP_CACHE`: Clears the cache if its set.
//!
//! The cache is stored at `std::env::temp_dir().join("todo_or_die_cache")`.
//!
//! # You can still compile offline
//!
//! If you're offline or GitHub is down you can still build. If the macros hit
//! some kind of error a warning will be printed but they wont trigger a compile
//! error.
//!
//! [ruby]: https://rubygems.org/gems/todo_or_die
#![warn(
clippy::all,
clippy::dbg_macro,
clippy::todo,
clippy::empty_enum,
clippy::enum_glob_use,
clippy::mem_forget,
clippy::unused_self,
clippy::filter_map_next,
clippy::needless_continue,
clippy::needless_borrow,
clippy::match_wildcard_for_single_variants,
clippy::if_let_mutex,
clippy::mismatched_target_os,
clippy::await_holding_lock,
clippy::match_on_vec_items,
clippy::imprecise_flops,
clippy::suboptimal_flops,
clippy::lossy_float_literal,
clippy::rest_pat_in_fully_bound_structs,
clippy::fn_params_excessive_bools,
clippy::exit,
clippy::inefficient_to_string,
clippy::linkedlist,
clippy::macro_use_imports,
clippy::option_option,
clippy::verbose_file_reads,
clippy::unnested_or_patterns,
rust_2018_idioms,
future_incompatible,
nonstandard_style,
missing_debug_implementations,
missing_docs
)]
#![deny(unreachable_pub, private_in_public)]
#![allow(elided_lifetimes_in_paths, clippy::type_complexity)]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(test, allow(clippy::float_cmp))]
#[cfg(feature = "__internal_http")]
mod http;
#[cfg(feature = "github")]
mod github;
#[cfg(feature = "time")]
mod time;
#[cfg(feature = "crate")]
mod krate;
#[cfg(feature = "rust")]
mod rust;
/// Trigger a compile error if an issue has been closed.
///
/// Note that this will make network requests during compile which may make your builds flaky at
/// times.
///
/// Requires the `github` feature to be enabled.
///
/// # Example
///
/// ```compile_fail
/// todo_or_die::issue_closed!("tokio-rs", "axum", 1);
/// ```
///
/// # Authentication
///
/// `issue_closed` will first look for the environment variable `TODO_OR_DIE_GITHUB_TOKEN` and then
/// `GITHUB_TOKEN`, if either are found its value will be used as the auth token when making
/// requests to the GitHub API. This allows you to access private repos and get more generous
/// rate limits.
#[cfg(feature = "github")]
#[proc_macro]
pub fn issue_closed(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
perform_check(input, github::issue_closed)
}
/// Trigger a compile error if a pull request has been closed or merged.
///
/// Note that this will make network requests during compile which may make your builds flaky at
/// times.
///
/// Requires the `github` feature to be enabled.
///
/// # Example
///
/// ```compile_fail
/// todo_or_die::pr_closed!("tokio-rs/axum#266");
/// // todo_or_die::pr_closed!("tokio-rs", "axum", 266);
/// ```
///
/// # Authentication
///
/// `pr_closed` will first look for the environment variable `TODO_OR_DIE_GITHUB_TOKEN` and then
/// `GITHUB_TOKEN`, if either are found its value will be used as the auth token when making
/// requests to the GitHub API. This allows you to access private repos and get more generous
/// rate limits.
#[cfg(feature = "github")]
#[proc_macro]
pub fn pr_closed(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
perform_check(input, github::pr_closed)
}
/// Trigger a compile error if today is after the given date
///
/// Requires the `time` feature to be enabled.
///
/// # Example
///
/// ```compile_fail
/// todo_or_die::after_date!(1994, 10, 22);
/// ```
#[cfg(feature = "time")]
#[proc_macro]
pub fn after_date(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
perform_check(input, time::after_date)
}
/// Trigger a compile error if the latest version of a crate hosted on crates.io matches some
/// expression.
///
/// Note that this will make network requests during compile which may make your builds flaky at
/// times.
///
/// Requires the `crate` feature to be enabled.
///
/// # Example
///
/// ```compile_fail
/// todo_or_die::crates_io!("tokio", ">=1.0");
/// ```
///
/// Any version requirement supported by [`semver::VersionReq::parse`] is supported.
///
/// [`semver::VersionReq::parse`]: https://docs.rs/semver/latest/semver/struct.VersionReq.html#method.parse
#[cfg(feature = "crate")]
#[proc_macro]
pub fn crates_io(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
perform_check(input, krate::crates_io)
}
/// Trigger a compile error if the currently used version of rust used matches some expression.
///
/// Note that release channels (like `nightly` or `beta`) are ignored.
///
/// Requires the `rust` feature to be enabled.
///
/// # Example
///
/// ```compile_fail
/// todo_or_die::rust_version!(">1.50");
/// ```
///
/// Any version requirement supported by [`semver::VersionReq::parse`] is supported.
///
/// [`semver::VersionReq::parse`]: https://docs.rs/semver/latest/semver/struct.VersionReq.html#method.parse
#[cfg(feature = "rust")]
#[proc_macro]
pub fn rust_version(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
perform_check(input, rust::rust_version)
}
#[allow(dead_code)]
fn perform_check<F, T>(input: proc_macro::TokenStream, f: F) -> proc_macro::TokenStream
where
F: FnOnce(T) -> anyhow::Result<Option<String>>,
T: syn::parse::Parse,
{
if std::env::var("TODO_OR_DIE_SKIP").is_ok() {
return Default::default();
}
let input = match syn::parse::<T>(input) {
Ok(value) => value,
Err(err) => {
let err = err.to_string();
return quote::quote! {
::std::compile_error!(#err);
}
.into();
}
};
match f(input) {
Ok(None) => {}
Ok(Some(msg)) => {
return quote::quote! {
::std::compile_error!(#msg);
}
.into();
}
Err(err) => {
eprintln!("something went wrong\n\n{:?}", err);
}
}
Default::default()
}