add error code and report codegen, document a bit

This commit is contained in:
Akshay 2021-09-21 20:51:20 +05:30
parent 3eec886fe8
commit 7953d8cc93
3 changed files with 83 additions and 28 deletions

View File

@ -6,10 +6,41 @@ pub use lints::LINTS;
use rnix::{SyntaxElement, SyntaxKind, TextRange}; use rnix::{SyntaxElement, SyntaxKind, TextRange};
use std::{default::Default, convert::Into}; use std::{default::Default, convert::Into};
pub trait Rule { /// Report generated by a lint
fn validate(&self, node: &SyntaxElement) -> Option<Report>; #[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<Diagnostic>,
} }
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)] #[derive(Debug)]
pub struct Diagnostic { pub struct Diagnostic {
pub at: TextRange, pub at: TextRange,
@ -18,14 +49,18 @@ pub struct Diagnostic {
} }
impl Diagnostic { impl Diagnostic {
/// Construct a diagnostic.
pub fn new(at: TextRange, message: String) -> Self { pub fn new(at: TextRange, message: String) -> Self {
Self { at, message, suggestion: None } Self { at, message, suggestion: None }
} }
/// Construct a diagnostic with a fix.
pub fn suggest(at: TextRange, message: String, suggestion: Suggestion) -> Self { pub fn suggest(at: TextRange, message: String, suggestion: Suggestion) -> Self {
Self { at, message, suggestion: Some(suggestion) } 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)] #[derive(Debug)]
pub struct Suggestion { pub struct Suggestion {
pub at: TextRange, pub at: TextRange,
@ -33,6 +68,7 @@ pub struct Suggestion {
} }
impl Suggestion { impl Suggestion {
/// Construct a suggestion.
pub fn new<E: Into<SyntaxElement>>(at: TextRange, fix: E) -> Self { pub fn new<E: Into<SyntaxElement>>(at: TextRange, fix: E) -> Self {
Self { Self {
at, at,
@ -41,39 +77,31 @@ impl Suggestion {
} }
} }
#[derive(Debug, Default)] /// Lint logic is defined via this trait. Do not implement manually,
pub struct Report { /// look at the `lint` attribute macro instead for implementing rules
pub diagnostics: Vec<Diagnostic>, pub trait Rule {
pub note: &'static str fn validate(&self, node: &SyntaxElement) -> Option<Report>;
}
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
}
} }
/// Contains information about the lint itself. Do not implement manually,
/// look at the `lint` attribute macro instead for implementing rules
pub trait Metadata { pub trait Metadata {
fn name() -> &'static str where Self: Sized; fn name() -> &'static str where Self: Sized;
fn note() -> &'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_with(&self, with: &SyntaxKind) -> bool;
fn match_kind(&self) -> SyntaxKind; 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 {} 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_export]
macro_rules! lint_map { macro_rules! lint_map {
($($s:ident),*,) => { ($($s:ident),*,) => {

View File

@ -10,6 +10,7 @@ use rnix::{
#[lint( #[lint(
name = "bool_comparison", name = "bool_comparison",
note = "Unnecessary comparison with boolean", note = "Unnecessary comparison with boolean",
code = 1,
match_with = SyntaxKind::NODE_BIN_OP match_with = SyntaxKind::NODE_BIN_OP
)] )]
struct BoolComparison; struct BoolComparison;
@ -70,10 +71,7 @@ impl Rule for BoolComparison {
non_bool_side, non_bool_side,
bool_side bool_side
); );
Some( Some(Self::report().suggest(at, message, Suggestion::new(at, replacement)))
Report::new(Self::note())
.suggest(at, message, Suggestion::new(at, replacement))
)
} else { } else {
None None
} }

View File

@ -53,12 +53,16 @@ fn generate_self_impl(struct_name: &Ident) -> TokenStream2 {
fn generate_meta_impl(struct_name: &Ident, meta: &LintMeta) -> TokenStream2 { fn generate_meta_impl(struct_name: &Ident, meta: &LintMeta) -> TokenStream2 {
let name_fn = generate_name_fn(meta); let name_fn = generate_name_fn(meta);
let note_fn = generate_note_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_with_fn = generate_match_with_fn(meta);
let match_kind = generate_match_kind(meta); let match_kind = generate_match_kind(meta);
quote! { quote! {
impl Metadata for #struct_name { impl Metadata for #struct_name {
#name_fn #name_fn
#note_fn #note_fn
#code_fn
#report_fn
#match_with_fn #match_with_fn
#match_kind #match_kind
} }
@ -99,6 +103,31 @@ fn generate_note_fn(meta: &LintMeta) -> TokenStream2 {
panic!("Invalid value for `note`"); 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 { fn generate_match_with_fn(meta: &LintMeta) -> TokenStream2 {
let match_with_lit = meta let match_with_lit = meta
.0 .0