Lints and suggestions for the Nix programming language
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

new lint: repeated_keys

Check for repeated keys in attrsets. For example:

```
foo.bar1 = 1;
foo.bar2 = 2;
foo.bar3 = 3;
```

should recommend creating a

```
foo = { ... }
```

Report only on multi-level keys

Add tests

tweak repeated-keys lint

authored by

Stanisław Pitucha and committed by
Akshay
6463c054 a0c7a327

+178 -1
+29
bin/tests/data/repeated_keys.nix
··· 1 + [ 2 + # fine 3 + { 4 + foo.bar = 1; 5 + } 6 + 7 + # do not raise on rec 8 + rec { 9 + foo.x = foo.y; 10 + foo.y = 2; 11 + foo.z = 3; 12 + } 13 + 14 + # exactly 3 occurrences 15 + { 16 + foo.bar = 1; 17 + foo.bar."hello" = 1; 18 + foo.again = 1; 19 + } 20 + 21 + # more than 3, omit the extra 22 + { 23 + foo.baz.bar1 = 1; 24 + foo.baz.bar2 = 2; 25 + foo.baz.bar3 = 3; 26 + foo.baz.bar4 = 4; 27 + foo.baz.bar5 = 5; 28 + } 29 + ]
+2 -1
bin/tests/main.rs
··· 65 65 faster_zipattrswith => session_info!("2.6"), 66 66 deprecated_to_path => session_info!("2.4"), 67 67 bool_simplification, 68 - useless_has_attr 68 + useless_has_attr, 69 + repeated_keys 69 70 }
+32
bin/tests/snapshots/main__repeated_keys.snap
··· 1 + --- 2 + source: bin/tests/main.rs 3 + expression: "&out" 4 + 5 + --- 6 + [W20] Warning: Avoid repeated keys in attribute sets 7 + ╭─[data/repeated_keys.nix:16:5] 8 + 9 + 16 │ foo.bar = 1; 10 + · ───┬─── 11 + · ╰───── The key foo is first assigned here ... 12 + 17 │ foo.bar."hello" = 1; 13 + · ───────┬─────── 14 + · ╰───────── ... repeated here ... 15 + 18 │ foo.again = 1; 16 + · ────┬──── 17 + · ╰────── ... and here. Try foo = { bar=...; bar."hello"=...; again=...; } instead. 18 + ────╯ 19 + [W20] Warning: Avoid repeated keys in attribute sets 20 + ╭─[data/repeated_keys.nix:23:5] 21 + 22 + 23 │ foo.baz.bar1 = 1; 23 + · ──────┬───── 24 + · ╰─────── The key foo is first assigned here ... 25 + 24 │ foo.baz.bar2 = 2; 26 + · ──────┬───── 27 + · ╰─────── ... repeated here ... 28 + 25 │ foo.baz.bar3 = 3; 29 + · ──────┬───── 30 + · ╰─────── ... and here (2 occurrences omitted). Try foo = { baz.bar1=...; baz.bar2=...; baz.bar3=...; } instead. 31 + ────╯ 32 +
+1
lib/src/lints.rs
··· 20 20 deprecated_to_path, 21 21 bool_simplification, 22 22 useless_has_attr, 23 + repeated_keys, 23 24 }
+114
lib/src/lints/repeated_keys.rs
··· 1 + use crate::{session::SessionInfo, Metadata, Report, Rule}; 2 + 3 + use if_chain::if_chain; 4 + use macros::lint; 5 + use rnix::{ 6 + types::{AttrSet, EntryHolder, Ident, KeyValue, TokenWrapper, TypedNode}, 7 + NodeOrToken, SyntaxElement, SyntaxKind, 8 + }; 9 + 10 + /// ## What it does 11 + /// Checks for keys in attribute sets with repetitive keys, and suggests using 12 + /// an attribute set instead. 13 + /// 14 + /// ## Why is this bad? 15 + /// Avoiding repetetion helps improve readibility. 16 + /// 17 + /// ## Example 18 + /// ```nix 19 + /// { 20 + /// foo.a = 1; 21 + /// foo.b = 2; 22 + /// foo.c = 3; 23 + /// } 24 + /// ``` 25 + /// 26 + /// Don't repeat. 27 + /// ```nix 28 + /// { 29 + /// foo = { 30 + /// a = 1; 31 + /// b = 2; 32 + /// c = 3; 33 + /// }; 34 + /// } 35 + /// ``` 36 + 37 + #[lint( 38 + name = "repeated_keys", 39 + note = "Avoid repeated keys in attribute sets", 40 + code = 20, 41 + match_with = SyntaxKind::NODE_KEY_VALUE 42 + )] 43 + struct RepeatedKeys; 44 + 45 + impl Rule for RepeatedKeys { 46 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 47 + if_chain! { 48 + if let NodeOrToken::Node(node) = node; 49 + if let Some(key_value) = KeyValue::cast(node.clone()); 50 + if let Some(key) = key_value.key(); 51 + if let mut components = key.path(); 52 + if let Some(first_component) = components.next(); 53 + if let Some(first_component_ident) = Ident::cast(first_component); 54 + // ensure that there are >1 components 55 + if components.next().is_some(); 56 + 57 + if let Some(parent_node) = node.parent(); 58 + if let Some(parent_attr_set) = AttrSet::cast(parent_node.clone()); 59 + 60 + if !parent_attr_set.recursive(); 61 + let occurrences = parent_attr_set.entries().filter_map(|kv_scrutinee| { 62 + let scrutinee_key = kv_scrutinee.key()?; 63 + let mut kv_scrutinee_components = scrutinee_key.path(); 64 + let kv_scrutinee_first_component = kv_scrutinee_components.next()?; 65 + let kv_scrutinee_ident = Ident::cast(kv_scrutinee_first_component)?; 66 + if kv_scrutinee_ident.as_str() == first_component_ident.as_str() { 67 + Some(( 68 + kv_scrutinee.key()?.node().text_range(), 69 + kv_scrutinee_components 70 + .map(|n| n.to_string()) 71 + .collect::<Vec<_>>() 72 + .join("."), 73 + )) 74 + } else { 75 + None 76 + } 77 + }).collect::<Vec<_>>(); 78 + 79 + if occurrences.first()?.0 == key.node().text_range(); 80 + if occurrences.len() >= 3; 81 + 82 + then { 83 + let mut iter = occurrences.into_iter(); 84 + 85 + let (first_annotation, first_subkey) = iter.next().unwrap(); 86 + let first_message = format!("The key `{}` is first assigned here ...", first_component_ident.as_str()); 87 + 88 + let (second_annotation, second_subkey) = iter.next().unwrap(); 89 + let second_message = "... repeated here ..."; 90 + 91 + let (third_annotation, third_subkey) = iter.next().unwrap(); 92 + let third_message = { 93 + let remaining_occurrences = iter.count(); 94 + let mut message = match remaining_occurrences { 95 + 0 => format!("... and here."), 96 + 1 => format!("... and here (`1` occurrence omitted)."), 97 + n => format!("... and here (`{}` occurrences omitted).", n), 98 + }; 99 + message.push_str(&format!(" Try `{} = {{ {}=...; {}=...; {}=...; }}` instead.", first_component_ident.as_str(), first_subkey, second_subkey, third_subkey)); 100 + message 101 + }; 102 + 103 + Some( 104 + self.report() 105 + .diagnostic(first_annotation, first_message) 106 + .diagnostic(second_annotation, second_message) 107 + .diagnostic(third_annotation, third_message), 108 + ) 109 + } else { 110 + None 111 + } 112 + } 113 + } 114 + }