use std::collections::HashMap; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use syn::{ parse::{Parse, ParseStream, Result}, punctuated::Punctuated, Expr, ExprArray, Ident, Lit, Path, Token, }; struct KeyValue { key: Ident, _eq: Token![=], value: Expr, } impl Parse for KeyValue { fn parse(input: ParseStream) -> Result { Ok(Self { key: input.parse()?, _eq: input.parse()?, value: input.parse()?, }) } } pub struct RawLintMeta(HashMap); impl Parse for RawLintMeta { fn parse(input: ParseStream) -> Result { Ok(Self( Punctuated::::parse_terminated(input)? .into_iter() .map(|item| (item.key, item.value)) .collect(), )) } } pub struct LintMeta<'μ> { name: &'μ Lit, note: &'μ Lit, code: &'μ Lit, match_with: MatchWith<'μ>, } enum MatchWith<'π> { Path(&'π Path), Array(&'π ExprArray), } fn extract<'λ>(id: &str, raw: &'λ RawLintMeta) -> &'λ Expr { raw.0 .get(&format_ident!("{}", id)) .unwrap_or_else(|| panic!("`{}` not present", id)) } fn as_lit(e: &Expr) -> &Lit { match e { Expr::Lit(l) => &l.lit, _ => panic!("expected a literal"), } } impl<'μ> LintMeta<'μ> { fn from_raw(raw: &'μ RawLintMeta) -> Self { let name = as_lit(extract("name", raw)); let note = as_lit(extract("note", raw)); let code = as_lit(extract("code", raw)); let match_with_expr = extract("match_with", raw); let match_with = match match_with_expr { Expr::Path(p) => MatchWith::Path(&p.path), Expr::Array(a) => MatchWith::Array(a), _ => panic!("`match_with` is neither a path nor an array"), }; Self { name, note, code, match_with, } } fn generate_name_fn(&self) -> TokenStream2 { let name_str = self.name; return quote! { fn name(&self) -> &'static str { #name_str } }; } fn generate_note_fn(&self) -> TokenStream2 { let note_str = self.note; return quote! { fn note(&self) -> &'static str { #note_str } }; } fn generate_code_fn(&self) -> TokenStream2 { let code_int = self.code; return quote! { fn code(&self) -> u32 { #code_int } }; } fn generate_match_with_fn(&self) -> TokenStream2 { match self.match_with { MatchWith::Path(p) => { quote! { fn match_with(&self, with: &SyntaxKind) -> bool { #p == *with } } } MatchWith::Array(a) => { quote! { fn match_with(&self, with: &SyntaxKind) -> bool { #a.contains(with) } } } } } fn generate_match_kind_fn(&self) -> TokenStream2 { match self.match_with { MatchWith::Path(p) => { quote! { fn match_kind(&self) -> Vec { vec![#p] } } } MatchWith::Array(a) => { quote! { fn match_kind(&self) -> Vec { #a.to_vec() } } } } } fn generate_report_fn(&self) -> TokenStream2 { quote! { fn report(&self) -> crate::Report { crate::Report::new(self.note(), self.code()) } } } } pub fn generate_meta_impl(struct_name: &Ident, meta: &RawLintMeta) -> TokenStream2 { let not_raw = LintMeta::from_raw(meta); let name_fn = not_raw.generate_name_fn(); let note_fn = not_raw.generate_note_fn(); let code_fn = not_raw.generate_code_fn(); let match_with_fn = not_raw.generate_match_with_fn(); let match_kind = not_raw.generate_match_kind_fn(); let report_fn = not_raw.generate_report_fn(); quote! { impl crate::Metadata for #struct_name { #name_fn #note_fn #code_fn #match_with_fn #match_kind #report_fn } } }