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 std::{default::Default, convert::Into};
pub trait Rule {
fn validate(&self, node: &SyntaxElement) -> Option<Report>;
/// 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<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)]
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<E: Into<SyntaxElement>>(at: TextRange, fix: E) -> Self {
Self {
at,
@ -41,39 +77,31 @@ impl Suggestion {
}
}
#[derive(Debug, Default)]
pub struct Report {
pub diagnostics: Vec<Diagnostic>,
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<Report>;
}
/// 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),*,) => {

View File

@ -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
}

View File

@ -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