aboutsummaryrefslogtreecommitdiff
path: root/lib/src/lints
diff options
context:
space:
mode:
Diffstat (limited to 'lib/src/lints')
-rw-r--r--lib/src/lints/useless_has_attr.rs83
1 files changed, 83 insertions, 0 deletions
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}