From e8c955da4cbb042e6f9b89307d143f5bfa6779fa Mon Sep 17 00:00:00 2001 From: Akshay Date: Sun, 31 Oct 2021 14:35:26 +0530 Subject: add `explain` subcommand and explanations to all lints --- macros/src/metadata.rs | 177 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 macros/src/metadata.rs (limited to 'macros/src/metadata.rs') diff --git a/macros/src/metadata.rs b/macros/src/metadata.rs new file mode 100644 index 0000000..b41eb9c --- /dev/null +++ b/macros/src/metadata.rs @@ -0,0 +1,177 @@ +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 + } + } +} -- cgit v1.2.3