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()
}