From 4152367f5dee2a70ff49f4aff4040d5d433b8e44 Mon Sep 17 00:00:00 2001 From: Akshay Date: Mon, 13 Sep 2021 22:20:25 +0530 Subject: add demo lint: bool_comparison --- lib/src/lib.rs | 50 +++++++++++++++++++++++--- lib/src/lints.rs | 2 ++ lib/src/lints/bool_comparison.rs | 78 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 lib/src/lints.rs create mode 100644 lib/src/lints/bool_comparison.rs (limited to 'lib/src') diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 31e1bb2..537f4b3 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,7 +1,47 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); +pub mod lints; + +use rnix::{SyntaxElement, SyntaxKind, TextRange}; +use std::ops::Deref; + +pub trait Rule { + fn validate(&self, node: &SyntaxElement) -> Option; +} + +#[derive(Debug)] +pub struct Diagnostic { + pub at: TextRange, + pub message: String, +} + +impl Diagnostic { + pub fn new(at: TextRange, message: String) -> Self { + Self { at, message } } } + +pub trait Metadata { + fn name(&self) -> &str; + fn note(&self) -> &str; + fn match_with(&self, with: &SyntaxKind) -> bool; +} + +pub trait Lint: Metadata + Rule + Send + Sync {} + +// #[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 RULES: HashMap> = { +// vec![$(&*$s::LINT,)*] +// } +// } +// } +// } diff --git a/lib/src/lints.rs b/lib/src/lints.rs new file mode 100644 index 0000000..b0df71b --- /dev/null +++ b/lib/src/lints.rs @@ -0,0 +1,2 @@ +pub mod bool_comparison; +pub mod with_list; diff --git a/lib/src/lints/bool_comparison.rs b/lib/src/lints/bool_comparison.rs new file mode 100644 index 0000000..4476b31 --- /dev/null +++ b/lib/src/lints/bool_comparison.rs @@ -0,0 +1,78 @@ +use crate::{Diagnostic, Lint, Metadata, Rule}; + +use if_chain::if_chain; +use macros::lint; +use rnix::{ + types::{BinOp, BinOpKind, Ident, TokenWrapper, TypedNode}, + NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, +}; + +#[lint( + name = "bool_comparison", + note = "Unnecessary comparison with boolean", + match_with = "SyntaxKind::NODE_BIN_OP" +)] +struct BoolComparison; + +impl Rule for BoolComparison { + fn validate(&self, node: &SyntaxElement) -> Option { + if_chain! { + if let NodeOrToken::Node(bin_op_node) = node; + if let Some(bin_expr) = BinOp::cast(bin_op_node.clone()); + if let Some(lhs) = bin_expr.lhs(); + if let Some(rhs) = bin_expr.rhs(); + + if let BinOpKind::Equal | BinOpKind::NotEqual = bin_expr.operator(); + let (non_bool_side, bool_side) = if is_boolean_ident(&lhs) { + (rhs, lhs) + } else if is_boolean_ident(&rhs) { + (lhs, rhs) + } else { + return None + }; + then { + let at = node.text_range(); + let message = format!( + "Comparing `{}` with boolean literal `{}`", + non_bool_side, + bool_side + ); + dbg!(Some(Diagnostic::new(at, message))) + } else { + None + } + } + } +} + +// not entirely accurate, underhanded nix programmers might write `true = false` +fn is_boolean_ident(node: &SyntaxNode) -> bool { + if let Some(ident_expr) = Ident::cast(node.clone()) { + ident_expr.as_str() == "true" || ident_expr.as_str() == "false" + } else { + false + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use rnix::{parser, WalkEvent}; +// +// #[test] +// fn trivial() { +// let src = r#" +// a == true +// "#; +// let parsed = rnix::parse(src).as_result().ok().unwrap(); +// let _ = parsed +// .node() +// .preorder_with_tokens() +// .filter_map(|event| match event { +// WalkEvent::Enter(t) => Some(t), +// _ => None, +// }) +// .map(|node| BoolComparison.validate(&node)) +// .collect::>(); +// } +// } -- cgit v1.2.3