mirror of
https://github.com/oppiliappan/statix.git
synced 2024-08-17 05:00:24 +03:00
add error code and report codegen, document a bit
This commit is contained in:
parent
3eec886fe8
commit
7953d8cc93
@ -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),*,) => {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user