mod lints; mod make; pub use lints::LINTS; use rnix::{SyntaxElement, SyntaxKind, TextRange}; use std::{convert::Into, default::Default}; #[cfg(feature = "json-out")] use serde::{Serialize, ser::{SerializeStruct, Serializer}}; /// Report generated by a lint #[derive(Debug, Default)] #[cfg_attr(feature = "json-out", derive(Serialize))] 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 `Report::new` 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: S) -> 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: S, suggestion: Suggestion, ) -> Self { self.diagnostics .push(Diagnostic::suggest(at, message, suggestion)); self } /// A range that encompasses all the suggestions provided in this report pub fn total_suggestion_range(&self) -> Option { self.diagnostics .iter() .flat_map(|d| Some(d.suggestion.as_ref()?.at)) .reduce(|acc, next| acc.cover(next)) } /// Unsafe but handy replacement for above pub fn range(&self) -> TextRange { self.total_suggestion_range().unwrap() } /// Apply all diagnostics. Assumption: diagnostics do not overlap pub fn apply(&self, src: &mut String) { for d in self.diagnostics.iter() { d.apply(src); } } } /// Mapping from a bytespan to an error message. /// Can optionally suggest a fix. #[derive(Debug)] pub struct Diagnostic { pub at: TextRange, pub message: String, pub suggestion: Option, } impl Diagnostic { /// Construct a diagnostic. pub fn new>(at: TextRange, message: S) -> Self { Self { at, message: message.as_ref().into(), suggestion: None, } } /// Construct a diagnostic with a fix. pub fn suggest>(at: TextRange, message: S, suggestion: Suggestion) -> Self { Self { at, message: message.as_ref().into(), suggestion: Some(suggestion), } } /// Apply a diagnostic to a source file pub fn apply(&self, src: &mut String) { if let Some(s) = &self.suggestion { s.apply(src); } } } #[cfg(feature = "json-out")] impl Serialize for Diagnostic { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut s = serializer.serialize_struct("Diagnostic", 3)?; let at = { let start = usize::from(self.at.start()); let end = usize::from(self.at.end()); (start, end) }; s.serialize_field("at", &at)?; s.serialize_field("message", &self.message)?; if let Some(suggestion) = &self.suggestion { s.serialize_field("suggestion", suggestion)?; } s.end() } } /// 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, pub fix: SyntaxElement, } impl Suggestion { /// Construct a suggestion. pub fn new>(at: TextRange, fix: E) -> Self { Self { at, fix: fix.into(), } } /// Apply a suggestion to a source file pub fn apply(&self, src: &mut String) { let start = usize::from(self.at.start()); let end = usize::from(self.at.end()); src.replace_range(start..end, &self.fix.to_string()) } } #[cfg(feature = "json-out")] impl Serialize for Suggestion { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut s = serializer.serialize_struct("Suggestion", 2)?; let at = { let start = usize::from(self.at.start()); let end = usize::from(self.at.end()); (start, end) }; let fix = self.fix.to_string(); s.serialize_field("at", &at)?; s.serialize_field("fix", &fix)?; s.end() } } /// 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) -> Vec; } /// 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),*,) => { lint_map!($($s),*); }; ($($s:ident),*) => { use ::std::collections::HashMap; use ::rnix::SyntaxKind; $( mod $s; )* ::lazy_static::lazy_static! { pub static ref LINTS: HashMap>> = { let mut map = HashMap::new(); $( { let temp_lint = &*$s::LINT; let temp_matches = temp_lint.match_kind(); for temp_match in temp_matches { map.entry(temp_match) .and_modify(|v: &mut Vec<_>| v.push(temp_lint)) .or_insert_with(|| vec![temp_lint]); } } )* map }; } } }