blob: 15577217358312d420a434c82f14da3142c916fb [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use proc_macro::TokenStream;
use quote::quote;
const FXBUG_REGEX: &str = r"https://fxbug\.dev/[0-9]+";
/// Provides an unambiguous conditional compilation tied to a URL for bug
/// tracking.
///
/// This macro expands to annotate items with `[cfg(test)]`, and is meant to be
/// used in production code that is being built iteratively and which has tests,
/// but doesn't yet have production usages outside of test configurations.
///
/// Example:
///
/// ```
/// #[todo_unused::todo_unused("https://fxbug.dev/000")]
/// fn new_yet_unused_functionality() {}
/// ```
/// Expands to
///
/// ```
/// #[cfg(test)]
/// fn new_yet_unused_functionality() {}
/// ```
///
/// `cfg(test)` is preferable to `allow(dead_code)` because it cannot rot, while
/// a dead code allowance may end up outliving its usefulness if it gets used
/// far away. The choice to encode a URL with a trackable and actionable bug is
/// a cultural statement that changes should be kept small enough to be
/// digestible, but that any code that is not exercised in production should be
/// tracked and followed up on with urgency.
#[proc_macro_attribute]
pub fn todo_unused(attrs: TokenStream, input: TokenStream) -> TokenStream {
if attrs.is_empty() {
return syn::Error::new_spanned(
proc_macro2::TokenStream::from(attrs),
"missing required bug URL",
)
.to_compile_error()
.into();
}
let attrs = syn::parse_macro_input!(attrs as syn::LitStr);
let re = regex::Regex::new(FXBUG_REGEX).expect("failed to compile internal regex");
if !re.is_match(&attrs.value()) {
return syn::Error::new_spanned(
attrs,
"bug URL must be in the form https://fxbug.dev/0000",
)
.to_compile_error()
.into();
}
let input = proc_macro2::TokenStream::from(input);
quote! {
#[cfg(test)]
#input
}
.into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fxbug_regex() {
let re = regex::Regex::new(FXBUG_REGEX).unwrap();
for (str, expect_match) in [
("https://fxbug.dev/42074312", true),
("http://fxbug.dev/42074312", false),
("https://fxbug.dev/", false),
("https://fxbugxdev/1234", false),
] {
assert_eq!(re.is_match(str), expect_match, "{}", str);
}
}
}