mirror of
https://github.com/oppiliappan/statix.git
synced 2024-09-11 17:25:33 +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 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),*,) => {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user