|
| 1 | +use std::cell::RefCell; |
| 2 | +use std::collections::{HashMap, HashSet}; |
| 3 | +use std::ops::Range; |
| 4 | + |
| 5 | +use devise::ext::PathExt; |
| 6 | +use proc_macro2::{Span, TokenStream}; |
| 7 | +use syn::{parse::Parser, punctuated::Punctuated}; |
| 8 | + |
| 9 | +macro_rules! declare_lints { |
| 10 | + ($($name:ident ( $string:literal) ),* $(,)?) => ( |
| 11 | + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| 12 | + pub enum Lint { |
| 13 | + $($name),* |
| 14 | + } |
| 15 | + |
| 16 | + impl Lint { |
| 17 | + fn from_str(string: &str) -> Option<Self> { |
| 18 | + $(if string.eq_ignore_ascii_case($string) { |
| 19 | + return Some(Lint::$name); |
| 20 | + })* |
| 21 | + |
| 22 | + None |
| 23 | + } |
| 24 | + |
| 25 | + fn as_str(&self) -> &'static str { |
| 26 | + match self { |
| 27 | + $(Lint::$name => $string),* |
| 28 | + } |
| 29 | + } |
| 30 | + |
| 31 | + fn lints() -> &'static str { |
| 32 | + concat!("[" $(,$string,)", "* "]") |
| 33 | + } |
| 34 | + } |
| 35 | + ) |
| 36 | +} |
| 37 | + |
| 38 | +declare_lints! { |
| 39 | + UnknownFormat("unknown_format"), |
| 40 | + DubiousPayload("dubious_payload"), |
| 41 | + SegmentChars("segment_chars"), |
| 42 | + ArbitraryMain("arbitrary_main"), |
| 43 | + SyncSpawn("sync_spawn"), |
| 44 | +} |
| 45 | + |
| 46 | +thread_local! { |
| 47 | + static SUPPRESSIONS: RefCell<HashMap<Lint, HashSet<Range<usize>>>> = RefCell::default(); |
| 48 | +} |
| 49 | + |
| 50 | +fn span_to_range(span: Span) -> Option<Range<usize>> { |
| 51 | + let string = format!("{span:?}"); |
| 52 | + let i = string.find('(')?; |
| 53 | + let j = string[i..].find(')')?; |
| 54 | + let (start, end) = string[(i + 1)..(i + j)].split_once("..")?; |
| 55 | + Some(Range { start: start.parse().ok()?, end: end.parse().ok()? }) |
| 56 | +} |
| 57 | + |
| 58 | +impl Lint { |
| 59 | + pub fn suppress_attrs(attrs: &[syn::Attribute], ctxt: Span) { |
| 60 | + let _ = attrs.iter().try_for_each(|attr| Lint::suppress_attr(attr, ctxt)); |
| 61 | + } |
| 62 | + |
| 63 | + pub fn suppress_attr(attr: &syn::Attribute, ctxt: Span) -> Result<(), syn::Error> { |
| 64 | + let syn::Meta::List(list) = &attr.meta else { |
| 65 | + return Ok(()); |
| 66 | + }; |
| 67 | + |
| 68 | + if !list.path.last_ident().map_or(false, |i| i == "suppress") { |
| 69 | + return Ok(()); |
| 70 | + } |
| 71 | + |
| 72 | + Self::suppress_tokens(list.tokens.clone(), ctxt) |
| 73 | + } |
| 74 | + |
| 75 | + pub fn suppress_tokens(attr_tokens: TokenStream, ctxt: Span) -> Result<(), syn::Error> { |
| 76 | + let lints = Punctuated::<Lint, syn::Token![,]>::parse_terminated.parse2(attr_tokens)?; |
| 77 | + lints.iter().for_each(|lint| lint.suppress(ctxt)); |
| 78 | + Ok(()) |
| 79 | + } |
| 80 | + |
| 81 | + pub fn suppress(self, ctxt: Span) { |
| 82 | + SUPPRESSIONS.with_borrow_mut(|s| { |
| 83 | + let range = span_to_range(ctxt).unwrap_or_default(); |
| 84 | + s.entry(self).or_default().insert(range); |
| 85 | + }) |
| 86 | + } |
| 87 | + |
| 88 | + pub fn is_suppressed(self, ctxt: Span) -> bool { |
| 89 | + SUPPRESSIONS.with_borrow(|s| { |
| 90 | + let this = span_to_range(ctxt).unwrap_or_default(); |
| 91 | + s.get(&self).map_or(false, |set| { |
| 92 | + set.iter().any(|r| this.start >= r.start && this.end <= r.end) |
| 93 | + }) |
| 94 | + }) |
| 95 | + } |
| 96 | + |
| 97 | + pub fn enabled(self, ctxt: Span) -> bool { |
| 98 | + !self.is_suppressed(ctxt) |
| 99 | + } |
| 100 | + |
| 101 | + pub fn how_to_suppress(self) -> String { |
| 102 | + format!("apply `#[suppress({})]` before the item to suppress this lint", self.as_str()) |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +impl syn::parse::Parse for Lint { |
| 107 | + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> { |
| 108 | + let ident: syn::Ident = input.parse()?; |
| 109 | + let name = ident.to_string(); |
| 110 | + Lint::from_str(&name).ok_or_else(|| { |
| 111 | + let msg = format!("invalid lint `{name}` (known lints: {})", Lint::lints()); |
| 112 | + syn::Error::new(ident.span(), msg) |
| 113 | + }) |
| 114 | + } |
| 115 | +} |
0 commit comments