Lints and suggestions for the Nix programming language
1use crate::{Metadata, Report, Rule, Suggestion, make};
2
3use macros::lint;
4use rnix::{
5 NodeOrToken, SyntaxElement, SyntaxKind,
6 ast::{Attr, AttrpathValue, Expr},
7};
8use rowan::ast::AstNode as _;
9
10/// ## What it does
11/// Checks for bindings of the form `a = a`.
12///
13/// ## Why is this bad?
14/// If the aim is to bring attributes from a larger scope into
15/// the current scope, prefer an inherit statement.
16///
17/// ## Example
18///
19/// ```nix
20/// let
21/// a = 2;
22/// in
23/// { a = a; b = 3; }
24/// ```
25///
26/// Try `inherit` instead:
27///
28/// ```nix
29/// let
30/// a = 2;
31/// in
32/// { inherit a; b = 3; }
33/// ```
34#[lint(
35 name = "manual_inherit",
36 note = "Assignment instead of inherit",
37 code = 3,
38 match_with = SyntaxKind::NODE_ATTRPATH_VALUE
39)]
40struct ManualInherit;
41
42impl Rule for ManualInherit {
43 fn validate(&self, node: &SyntaxElement) -> Option<Report> {
44 let NodeOrToken::Node(node) = node else {
45 return None;
46 };
47
48 let attrpath_value = AttrpathValue::cast(node.clone())?;
49 let attrpath = attrpath_value.attrpath()?;
50 let mut attrs = attrpath.attrs();
51 let first_attr = attrs.next()?;
52
53 if attrs.next().is_some() {
54 return None;
55 }
56
57 let Attr::Ident(key) = first_attr else {
58 return None;
59 };
60
61 let Some(Expr::Ident(value)) = attrpath_value.value() else {
62 return None;
63 };
64
65 if key.to_string() != value.to_string() {
66 return None;
67 }
68
69 let replacement = make::inherit_stmt(&[key]).syntax().clone();
70
71 Some(self.report().suggest(
72 node.text_range(),
73 "This assignment is better written with `inherit`",
74 Suggestion::with_replacement(node.text_range(), replacement),
75 ))
76 }
77}