aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/src/lints.rs1
-rw-r--r--lib/src/lints/useless_has_attr.rs83
-rw-r--r--lib/src/make.rs4
3 files changed, 88 insertions, 0 deletions
diff --git a/lib/src/lints.rs b/lib/src/lints.rs
index 582cabe..7d73277 100644
--- a/lib/src/lints.rs
+++ b/lib/src/lints.rs
@@ -19,4 +19,5 @@ lints! {
19 faster_zipattrswith, 19 faster_zipattrswith,
20 deprecated_to_path, 20 deprecated_to_path,
21 bool_simplification, 21 bool_simplification,
22 useless_has_attr,
22} 23}
diff --git a/lib/src/lints/useless_has_attr.rs b/lib/src/lints/useless_has_attr.rs
new file mode 100644
index 0000000..aae560a
--- /dev/null
+++ b/lib/src/lints/useless_has_attr.rs
@@ -0,0 +1,83 @@
1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2
3use if_chain::if_chain;
4use macros::lint;
5use rnix::{
6 types::{BinOp, BinOpKind, IfElse, Select, TypedNode},
7 NodeOrToken, SyntaxElement, SyntaxKind,
8};
9
10/// ## What it does
11/// Checks for expressions that use the "has attribute" operator: `?`,
12/// where the `or` operator would suffice.
13///
14/// ## Why is this bad?
15/// The `or` operator is more readable.
16///
17/// ## Example
18/// ```nix
19/// if x ? a then x.a else some_default
20/// ```
21///
22/// Use `or` instead:
23///
24/// ```nix
25/// x.a or some_default
26/// ```
27#[lint(
28 name = "useless_has_attr",
29 note = "This `if` expression can be simplified with `or`",
30 code = 19,
31 match_with = SyntaxKind::NODE_IF_ELSE
32)]
33struct UselessHasAttr;
34
35impl Rule for UselessHasAttr {
36 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
37 if_chain! {
38 if let NodeOrToken::Node(node) = node;
39 if let Some(if_else_expr) = IfElse::cast(node.clone());
40 if let Some(condition_expr) = if_else_expr.condition();
41 if let Some(default_expr) = if_else_expr.else_body();
42 if let Some(cond_bin_expr) = BinOp::cast(condition_expr.clone());
43 if let Some(BinOpKind::IsSet) = cond_bin_expr.operator();
44
45 // set ? attr_path
46 // ^^^--------------- lhs
47 // ^^^^^^^^^^--- rhs
48 if let Some(set) = cond_bin_expr.lhs();
49 if let Some(attr_path) = cond_bin_expr.rhs();
50
51 // check if body of the `if` expression is of the form `set.attr_path`
52 if let Some(body_expr) = if_else_expr.body();
53 if let Some(body_select_expr) = Select::cast(body_expr.clone());
54 let expected_body = make::select(&set, &attr_path);
55
56 // text comparison will do for now
57 if body_select_expr.node().text() == expected_body.node().text();
58 then {
59 let at = node.text_range();
60 // `or` is tightly binding, we need to parenthesize non-literal exprs
61 let default_with_parens = match default_expr.kind() {
62 SyntaxKind::NODE_LIST
63 | SyntaxKind::NODE_PAREN
64 | SyntaxKind::NODE_STRING
65 | SyntaxKind::NODE_ATTR_SET
66 | SyntaxKind::NODE_IDENT => default_expr,
67 _ => make::parenthesize(&default_expr).node().clone(),
68 };
69 let replacement = make::or_default(&set, &attr_path, &default_with_parens).node().clone();
70 let message = format!(
71 "Consider using `{}` instead of this `if` expression",
72 replacement
73 );
74 Some(
75 self.report()
76 .suggest(at, message, Suggestion::new(at, replacement)),
77 )
78 } else {
79 None
80 }
81 }
82 }
83}
diff --git a/lib/src/make.rs b/lib/src/make.rs
index c36fa7e..2c33ac8 100644
--- a/lib/src/make.rs
+++ b/lib/src/make.rs
@@ -88,3 +88,7 @@ pub fn empty() -> types::Root {
88pub fn binary(lhs: &SyntaxNode, op: &str, rhs: &SyntaxNode) -> types::BinOp { 88pub fn binary(lhs: &SyntaxNode, op: &str, rhs: &SyntaxNode) -> types::BinOp {
89 ast_from_text(&format!("{} {} {}", lhs, op, rhs)) 89 ast_from_text(&format!("{} {} {}", lhs, op, rhs))
90} 90}
91
92pub fn or_default(set: &SyntaxNode, index: &SyntaxNode, default: &SyntaxNode) -> types::OrDefault {
93 ast_from_text(&format!("{}.{} or {}", set, index, default))
94}