diff options
Diffstat (limited to 'lib/src/lints/useless_parens.rs')
-rw-r--r-- | lib/src/lints/useless_parens.rs | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/lib/src/lints/useless_parens.rs b/lib/src/lints/useless_parens.rs new file mode 100644 index 0000000..d93f06c --- /dev/null +++ b/lib/src/lints/useless_parens.rs | |||
@@ -0,0 +1,99 @@ | |||
1 | use crate::{Lint, Metadata, Report, Rule, Suggestion, Diagnostic}; | ||
2 | |||
3 | use if_chain::if_chain; | ||
4 | use macros::lint; | ||
5 | use rnix::{ | ||
6 | types::{ParsedType, KeyValue, Paren, TypedNode, Wrapper}, | ||
7 | NodeOrToken, SyntaxElement, SyntaxKind, | ||
8 | }; | ||
9 | |||
10 | #[lint( | ||
11 | name = "useless parens", | ||
12 | note = "These parentheses can be omitted", | ||
13 | code = 8, | ||
14 | match_with = [ | ||
15 | SyntaxKind::NODE_KEY_VALUE, | ||
16 | SyntaxKind::NODE_PAREN, | ||
17 | SyntaxKind::NODE_LET_IN, | ||
18 | ] | ||
19 | )] | ||
20 | struct UselessParens; | ||
21 | |||
22 | impl Rule for UselessParens { | ||
23 | fn validate(&self, node: &SyntaxElement) -> Option<Report> { | ||
24 | if_chain! { | ||
25 | if let NodeOrToken::Node(node) = node; | ||
26 | if let Some(parsed_type_node) = ParsedType::cast(node.clone()); | ||
27 | |||
28 | if let Some(diagnostic) = do_thing(parsed_type_node); | ||
29 | then { | ||
30 | let mut report = Self::report(); | ||
31 | report.diagnostics.push(diagnostic); | ||
32 | Some(report) | ||
33 | } else { | ||
34 | None | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | |||
40 | fn do_thing(parsed_type_node: ParsedType) -> Option<Diagnostic> { | ||
41 | match parsed_type_node { | ||
42 | ParsedType::KeyValue(kv) => if_chain! { | ||
43 | if let Some(value_node) = kv.value(); | ||
44 | let value_range = value_node.text_range(); | ||
45 | if let Some(value_in_parens) = Paren::cast(value_node); | ||
46 | if let Some(inner) = value_in_parens.inner(); | ||
47 | then { | ||
48 | let at = value_range; | ||
49 | let message = "Useless parentheses around value in `let` binding"; | ||
50 | let replacement = inner; | ||
51 | Some(Diagnostic::suggest(at, message, Suggestion::new(at, replacement))) | ||
52 | } else { | ||
53 | None | ||
54 | } | ||
55 | }, | ||
56 | ParsedType::LetIn(let_in) => if_chain! { | ||
57 | if let Some(body_node) = let_in.body(); | ||
58 | let body_range = body_node.text_range(); | ||
59 | if let Some(body_as_parens) = Paren::cast(body_node); | ||
60 | if let Some(inner) = body_as_parens.inner(); | ||
61 | then { | ||
62 | let at = body_range; | ||
63 | let message = "Useless parentheses around body of `let` expression"; | ||
64 | let replacement = inner; | ||
65 | Some(Diagnostic::suggest(at, message, Suggestion::new(at, replacement))) | ||
66 | } else { | ||
67 | None | ||
68 | } | ||
69 | }, | ||
70 | ParsedType::Paren(paren_expr) => if_chain! { | ||
71 | let paren_expr_range = paren_expr.node().text_range(); | ||
72 | if let Some(father_node) = paren_expr.node().parent(); | ||
73 | |||
74 | // ensure that we don't lint inside let-in statements | ||
75 | // we already lint such cases in previous match stmt | ||
76 | if KeyValue::cast(father_node).is_none(); | ||
77 | |||
78 | if let Some(inner_node) = paren_expr.inner(); | ||
79 | if let Some(parsed_inner) = ParsedType::cast(inner_node); | ||
80 | if matches!( | ||
81 | parsed_inner, | ||
82 | ParsedType::List(_) | ||
83 | | ParsedType::Paren(_) | ||
84 | | ParsedType::Str(_) | ||
85 | | ParsedType::AttrSet(_) | ||
86 | | ParsedType::Select(_) | ||
87 | ); | ||
88 | then { | ||
89 | let at = paren_expr_range; | ||
90 | let message = "Useless parentheses around primitive expression"; | ||
91 | let replacement = parsed_inner.node().clone(); | ||
92 | Some(Diagnostic::suggest(at, message, Suggestion::new(at, replacement))) | ||
93 | } else { | ||
94 | None | ||
95 | } | ||
96 | }, | ||
97 | _ => None | ||
98 | } | ||
99 | } | ||