···77# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8899[dependencies]
1010-if_chain.workspace = true
1110lazy_static.workspace = true
1211macros.workspace = true
1312rnix.workspace = true
+43-46
lib/src/lints/bool_comparison.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
···36353736impl Rule for BoolComparison {
3837 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
3939- if_chain! {
4040- if let NodeOrToken::Node(node) = node;
4141- if let Some(bin_expr) = BinOp::cast(node.clone());
4242- if let Some(lhs) = bin_expr.lhs();
4343- if let Some(rhs) = bin_expr.rhs();
4444- if let Some(op) = bin_expr.operator();
4545-4646- if let BinOpKind::Equal | BinOpKind::NotEqual = op;
3838+ if let NodeOrToken::Node(node) = node
3939+ && let Some(bin_expr) = BinOp::cast(node.clone())
4040+ && let Some(lhs) = bin_expr.lhs()
4141+ && let Some(rhs) = bin_expr.rhs()
4242+ && let Some(op) = bin_expr.operator()
4343+ && let BinOpKind::Equal | BinOpKind::NotEqual = op
4444+ {
4745 let (non_bool_side, bool_side) = if boolean_ident(&lhs).is_some() {
4846 (rhs, lhs)
4947 } else if boolean_ident(&rhs).is_some() {
5048 (lhs, rhs)
5149 } else {
5252- return None
5050+ return None;
5351 };
5454- then {
5555- let at = node.text_range();
5656- let replacement = {
5757- match (boolean_ident(&bool_side).unwrap(), op == BinOpKind::Equal) {
5858- (NixBoolean::True, true) | (NixBoolean::False, false) => {
5959- // `a == true`, `a != false` replace with just `a`
6060- non_bool_side.clone()
6161- },
6262- (NixBoolean::True, false) | (NixBoolean::False, true) => {
6363- // `a != true`, `a == false` replace with `!a`
6464- match non_bool_side.kind() {
6565- SyntaxKind::NODE_APPLY
6666- | SyntaxKind::NODE_PAREN
6767- | SyntaxKind::NODE_IDENT => {
6868- // do not parenthsize the replacement
5252+ let at = node.text_range();
5353+ let replacement = {
5454+ match (boolean_ident(&bool_side).unwrap(), op == BinOpKind::Equal) {
5555+ (NixBoolean::True, true) | (NixBoolean::False, false) => {
5656+ // `a == true`, `a != false` replace with just `a`
5757+ non_bool_side.clone()
5858+ }
5959+ (NixBoolean::True, false) | (NixBoolean::False, true) => {
6060+ // `a != true`, `a == false` replace with `!a`
6161+ match non_bool_side.kind() {
6262+ SyntaxKind::NODE_APPLY
6363+ | SyntaxKind::NODE_PAREN
6464+ | SyntaxKind::NODE_IDENT => {
6565+ // do not parenthsize the replacement
6666+ make::unary_not(&non_bool_side).node().clone()
6767+ }
6868+ SyntaxKind::NODE_BIN_OP => {
6969+ let inner = BinOp::cast(non_bool_side.clone()).unwrap();
7070+ // `!a ? b`, no paren required
7171+ if inner.operator()? == BinOpKind::IsSet {
6972 make::unary_not(&non_bool_side).node().clone()
7070- },
7171- SyntaxKind::NODE_BIN_OP => {
7272- let inner = BinOp::cast(non_bool_side.clone()).unwrap();
7373- // `!a ? b`, no paren required
7474- if inner.operator()? == BinOpKind::IsSet {
7575- make::unary_not(&non_bool_side).node().clone()
7676- } else {
7777- let parens = make::parenthesize(&non_bool_side);
7878- make::unary_not(parens.node()).node().clone()
7979- }
8080- },
8181- _ => {
7373+ } else {
8274 let parens = make::parenthesize(&non_bool_side);
8375 make::unary_not(parens.node()).node().clone()
8476 }
8577 }
8686- },
7878+ _ => {
7979+ let parens = make::parenthesize(&non_bool_side);
8080+ make::unary_not(parens.node()).node().clone()
8181+ }
8282+ }
8783 }
8888- };
8989- let message = format!(
9090- "Comparing `{non_bool_side}` with boolean literal `{bool_side}`"
9191- );
9292- Some(self.report().suggest(at, message, Suggestion::new(at, replacement)))
9393- } else {
9494- None
9595- }
8484+ }
8585+ };
8686+ let message = format!("Comparing `{non_bool_side}` with boolean literal `{bool_side}`");
8787+ Some(
8888+ self.report()
8989+ .suggest(at, message, Suggestion::new(at, replacement)),
9090+ )
9191+ } else {
9292+ None
9693 }
9794 }
9895}
+20-23
lib/src/lints/bool_simplification.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···33323433impl Rule for BoolSimplification {
3534 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
3636- if_chain! {
3737- if let NodeOrToken::Node(node) = node;
3838- if let Some(unary_expr) = UnaryOp::cast(node.clone());
3939- if unary_expr.operator() == UnaryOpKind::Invert;
4040- if let Some(value_expr) = unary_expr.value();
4141- if let Some(paren_expr) = Paren::cast(value_expr);
4242- if let Some(inner_expr) = paren_expr.inner();
4343- if let Some(bin_expr) = BinOp::cast(inner_expr);
4444- if let Some(BinOpKind::Equal) = bin_expr.operator();
4545- then {
4646- let at = node.text_range();
4747- let message = "Try `!=` instead of `!(... == ...)`";
3535+ if let NodeOrToken::Node(node) = node
3636+ && let Some(unary_expr) = UnaryOp::cast(node.clone())
3737+ && unary_expr.operator() == UnaryOpKind::Invert
3838+ && let Some(value_expr) = unary_expr.value()
3939+ && let Some(paren_expr) = Paren::cast(value_expr)
4040+ && let Some(inner_expr) = paren_expr.inner()
4141+ && let Some(bin_expr) = BinOp::cast(inner_expr)
4242+ && let Some(BinOpKind::Equal) = bin_expr.operator()
4343+ {
4444+ let at = node.text_range();
4545+ let message = "Try `!=` instead of `!(... == ...)`";
48464949- let lhs = bin_expr.lhs()?;
5050- let rhs = bin_expr.rhs()?;
5151- let replacement = make::binary(&lhs, "!=", &rhs).node().clone();
5252- Some(
5353- self.report()
5454- .suggest(at, message, Suggestion::new(at, replacement)),
5555- )
5656- } else {
5757- None
5858- }
4747+ let lhs = bin_expr.lhs()?;
4848+ let rhs = bin_expr.rhs()?;
4949+ let replacement = make::binary(&lhs, "!=", &rhs).node().clone();
5050+ Some(
5151+ self.report()
5252+ .suggest(at, message, Suggestion::new(at, replacement)),
5353+ )
5454+ } else {
5555+ None
5956 }
6057 }
6158}
+34-34
lib/src/lints/collapsible_let_in.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind, TextRange,
···46454746impl Rule for CollapsibleLetIn {
4847 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4949- if_chain! {
5050- if let NodeOrToken::Node(node) = node;
5151- if let Some(let_in_expr) = LetIn::cast(node.clone());
5252- if let Some(body) = let_in_expr.body();
4848+ if let NodeOrToken::Node(node) = node
4949+ && let Some(let_in_expr) = LetIn::cast(node.clone())
5050+ && let Some(body) = let_in_expr.body()
5151+ && LetIn::cast(body.clone()).is_some()
5252+ {
5353+ let first_annotation = node.text_range();
5454+ let first_message = "This `let in` expression contains a nested `let in` expression";
53555454- if LetIn::cast(body.clone()).is_some();
5555- then {
5656- let first_annotation = node.text_range();
5757- let first_message = "This `let in` expression contains a nested `let in` expression";
5656+ let second_annotation = body.text_range();
5757+ let second_message = "This `let in` expression is nested";
58585959- let second_annotation = body.text_range();
6060- let second_message = "This `let in` expression is nested";
5959+ let replacement_at = {
6060+ let start = body
6161+ .siblings_with_tokens(Direction::Prev)
6262+ .find(|elem| elem.kind() == SyntaxKind::TOKEN_IN)?
6363+ .text_range()
6464+ .start();
6565+ let end = body
6666+ .descendants_with_tokens()
6767+ .find(|elem| elem.kind() == SyntaxKind::TOKEN_LET)?
6868+ .text_range()
6969+ .end();
7070+ TextRange::new(start, end)
7171+ };
7272+ let replacement = make::empty().node().clone();
61736262- let replacement_at = {
6363- let start = body
6464- .siblings_with_tokens(Direction::Prev)
6565- .find(|elem| elem.kind() == SyntaxKind::TOKEN_IN)?
6666- .text_range()
6767- .start();
6868- let end = body
6969- .descendants_with_tokens()
7070- .find(|elem| elem.kind() == SyntaxKind::TOKEN_LET)?
7171- .text_range()
7272- .end();
7373- TextRange::new(start, end)
7474- };
7575- let replacement = make::empty().node().clone();
7676-7777- Some(
7878- self.report()
7979- .diagnostic(first_annotation, first_message)
8080- .suggest(second_annotation, second_message, Suggestion::new(replacement_at, replacement))
8181- )
8282- } else {
8383- None
8484- }
7474+ Some(
7575+ self.report()
7676+ .diagnostic(first_annotation, first_message)
7777+ .suggest(
7878+ second_annotation,
7979+ second_message,
8080+ Suggestion::new(replacement_at, replacement),
8181+ ),
8282+ )
8383+ } else {
8484+ None
8585 }
8686 }
8787}
+12-13
lib/src/lints/deprecated_to_path.rs
···11use crate::{Metadata, Report, Rule, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···42414342impl Rule for DeprecatedIsNull {
4443 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4545- if_chain! {
4646- if let NodeOrToken::Node(node) = node;
4747- if let Some(apply) = Apply::cast(node.clone());
4848- let lambda_path = apply.lambda()?.to_string();
4949- if ALLOWED_PATHS.contains(&lambda_path.as_str());
5050- then {
5151- let at = node.text_range();
5252- let message = format!("`{lambda_path}` is deprecated, see `:doc builtins.toPath` within the REPL for more");
5353- Some(self.report().diagnostic(at, message))
5454- } else {
5555- None
5656- }
4444+ if let NodeOrToken::Node(node) = node
4545+ && let Some(apply) = Apply::cast(node.clone())
4646+ && let lambda_path = apply.lambda()?.to_string()
4747+ && ALLOWED_PATHS.contains(&lambda_path.as_str())
4848+ {
4949+ let at = node.text_range();
5050+ let message = format!(
5151+ "`{lambda_path}` is deprecated, see `:doc builtins.toPath` within the REPL for more"
5252+ );
5353+ Some(self.report().diagnostic(at, message))
5454+ } else {
5555+ None
5756 }
5857 }
5958}
+15-19
lib/src/lints/empty_inherit.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo, utils};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···30293130impl Rule for EmptyInherit {
3231 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
3333- if_chain! {
3434- if let NodeOrToken::Node(node) = node;
3535- if let Some(inherit_stmt) = Inherit::cast(node.clone());
3636- if inherit_stmt.from().is_none();
3737- if inherit_stmt.idents().count() == 0;
3838- then {
3939- let at = node.text_range();
4040- let replacement = make::empty().node().clone();
4141- let replacement_at = utils::with_preceeding_whitespace(node);
4242- let message = "Remove this empty `inherit` statement";
4343- Some(
4444- self
4545- .report()
4646- .suggest(at, message, Suggestion::new(replacement_at, replacement))
4747- )
4848- } else {
4949- None
5050- }
3232+ if let NodeOrToken::Node(node) = node
3333+ && let Some(inherit_stmt) = Inherit::cast(node.clone())
3434+ && inherit_stmt.from().is_none()
3535+ && inherit_stmt.idents().count() == 0
3636+ {
3737+ let at = node.text_range();
3838+ let replacement = make::empty().node().clone();
3939+ let replacement_at = utils::with_preceeding_whitespace(node);
4040+ let message = "Remove this empty `inherit` statement";
4141+ Some(
4242+ self.report()
4343+ .suggest(at, message, Suggestion::new(replacement_at, replacement)),
4444+ )
4545+ } else {
4646+ None
5147 }
5248 }
5349}
+19-23
lib/src/lints/empty_let_in.rs
···11use crate::{Metadata, Report, Rule, Suggestion, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···35343635impl Rule for EmptyLetIn {
3736 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
3838- if_chain! {
3939- if let NodeOrToken::Node(node) = node;
4040- if let Some(let_in_expr) = LetIn::cast(node.clone());
4141- let entries = let_in_expr.entries();
4242- let inherits = let_in_expr.inherits();
4343-4444- if entries.count() == 0;
4545- if inherits.count() == 0;
4646-4747- if let Some(body) = let_in_expr.body();
4848-3737+ if let NodeOrToken::Node(node) = node
3838+ && let Some(let_in_expr) = LetIn::cast(node.clone())
3939+ && let entries = let_in_expr.entries()
4040+ && let inherits = let_in_expr.inherits()
4141+ && entries.count() == 0
4242+ && inherits.count() == 0
4343+ && let Some(body) = let_in_expr.body()
4444+ {
4945 // ensure that the let-in-expr does not have comments
5046 let has_comments = node
5147 .children_with_tokens()
5248 .any(|el| el.kind() == SyntaxKind::TOKEN_COMMENT);
5353- then {
5454- let at = node.text_range();
5555- let replacement = body;
5656- let message = "This let-in expression has no entries";
5757- Some(if has_comments {
5858- self.report().diagnostic(at, message)
5959- } else {
6060- self.report().suggest(at, message, Suggestion::new(at, replacement))
6161- })
4949+5050+ let at = node.text_range();
5151+ let replacement = body;
5252+ let message = "This let-in expression has no entries";
5353+ Some(if has_comments {
5454+ self.report().diagnostic(at, message)
6255 } else {
6363- None
6464- }
5656+ self.report()
5757+ .suggest(at, message, Suggestion::new(at, replacement))
5858+ })
5959+ } else {
6060+ None
6561 }
6662 }
6763}
+15-18
lib/src/lints/empty_list_concat.rs
···11use crate::{Metadata, Report, Rule, Suggestion, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
···33323433impl Rule for EmptyListConcat {
3534 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
3636- if_chain! {
3737- if let NodeOrToken::Node(node) = node;
3838- if let Some(bin_expr) = BinOp::cast(node.clone());
3939- if let Some(lhs) = bin_expr.lhs();
4040- if let Some(rhs) = bin_expr.rhs();
4141- if let Some(op) = bin_expr.operator();
4242- if let BinOpKind::Concat = op;
4343- then {
4444- let at = node.text_range();
4545- let message = "Concatenation with the empty list, `[]`, is a no-op";
4646- if is_empty_array(&lhs) {
4747- Some(self.report().suggest(at, message, Suggestion::new(at, rhs)))
4848- } else if is_empty_array(&rhs) {
4949- Some(self.report().suggest(at, message, Suggestion::new(at, lhs)))
5050- } else {
5151- None
5252- }
3535+ if let NodeOrToken::Node(node) = node
3636+ && let Some(bin_expr) = BinOp::cast(node.clone())
3737+ && let Some(lhs) = bin_expr.lhs()
3838+ && let Some(rhs) = bin_expr.rhs()
3939+ && let Some(op) = bin_expr.operator()
4040+ && let BinOpKind::Concat = op
4141+ {
4242+ let at = node.text_range();
4343+ let message = "Concatenation with the empty list, `[]`, is a no-op";
4444+ if is_empty_array(&lhs) {
4545+ Some(self.report().suggest(at, message, Suggestion::new(at, rhs)))
4646+ } else if is_empty_array(&rhs) {
4747+ Some(self.report().suggest(at, message, Suggestion::new(at, lhs)))
5348 } else {
5449 None
5550 }
5151+ } else {
5252+ None
5653 }
5754 }
5855}
+25-31
lib/src/lints/empty_pattern.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
···42414342impl Rule for EmptyPattern {
4443 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4545- if_chain! {
4646- if let NodeOrToken::Node(node) = node;
4747- if let Some(lambda_expr) = Lambda::cast(node.clone());
4848- if let Some(arg) = lambda_expr.arg();
4949- if let Some(body) = lambda_expr.body();
5050-5151- if let Some(pattern) = Pattern::cast(arg);
5252-4444+ if let NodeOrToken::Node(node) = node
4545+ && let Some(lambda_expr) = Lambda::cast(node.clone())
4646+ && let Some(arg) = lambda_expr.arg()
4747+ && let Some(body) = lambda_expr.body()
4848+ && let Some(pattern) = Pattern::cast(arg)
5349 // no patterns within `{ }`
5454- if pattern.entries().count() == 0;
5050+ && pattern.entries().count() == 0
5551 // pattern is not bound
5656- if pattern.at().is_none();
5757-5252+ && pattern.at().is_none()
5853 // not a nixos module
5959- if !is_module(&body);
6060-6161- then {
6262- let at = pattern.node().text_range();
6363- let message = "This pattern is empty, use `_` instead";
6464- let replacement = make::ident("_").node().clone();
6565- Some(self.report().suggest(at, message, Suggestion::new(at, replacement)))
6666- } else {
6767- None
6868- }
5454+ && !is_module(&body)
5555+ {
5656+ let at = pattern.node().text_range();
5757+ let message = "This pattern is empty, use `_` instead";
5858+ let replacement = make::ident("_").node().clone();
5959+ Some(
6060+ self.report()
6161+ .suggest(at, message, Suggestion::new(at, replacement)),
6262+ )
6363+ } else {
6464+ None
6965 }
7066 }
7167}
72687369fn is_module(body: &SyntaxNode) -> bool {
7474- if_chain! {
7575- if let Some(attr_set) = AttrSet::cast(body.clone());
7676- if attr_set
7070+ if let Some(attr_set) = AttrSet::cast(body.clone())
7171+ && attr_set
7772 .entries()
7873 .filter_map(|e| e.key())
7979- .any(|k| k.node().to_string() == "imports");
8080- then {
8181- true
8282- } else {
8383- false
8484- }
7474+ .any(|k| k.node().to_string() == "imports")
7575+ {
7676+ true
7777+ } else {
7878+ false
8579 }
8680}
+24-34
lib/src/lints/eta_reduction.rs
···11use crate::{Metadata, Report, Rule, Suggestion, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
···43424443impl Rule for EtaReduction {
4544 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4646- if_chain! {
4747- if let NodeOrToken::Node(node) = node;
4848- if let Some(lambda_expr) = Lambda::cast(node.clone());
4949-5050- if let Some(arg_node) = lambda_expr.arg();
5151- if let Some(arg) = Ident::cast(arg_node);
5252-5353- if let Some(body_node) = lambda_expr.body();
5454- if let Some(body) = Apply::cast(body_node);
5555-5656- if let Some(value_node) = body.value();
5757- if let Some(value) = Ident::cast(value_node);
5858-5959- if arg.as_str() == value.as_str();
6060-6161- if let Some(lambda_node) = body.lambda();
6262- if !mentions_ident(&arg, &lambda_node);
6363- // lambda body should be no more than a single Ident to
6464- // retain code readability
6565- if let Some(_) = Ident::cast(lambda_node);
6666-6767- then {
6868- let at = node.text_range();
6969- let replacement = body.lambda()?;
7070- let message =
7171- format!(
7272- "Found eta-reduction: `{}`",
7373- replacement.text()
7474- );
7575- Some(self.report().suggest(at, message, Suggestion::new(at, replacement)))
7676- } else {
7777- None
7878- }
4545+ if let NodeOrToken::Node(node) = node
4646+ && let Some(lambda_expr) = Lambda::cast(node.clone())
4747+ && let Some(arg_node) = lambda_expr.arg()
4848+ && let Some(arg) = Ident::cast(arg_node)
4949+ && let Some(body_node) = lambda_expr.body()
5050+ && let Some(body) = Apply::cast(body_node)
5151+ && let Some(value_node) = body.value()
5252+ && let Some(value) = Ident::cast(value_node)
5353+ && arg.as_str() == value.as_str()
5454+ && let Some(lambda_node) = body.lambda()
5555+ && !mentions_ident(&arg, &lambda_node)
5656+ // lambda body should be no more than a single Ident to
5757+ // retain code readability
5858+ && let Some(_) = Ident::cast(lambda_node)
5959+ {
6060+ let at = node.text_range();
6161+ let replacement = body.lambda()?;
6262+ let message = format!("Found eta-reduction: `{}`", replacement.text());
6363+ Some(
6464+ self.report()
6565+ .suggest(at, message, Suggestion::new(at, replacement)),
6666+ )
6767+ } else {
6868+ None
7969 }
8070 }
8171}
+19-21
lib/src/lints/legacy_let_syntax.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···45444645impl Rule for ManualInherit {
4746 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4848- if_chain! {
4949- if let NodeOrToken::Node(node) = node;
5050- if let Some(legacy_let) = LegacyLet::cast(node.clone());
5151-5252- if legacy_let
4747+ if let NodeOrToken::Node(node) = node
4848+ && let Some(legacy_let) = LegacyLet::cast(node.clone())
4949+ && legacy_let
5350 .entries()
5454- .any(|kv| matches!(kv.key(), Some(k) if key_is_ident(&k, "body")));
5151+ .any(|kv| matches!(kv.key(), Some(k) if key_is_ident(&k, "body")))
5252+ {
5353+ let inherits = legacy_let.inherits();
5454+ let entries = legacy_let.entries();
5555+ let attrset = make::attrset(inherits, entries, true);
5656+ let parenthesized = make::parenthesize(attrset.node());
5757+ let selected = make::select(parenthesized.node(), make::ident("body").node());
55585656- then {
5757- let inherits = legacy_let.inherits();
5858- let entries = legacy_let.entries();
5959- let attrset = make::attrset(inherits, entries, true);
6060- let parenthesized = make::parenthesize(attrset.node());
6161- let selected = make::select(parenthesized.node(), make::ident("body").node());
5959+ let at = node.text_range();
6060+ let message = "Prefer `rec` over undocumented `let` syntax";
6161+ let replacement = selected.node().clone();
62626363- let at = node.text_range();
6464- let message = "Prefer `rec` over undocumented `let` syntax";
6565- let replacement = selected.node().clone();
6666-6767- Some(self.report().suggest(at, message, Suggestion::new(at, replacement)))
6868- } else {
6969- None
7070- }
6363+ Some(
6464+ self.report()
6565+ .suggest(at, message, Suggestion::new(at, replacement)),
6666+ )
6767+ } else {
6868+ None
7169 }
7270 }
7371}
+19-20
lib/src/lints/manual_inherit.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···41404241impl Rule for ManualInherit {
4342 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4444- if_chain! {
4545- if let NodeOrToken::Node(node) = node;
4646- if let Some(key_value_stmt) = KeyValue::cast(node.clone());
4747- if let mut key_path = key_value_stmt.key()?.path();
4848- if let Some(key_node) = key_path.next();
4343+ if let NodeOrToken::Node(node) = node
4444+ && let Some(key_value_stmt) = KeyValue::cast(node.clone())
4545+ && let mut key_path = key_value_stmt.key()?.path()
4646+ && let Some(key_node) = key_path.next()
4947 // ensure that path has exactly one component
5050- if key_path.next().is_none();
5151- if let Some(key) = Ident::cast(key_node);
4848+ && key_path.next().is_none()
4949+ && let Some(key) = Ident::cast(key_node)
52505353- if let Some(value_node) = key_value_stmt.value();
5454- if let Some(value) = Ident::cast(value_node);
5555-5656- if key.as_str() == value.as_str();
5151+ && let Some(value_node) = key_value_stmt.value()
5252+ && let Some(value) = Ident::cast(value_node)
57535858- then {
5959- let at = node.text_range();
6060- let replacement = make::inherit_stmt(&[key]).node().clone();
6161- let message = "This assignment is better written with `inherit`";
6262- Some(self.report().suggest(at, message, Suggestion::new(at, replacement)))
6363- } else {
6464- None
6565- }
5454+ && key.as_str() == value.as_str()
5555+ {
5656+ let at = node.text_range();
5757+ let replacement = make::inherit_stmt(&[key]).node().clone();
5858+ let message = "This assignment is better written with `inherit`";
5959+ Some(
6060+ self.report()
6161+ .suggest(at, message, Suggestion::new(at, replacement)),
6262+ )
6363+ } else {
6464+ None
6665 }
6766 }
6867}
+24-27
lib/src/lints/manual_inherit_from.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···41404241impl Rule for ManualInherit {
4342 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4444- if_chain! {
4545- if let NodeOrToken::Node(node) = node;
4646- if let Some(key_value_stmt) = KeyValue::cast(node.clone());
4747- if let mut key_path = key_value_stmt.key()?.path();
4848- if let Some(key_node) = key_path.next();
4343+ if let NodeOrToken::Node(node) = node
4444+ && let Some(key_value_stmt) = KeyValue::cast(node.clone())
4545+ && let mut key_path = key_value_stmt.key()?.path()
4646+ && let Some(key_node) = key_path.next()
4947 // ensure that path has exactly one component
5050- if key_path.next().is_none();
5151- if let Some(key) = Ident::cast(key_node);
5252-5353- if let Some(value_node) = key_value_stmt.value();
5454- if let Some(value) = Select::cast(value_node);
5555- if let Some(index_node) = value.index();
5656- if let Some(index) = Ident::cast(index_node);
5757-5858- if key.as_str() == index.as_str();
5959-6060- then {
6161- let at = node.text_range();
6262- let replacement = {
6363- let set = value.set()?;
6464- make::inherit_from_stmt(&set, &[key]).node().clone()
6565- };
6666- let message = "This assignment is better written with `inherit`";
6767- Some(self.report().suggest(at, message, Suggestion::new(at, replacement)))
6868- } else {
6969- None
7070- }
4848+ && key_path.next().is_none()
4949+ && let Some(key) = Ident::cast(key_node)
5050+ && let Some(value_node) = key_value_stmt.value()
5151+ && let Some(value) = Select::cast(value_node)
5252+ && let Some(index_node) = value.index()
5353+ && let Some(index) = Ident::cast(index_node)
5454+ && key.as_str() == index.as_str()
5555+ {
5656+ let at = node.text_range();
5757+ let replacement = {
5858+ let set = value.set()?;
5959+ make::inherit_from_stmt(&set, &[key]).node().clone()
6060+ };
6161+ let message = "This assignment is better written with `inherit`";
6262+ Some(
6363+ self.report()
6464+ .suggest(at, message, Suggestion::new(at, replacement)),
6565+ )
6666+ } else {
6767+ None
7168 }
7269 }
7370}
+18-17
lib/src/lints/redundant_pattern_bind.rs
···11use crate::{Metadata, Report, Rule, Suggestion, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···36353736impl Rule for RedundantPatternBind {
3837 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
3939- if_chain! {
4040- if let NodeOrToken::Node(node) = node;
4141- if let Some(pattern) = Pattern::cast(node.clone());
3838+ if let NodeOrToken::Node(node) = node
3939+ && let Some(pattern) = Pattern::cast(node.clone())
4240 // no patterns within `{ }`
4343- if pattern.entries().count() == 0;
4444-4141+ && pattern.entries().count() == 0
4542 // pattern is just ellipsis
4646- if pattern.ellipsis();
4747-4343+ && pattern.ellipsis()
4844 // pattern is bound
4949- if let Some(ident) = pattern.at();
5050- then {
5151- let at = node.text_range();
5252- let message = format!("This pattern bind is redundant, use `{}` instead", ident.as_str());
5353- let replacement = ident.node().clone();
5454- Some(self.report().suggest(at, message, Suggestion::new(at, replacement)))
5555- } else {
5656- None
5757- }
4545+ && let Some(ident) = pattern.at()
4646+ {
4747+ let at = node.text_range();
4848+ let message = format!(
4949+ "This pattern bind is redundant, use `{}` instead",
5050+ ident.as_str()
5151+ );
5252+ let replacement = ident.node().clone();
5353+ Some(
5454+ self.report()
5555+ .suggest(at, message, Suggestion::new(at, replacement)),
5656+ )
5757+ } else {
5858+ None
5859 }
5960 }
6061}
+46-42
lib/src/lints/repeated_keys.rs
···2233use crate::{Metadata, Report, Rule, session::SessionInfo};
4455-use if_chain::if_chain;
65use macros::lint;
76use rnix::{
87 NodeOrToken, SyntaxElement, SyntaxKind,
···46454746impl Rule for RepeatedKeys {
4847 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4949- if_chain! {
5050- if let NodeOrToken::Node(node) = node;
5151- if let Some(key_value) = KeyValue::cast(node.clone());
5252- if let Some(key) = key_value.key();
5353- if let mut components = key.path();
5454- if let Some(first_component) = components.next();
5555- if let Some(first_component_ident) = Ident::cast(first_component);
4848+ if let NodeOrToken::Node(node) = node
4949+ && let Some(key_value) = KeyValue::cast(node.clone())
5050+ && let Some(key) = key_value.key()
5151+ && let mut components = key.path()
5252+ && let Some(first_component) = components.next()
5353+ && let Some(first_component_ident) = Ident::cast(first_component)
5654 // ensure that there are >1 components
5757- if components.next().is_some();
5858-5959- if let Some(parent_node) = node.parent();
6060- if let Some(parent_attr_set) = AttrSet::cast(parent_node);
6161-6262- if !parent_attr_set.recursive();
6363- let occurrences = parent_attr_set.entries().filter_map(|kv_scrutinee| {
5555+ && components.next().is_some()
5656+ && let Some(parent_node) = node.parent()
5757+ && let Some(parent_attr_set) = AttrSet::cast(parent_node)
5858+ && !parent_attr_set.recursive()
5959+ && let occurrences = parent_attr_set.entries().filter_map(|kv_scrutinee| {
6460 let scrutinee_key = kv_scrutinee.key()?;
6561 let mut kv_scrutinee_components = scrutinee_key.path();
6662 let kv_scrutinee_first_component = kv_scrutinee_components.next()?;
···7672 } else {
7773 None
7874 }
7979- }).collect::<Vec<_>>();
8080-8181- if occurrences.first()?.0 == key.node().text_range();
8282- if occurrences.len() >= 3;
8383-8484- then {
8585- let mut iter = occurrences.into_iter();
7575+ }).collect::<Vec<_>>()
7676+ && occurrences.first()?.0 == key.node().text_range()
7777+ && occurrences.len() >= 3
7878+ {
7979+ let mut iter = occurrences.into_iter();
86808787- let (first_annotation, first_subkey) = iter.next().unwrap();
8888- let first_message = format!("The key `{}` is first assigned here ...", first_component_ident.as_str());
8181+ let (first_annotation, first_subkey) = iter.next().unwrap();
8282+ let first_message = format!(
8383+ "The key `{}` is first assigned here ...",
8484+ first_component_ident.as_str()
8585+ );
89869090- let (second_annotation, second_subkey) = iter.next().unwrap();
9191- let second_message = "... repeated here ...";
8787+ let (second_annotation, second_subkey) = iter.next().unwrap();
8888+ let second_message = "... repeated here ...";
92899393- let (third_annotation, third_subkey) = iter.next().unwrap();
9494- let third_message = {
9595- let remaining_occurrences = iter.count();
9696- let mut message = match remaining_occurrences {
9797- 0 => "... and here.".to_string(),
9898- 1 => "... and here (`1` occurrence omitted).".to_string(),
9999- n => format!("... and here (`{n}` occurrences omitted)."),
100100- };
101101- write!(message, " Try `{} = {{ {}=...; {}=...; {}=...; }}` instead.", first_component_ident.as_str(), first_subkey, second_subkey, third_subkey).unwrap();
102102- message
9090+ let (third_annotation, third_subkey) = iter.next().unwrap();
9191+ let third_message = {
9292+ let remaining_occurrences = iter.count();
9393+ let mut message = match remaining_occurrences {
9494+ 0 => "... and here.".to_string(),
9595+ 1 => "... and here (`1` occurrence omitted).".to_string(),
9696+ n => format!("... and here (`{n}` occurrences omitted)."),
10397 };
9898+ write!(
9999+ message,
100100+ " Try `{} = {{ {}=...; {}=...; {}=...; }}` instead.",
101101+ first_component_ident.as_str(),
102102+ first_subkey,
103103+ second_subkey,
104104+ third_subkey
105105+ )
106106+ .unwrap();
107107+ message
108108+ };
104109105105- Some(
106106- self.report()
110110+ Some(
111111+ self.report()
107112 .diagnostic(first_annotation, first_message)
108113 .diagnostic(second_annotation, second_message)
109114 .diagnostic(third_annotation, third_message),
110110- )
111111- } else {
112112- None
113113- }
115115+ )
116116+ } else {
117117+ None
114118 }
115119 }
116120}
+12-12
lib/src/lints/unquoted_splice.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···41404241impl Rule for UnquotedSplice {
4342 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4444- if_chain! {
4545- if let NodeOrToken::Node(node) = node;
4646- if Dynamic::cast(node.clone()).is_some();
4747- then {
4848- let at = node.text_range();
4949- let replacement = make::quote(node).node().clone();
5050- let message = "Consider quoting this splice expression";
5151- Some(self.report().suggest(at, message, Suggestion::new(at, replacement)))
5252- } else {
5353- None
5454- }
4343+ if let NodeOrToken::Node(node) = node
4444+ && Dynamic::cast(node.clone()).is_some()
4545+ {
4646+ let at = node.text_range();
4747+ let replacement = make::quote(node).node().clone();
4848+ let message = "Consider quoting this splice expression";
4949+ Some(
5050+ self.report()
5151+ .suggest(at, message, Suggestion::new(at, replacement)),
5252+ )
5353+ } else {
5454+ None
5555 }
5656 }
5757}
+11-12
lib/src/lints/unquoted_uri.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{NodeOrToken, SyntaxElement, SyntaxKind, types::TypedNode};
65···47464847impl Rule for UnquotedUri {
4948 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
5050- if_chain! {
5151- if let NodeOrToken::Token(token) = node;
5252- then {
5353- let parent_node = token.parent();
5454- let at = token.text_range();
5555- let replacement = make::quote(&parent_node).node().clone();
5656- let message = "Consider quoting this URI expression";
5757- Some(self.report().suggest(at, message, Suggestion::new(at, replacement)))
5858- } else {
5959- None
6060- }
4949+ if let NodeOrToken::Token(token) = node {
5050+ let parent_node = token.parent();
5151+ let at = token.text_range();
5252+ let replacement = make::quote(&parent_node).node().clone();
5353+ let message = "Consider quoting this URI expression";
5454+ Some(
5555+ self.report()
5656+ .suggest(at, message, Suggestion::new(at, replacement)),
5757+ )
5858+ } else {
5959+ None
6160 }
6261 }
6362}
+34-37
lib/src/lints/useless_has_attr.rs
···11use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···34333534impl Rule for UselessHasAttr {
3635 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
3737- if_chain! {
3838- if let NodeOrToken::Node(node) = node;
3939- if let Some(if_else_expr) = IfElse::cast(node.clone());
4040- if let Some(condition_expr) = if_else_expr.condition();
4141- if let Some(default_expr) = if_else_expr.else_body();
4242- if let Some(cond_bin_expr) = BinOp::cast(condition_expr);
4343- if let Some(BinOpKind::IsSet) = cond_bin_expr.operator();
3636+ if let NodeOrToken::Node(node) = node
3737+ && let Some(if_else_expr) = IfElse::cast(node.clone())
3838+ && let Some(condition_expr) = if_else_expr.condition()
3939+ && let Some(default_expr) = if_else_expr.else_body()
4040+ && let Some(cond_bin_expr) = BinOp::cast(condition_expr)
4141+ && let Some(BinOpKind::IsSet) = cond_bin_expr.operator()
44424543 // set ? attr_path
4644 // ^^^--------------- lhs
4745 // ^^^^^^^^^^--- rhs
4848- if let Some(set) = cond_bin_expr.lhs();
4949- if let Some(attr_path) = cond_bin_expr.rhs();
4646+ && let Some(set) = cond_bin_expr.lhs()
4747+ && let Some(attr_path) = cond_bin_expr.rhs()
50485149 // check if body of the `if` expression is of the form `set.attr_path`
5252- if let Some(body_expr) = if_else_expr.body();
5353- if let Some(body_select_expr) = Select::cast(body_expr);
5454- let expected_body = make::select(&set, &attr_path);
5050+ && let Some(body_expr) = if_else_expr.body()
5151+ && let Some(body_select_expr) = Select::cast(body_expr)
5252+ &&let expected_body = make::select(&set, &attr_path)
55535654 // text comparison will do for now
5757- if body_select_expr.node().text() == expected_body.node().text();
5858- then {
5959- let at = node.text_range();
6060- // `or` is tightly binding, we need to parenthesize non-literal exprs
6161- let default_with_parens = match default_expr.kind() {
6262- SyntaxKind::NODE_LIST
6363- | SyntaxKind::NODE_PAREN
6464- | SyntaxKind::NODE_STRING
6565- | SyntaxKind::NODE_ATTR_SET
6666- | SyntaxKind::NODE_IDENT
6767- | SyntaxKind::NODE_SELECT => default_expr,
6868- _ => make::parenthesize(&default_expr).node().clone(),
6969- };
7070- let replacement = make::or_default(&set, &attr_path, &default_with_parens).node().clone();
7171- let message = format!(
7272- "Consider using `{replacement}` instead of this `if` expression"
7373- );
7474- Some(
7575- self.report()
7676- .suggest(at, message, Suggestion::new(at, replacement)),
7777- )
7878- } else {
7979- None
8080- }
5555+ && body_select_expr.node().text() == expected_body.node().text()
5656+ {
5757+ let at = node.text_range();
5858+ // `or` is tightly binding, we need to parenthesize non-literal exprs
5959+ let default_with_parens = match default_expr.kind() {
6060+ SyntaxKind::NODE_LIST
6161+ | SyntaxKind::NODE_PAREN
6262+ | SyntaxKind::NODE_STRING
6363+ | SyntaxKind::NODE_ATTR_SET
6464+ | SyntaxKind::NODE_IDENT
6565+ | SyntaxKind::NODE_SELECT => default_expr,
6666+ _ => make::parenthesize(&default_expr).node().clone(),
6767+ };
6868+ let replacement = make::or_default(&set, &attr_path, &default_with_parens)
6969+ .node()
7070+ .clone();
7171+ let message = format!("Consider using `{replacement}` instead of this `if` expression");
7272+ Some(
7373+ self.report()
7474+ .suggest(at, message, Suggestion::new(at, replacement)),
7575+ )
7676+ } else {
7777+ None
8178 }
8279 }
8380}
+47-41
lib/src/lints/useless_parens.rs
···11use crate::{Diagnostic, Metadata, Report, Rule, Suggestion, session::SessionInfo};
2233-use if_chain::if_chain;
43use macros::lint;
54use rnix::{
65 NodeOrToken, SyntaxElement, SyntaxKind,
···46454746impl Rule for UselessParens {
4847 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
4949- if_chain! {
5050- if let NodeOrToken::Node(node) = node;
5151- if let Some(parsed_type_node) = ParsedType::cast(node.clone());
5252-5353- if let Some(diagnostic) = do_thing(parsed_type_node);
5454- then {
5555- let mut report = self.report();
5656- report.diagnostics.push(diagnostic);
5757- Some(report)
5858- } else {
5959- None
6060- }
4848+ if let NodeOrToken::Node(node) = node
4949+ && let Some(parsed_type_node) = ParsedType::cast(node.clone())
5050+ && let Some(diagnostic) = do_thing(parsed_type_node)
5151+ {
5252+ let mut report = self.report();
5353+ report.diagnostics.push(diagnostic);
5454+ Some(report)
5555+ } else {
5656+ None
6157 }
6258 }
6359}
64606561fn do_thing(parsed_type_node: ParsedType) -> Option<Diagnostic> {
6662 match parsed_type_node {
6767- ParsedType::KeyValue(kv) => if_chain! {
6868- if let Some(value_node) = kv.value();
6969- let value_range = value_node.text_range();
7070- if let Some(value_in_parens) = Paren::cast(value_node);
7171- if let Some(inner) = value_in_parens.inner();
7272- then {
6363+ ParsedType::KeyValue(kv) => {
6464+ if let Some(value_node) = kv.value()
6565+ && let value_range = value_node.text_range()
6666+ && let Some(value_in_parens) = Paren::cast(value_node)
6767+ && let Some(inner) = value_in_parens.inner()
6868+ {
7369 let at = value_range;
7470 let message = "Useless parentheses around value in binding";
7571 let replacement = inner;
7676- Some(Diagnostic::suggest(at, message, Suggestion::new(at, replacement)))
7272+ Some(Diagnostic::suggest(
7373+ at,
7474+ message,
7575+ Suggestion::new(at, replacement),
7676+ ))
7777 } else {
7878 None
7979 }
8080- },
8181- ParsedType::LetIn(let_in) => if_chain! {
8282- if let Some(body_node) = let_in.body();
8383- let body_range = body_node.text_range();
8484- if let Some(body_as_parens) = Paren::cast(body_node);
8585- if let Some(inner) = body_as_parens.inner();
8686- then {
8080+ }
8181+ ParsedType::LetIn(let_in) => {
8282+ if let Some(body_node) = let_in.body()
8383+ && let body_range = body_node.text_range()
8484+ && let Some(body_as_parens) = Paren::cast(body_node)
8585+ && let Some(inner) = body_as_parens.inner()
8686+ {
8787 let at = body_range;
8888 let message = "Useless parentheses around body of `let` expression";
8989 let replacement = inner;
9090- Some(Diagnostic::suggest(at, message, Suggestion::new(at, replacement)))
9090+ Some(Diagnostic::suggest(
9191+ at,
9292+ message,
9393+ Suggestion::new(at, replacement),
9494+ ))
9195 } else {
9296 None
9397 }
9494- },
9595- ParsedType::Paren(paren_expr) => if_chain! {
9898+ }
9999+ ParsedType::Paren(paren_expr) => {
96100 let paren_expr_range = paren_expr.node().text_range();
9797- if let Some(father_node) = paren_expr.node().parent();
9898-101101+ if let Some(father_node) = paren_expr.node().parent()
99102 // ensure that we don't lint inside let-in statements
100103 // we already lint such cases in previous match stmt
101101- if KeyValue::cast(father_node.clone()).is_none();
104104+ && KeyValue::cast(father_node.clone()).is_none()
102105103106 // ensure that we don't lint inside let-bodies
104107 // if this primitive is a let-body, we have already linted it
105105- if LetIn::cast(father_node).is_none();
108108+ && LetIn::cast(father_node).is_none()
106109107107- if let Some(inner_node) = paren_expr.inner();
108108- if let Some(parsed_inner) = ParsedType::cast(inner_node);
109109- if matches!(
110110+ && let Some(inner_node) = paren_expr.inner()
111111+ && let Some(parsed_inner) = ParsedType::cast(inner_node)
112112+ && matches!(
110113 parsed_inner,
111114 ParsedType::List(_)
112115 | ParsedType::Paren(_)
···114117 | ParsedType::AttrSet(_)
115118 | ParsedType::Select(_)
116119 | ParsedType::Ident(_)
117117- );
118118- then {
120120+ ) {
119121 let at = paren_expr_range;
120122 let message = "Useless parentheses around primitive expression";
121123 let replacement = parsed_inner.node().clone();
122122- Some(Diagnostic::suggest(at, message, Suggestion::new(at, replacement)))
124124+ Some(Diagnostic::suggest(
125125+ at,
126126+ message,
127127+ Suggestion::new(at, replacement),
128128+ ))
123129 } else {
124130 None
125131 }
126126- },
132132+ }
127133 _ => None,
128134 }
129135}