From 7953d8cc9345794613ba2e7b90af9429579bc0cf Mon Sep 17 00:00:00 2001 From: Akshay Date: Tue, 21 Sep 2021 20:51:20 +0530 Subject: add error code and report codegen, document a bit --- lib/src/lib.rs | 76 +++++++++++++++++++++++++++------------- lib/src/lints/bool_comparison.rs | 6 ++-- macros/src/lib.rs | 29 +++++++++++++++ 3 files changed, 83 insertions(+), 28 deletions(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 0f92d0d..93792d4 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -6,10 +6,41 @@ pub use lints::LINTS; use rnix::{SyntaxElement, SyntaxKind, TextRange}; use std::{default::Default, convert::Into}; -pub trait Rule { - fn validate(&self, node: &SyntaxElement) -> Option; +/// Report generated by a lint +#[derive(Debug, Default)] +pub struct Report { + /// General information about this lint and where it applies. + pub note: &'static str, + /// An error code to uniquely identify this lint + pub code: u32, + /// Collection of diagnostics raised by this lint + pub diagnostics: Vec, +} + +impl Report { + /// Construct a report. Do not invoke manually, see `lint` macro + pub fn new(note: &'static str, code: u32) -> Self { + Self { + note, + code, + ..Default::default() + } + } + /// Add a diagnostic to this report + pub fn diagnostic(mut self, at: TextRange, message: String) -> Self { + self.diagnostics.push(Diagnostic::new(at, message)); + self + } + /// Add a diagnostic with a fix to this report + pub fn suggest(mut self, at: TextRange, message: String, suggestion: Suggestion) -> Self { + self.diagnostics.push(Diagnostic::suggest(at, message, suggestion)); + self + } + } +/// Mapping from a bytespan to an error message. +/// Can optionally suggest a fix. #[derive(Debug)] pub struct Diagnostic { pub at: TextRange, @@ -18,14 +49,18 @@ pub struct Diagnostic { } impl Diagnostic { + /// Construct a diagnostic. pub fn new(at: TextRange, message: String) -> Self { Self { at, message, suggestion: None } } + /// Construct a diagnostic with a fix. pub fn suggest(at: TextRange, message: String, suggestion: Suggestion) -> Self { Self { at, message, suggestion: Some(suggestion) } } } +/// Suggested fix for a diagnostic, the fix is provided as a syntax element. +/// Look at `make.rs` to construct fixes. #[derive(Debug)] pub struct Suggestion { pub at: TextRange, @@ -33,6 +68,7 @@ pub struct Suggestion { } impl Suggestion { + /// Construct a suggestion. pub fn new>(at: TextRange, fix: E) -> Self { Self { at, @@ -41,39 +77,31 @@ impl Suggestion { } } -#[derive(Debug, Default)] -pub struct Report { - pub diagnostics: Vec, - pub note: &'static str -} - -impl Report { - pub fn new(note: &'static str) -> Self { - Self { - note, - ..Default::default() - } - } - pub fn diagnostic(mut self, at: TextRange, message: String) -> Self { - self.diagnostics.push(Diagnostic::new(at, message)); - self - } - pub fn suggest(mut self, at: TextRange, message: String, suggestion: Suggestion) -> Self { - self.diagnostics.push(Diagnostic::suggest(at, message, suggestion)); - self - } - +/// Lint logic is defined via this trait. Do not implement manually, +/// look at the `lint` attribute macro instead for implementing rules +pub trait Rule { + fn validate(&self, node: &SyntaxElement) -> Option; } +/// Contains information about the lint itself. Do not implement manually, +/// look at the `lint` attribute macro instead for implementing rules pub trait Metadata { fn name() -> &'static str where Self: Sized; fn note() -> &'static str where Self: Sized; + fn code() -> u32 where Self: Sized; + fn report() -> Report where Self: Sized; fn match_with(&self, with: &SyntaxKind) -> bool; fn match_kind(&self) -> SyntaxKind; } +/// Combines Rule and Metadata, do not implement manually, this is derived by +/// the `lint` macro. pub trait Lint: Metadata + Rule + Send + Sync {} +/// Helper utility to take lints from modules and insert them into a map for efficient +/// access. Mapping is from a SyntaxKind to a list of lints that apply on that Kind. +/// +/// See `lints.rs` for usage. #[macro_export] macro_rules! lint_map { ($($s:ident),*,) => { diff --git a/lib/src/lints/bool_comparison.rs b/lib/src/lints/bool_comparison.rs index f869e17..d23a4b0 100644 --- a/lib/src/lints/bool_comparison.rs +++ b/lib/src/lints/bool_comparison.rs @@ -10,6 +10,7 @@ use rnix::{ #[lint( name = "bool_comparison", note = "Unnecessary comparison with boolean", + code = 1, match_with = SyntaxKind::NODE_BIN_OP )] struct BoolComparison; @@ -70,10 +71,7 @@ impl Rule for BoolComparison { non_bool_side, bool_side ); - Some( - Report::new(Self::note()) - .suggest(at, message, Suggestion::new(at, replacement)) - ) + Some(Self::report().suggest(at, message, Suggestion::new(at, replacement))) } else { None } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 36a8bb5..c33fcba 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -53,12 +53,16 @@ fn generate_self_impl(struct_name: &Ident) -> TokenStream2 { fn generate_meta_impl(struct_name: &Ident, meta: &LintMeta) -> TokenStream2 { let name_fn = generate_name_fn(meta); let note_fn = generate_note_fn(meta); + let code_fn = generate_code_fn(meta); + let report_fn = generate_report_fn(); let match_with_fn = generate_match_with_fn(meta); let match_kind = generate_match_kind(meta); quote! { impl Metadata for #struct_name { #name_fn #note_fn + #code_fn + #report_fn #match_with_fn #match_kind } @@ -99,6 +103,31 @@ fn generate_note_fn(meta: &LintMeta) -> TokenStream2 { panic!("Invalid value for `note`"); } +fn generate_code_fn(meta: &LintMeta) -> TokenStream2 { + let code = meta + .0 + .get(&format_ident!("code")) + .unwrap_or_else(|| panic!("`code` not present")); + if let syn::Expr::Lit(code_lit) = code { + if let Lit::Int(code_int) = &code_lit.lit { + return quote! { + fn code() -> u32 { + #code_int + } + }; + } + } + panic!("Invalid value for `note`"); +} + +fn generate_report_fn() -> TokenStream2 { + quote! { + fn report() -> Report { + Report::new(Self::note(), Self::code()) + } + } +} + fn generate_match_with_fn(meta: &LintMeta) -> TokenStream2 { let match_with_lit = meta .0 -- cgit v1.2.3