Lints and suggestions for the Nix programming language
1
fork

Configure Feed

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

feat: bump rnix dependency to 0.11.0

Co-authored-by: aviac <aviac@mailbox.org>
Co-authored-by: Wes Gray <wes.gray@gmail.com>
Co-authored-by: x10an14 <x10an14@users.noreply.github.com>

+342 -270
+17 -36
Cargo.lock
··· 59 59 ] 60 60 61 61 [[package]] 62 - name = "cbitset" 63 - version = "0.2.0" 64 - source = "registry+https://github.com/rust-lang/crates.io-index" 65 - checksum = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b" 66 - dependencies = [ 67 - "num-traits", 68 - ] 69 - 70 - [[package]] 71 62 name = "cfg-if" 72 63 version = "1.0.0" 73 64 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 119 110 120 111 [[package]] 121 112 name = "countme" 122 - version = "2.0.4" 113 + version = "3.0.1" 123 114 source = "registry+https://github.com/rust-lang/crates.io-index" 124 - checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" 115 + checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" 125 116 126 117 [[package]] 127 118 name = "crossbeam-channel" ··· 153 144 "cfg-if", 154 145 "crossbeam-utils", 155 146 "lazy_static", 156 - "memoffset", 147 + "memoffset 0.6.4", 157 148 "scopeguard", 158 149 ] 159 150 ··· 206 197 207 198 [[package]] 208 199 name = "hashbrown" 209 - version = "0.9.1" 200 + version = "0.11.2" 210 201 source = "registry+https://github.com/rust-lang/crates.io-index" 211 - checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 202 + checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 212 203 213 204 [[package]] 214 205 name = "hashbrown" 215 - version = "0.11.2" 206 + version = "0.14.5" 216 207 source = "registry+https://github.com/rust-lang/crates.io-index" 217 - checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 208 + checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 218 209 219 210 [[package]] 220 211 name = "heck" ··· 347 338 ] 348 339 349 340 [[package]] 350 - name = "num-traits" 351 - version = "0.2.14" 341 + name = "memoffset" 342 + version = "0.9.1" 352 343 source = "registry+https://github.com/rust-lang/crates.io-index" 353 - checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 344 + checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 354 345 dependencies = [ 355 346 "autocfg", 356 347 ] ··· 472 463 473 464 [[package]] 474 465 name = "rnix" 475 - version = "0.10.2" 466 + version = "0.11.0" 476 467 source = "registry+https://github.com/rust-lang/crates.io-index" 477 - checksum = "8024a523e8836f1a5d051203dc00d833357fee94e351b51348dfaeca5364daa9" 468 + checksum = "bb35cedbeb70e0ccabef2a31bcff0aebd114f19566086300b8f42c725fc2cb5f" 478 469 dependencies = [ 479 - "cbitset", 480 470 "rowan", 481 - "smol_str", 482 471 ] 483 472 484 473 [[package]] 485 474 name = "rowan" 486 - version = "0.12.6" 475 + version = "0.15.17" 487 476 source = "registry+https://github.com/rust-lang/crates.io-index" 488 - checksum = "a1b36e449f3702f3b0c821411db1cbdf30fb451726a9456dce5dabcd44420043" 477 + checksum = "d4f1e4a001f863f41ea8d0e6a0c34b356d5b733db50dadab3efef640bafb779b" 489 478 dependencies = [ 490 479 "countme", 491 - "hashbrown 0.9.1", 492 - "memoffset", 480 + "hashbrown 0.14.5", 481 + "memoffset 0.9.1", 493 482 "rustc-hash", 494 483 "text-size", 495 484 ] ··· 577 566 checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" 578 567 579 568 [[package]] 580 - name = "smol_str" 581 - version = "0.1.20" 582 - source = "registry+https://github.com/rust-lang/crates.io-index" 583 - checksum = "559b173452ec4061933b0c0e22d7d429c90ecdc1b3ae1c6e64238e7c15c3ee15" 584 - dependencies = [ 585 - "serde", 586 - ] 587 - 588 - [[package]] 589 569 name = "statix" 590 570 version = "0.5.8" 591 571 dependencies = [ ··· 597 577 "paste", 598 578 "rayon", 599 579 "rnix", 580 + "rowan", 600 581 "serde", 601 582 "serde_json", 602 583 "similar 2.1.0",
+2 -2
Cargo.toml
··· 20 20 proc-macro2 = "1.0.27" 21 21 quote = "1.0" 22 22 rayon = "1.5.1" 23 - rnix = "0.10.2" 24 - rowan = "0.12.5" 23 + rnix = "0.11.0" 24 + rowan = "0.15.17" 25 25 serde = { features = ["derive"], version = "1.0.68" } 26 26 serde_json = { version = "1.0.68" } 27 27 similar = "2.1.0"
+1
bin/Cargo.toml
··· 21 21 lib.workspace = true 22 22 rayon.workspace = true 23 23 rnix.workspace = true 24 + rowan.workspace = true 24 25 serde.workspace = true 25 26 serde_json = { optional = true, workspace = true } 26 27 similar.workspace = true
+5 -4
bin/src/fix/all.rs
··· 1 1 use std::borrow::Cow; 2 2 3 3 use lib::{Report, session::SessionInfo}; 4 - use rnix::{WalkEvent, parser::ParseError as RnixParseErr}; 4 + use rnix::{Root, WalkEvent, parser::ParseError as RnixParseErr}; 5 + use rowan::ast::AstNode as _; 5 6 6 7 use crate::{ 7 8 LintMap, ··· 13 14 lints: &LintMap, 14 15 sess: &SessionInfo, 15 16 ) -> Result<Vec<Report>, RnixParseErr> { 16 - let parsed = rnix::parse(source).as_result()?; 17 + let parsed = Root::parse(source).ok()?; 17 18 18 19 Ok(parsed 19 - .node() 20 + .syntax() 20 21 .preorder_with_tokens() 21 22 .filter_map(|event| match event { 22 23 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { ··· 93 94 sess: &'a SessionInfo, 94 95 ) -> Option<FixResult<'a>> { 95 96 let src = Cow::from(src); 96 - let _ = rnix::parse(&src).as_result().ok()?; 97 + let _ = Root::parse(&src).ok().ok()?; 97 98 let initial = FixResult::empty(src, lints, sess); 98 99 initial.into_iter().last() 99 100 }
+3 -3
bin/src/fix/single.rs
··· 1 1 use std::{borrow::Cow, convert::TryFrom}; 2 2 3 3 use lib::{Report, session::SessionInfo}; 4 - use rnix::{TextSize, WalkEvent}; 4 + use rnix::{Root, TextSize, WalkEvent}; 5 5 6 6 use crate::{err::SingleFixErr, fix::Source, utils}; 7 7 ··· 29 29 30 30 fn find(offset: TextSize, src: &str, sess: &SessionInfo) -> Result<Report, SingleFixErr> { 31 31 // we don't really need the source to form a completely parsed tree 32 - let parsed = rnix::parse(src); 32 + let parsed = Root::parse(src); 33 33 let lints = utils::lint_map(); 34 34 35 35 parsed 36 - .node() 36 + .syntax() 37 37 .preorder_with_tokens() 38 38 .filter_map(|event| match event { 39 39 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
+5 -5
bin/src/lint.rs
··· 1 1 use crate::{LintMap, utils}; 2 2 3 3 use lib::{Report, session::SessionInfo}; 4 - use rnix::WalkEvent; 4 + use rnix::{Root, WalkEvent}; 5 5 use vfs::{FileId, VfsEntry}; 6 6 7 7 #[derive(Debug)] ··· 14 14 pub fn lint_with(vfs_entry: &VfsEntry, lints: &LintMap, sess: &SessionInfo) -> LintResult { 15 15 let file_id = vfs_entry.file_id; 16 16 let source = vfs_entry.contents; 17 - let parsed = rnix::parse(source); 17 + let parsed = Root::parse(source); 18 18 19 19 let error_reports = parsed 20 20 .errors() 21 - .into_iter() 22 - .map(|err: rnix::parser::ParseError| Report::from_parse_err(&err)); 21 + .iter() 22 + .map(|err: &rnix::parser::ParseError| Report::from_parse_err(err)); 23 23 let reports = parsed 24 - .node() 24 + .syntax() 25 25 .preorder_with_tokens() 26 26 .filter_map(|event| match event { 27 27 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
+3 -3
bin/tests/snapshots/main__empty_list_concat_lint.snap
··· 31 31 · ╰──────── Concatenation with the empty list, [], is a no-op 32 32 ────╯ 33 33 [W23] Warning: Unnecessary concatenation with empty list 34 - ╭─[data/empty_list_concat.nix:15:4] 34 + ╭─[data/empty_list_concat.nix:15:10] 35 35 36 36 15 │ ([] ++ [] ++ []) 37 - · ────┬─── 38 - · ╰───── Concatenation with the empty list, [], is a no-op 37 + · ────┬─── 38 + · ╰───── Concatenation with the empty list, [], is a no-op 39 39 ────╯
+13 -18
lib/src/lints/bool_comparison.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, 6 - types::{BinOp, BinOpKind, Ident, TokenWrapper, TypedNode}, 6 + ast::{BinOp, BinOpKind, Ident}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for expressions of the form `x == true`, `x != true` and ··· 40 41 }; 41 42 let bin_expr = BinOp::cast(node.clone())?; 42 43 let (lhs, rhs) = (bin_expr.lhs()?, bin_expr.rhs()?); 44 + let (lhs, rhs) = (lhs.syntax(), rhs.syntax()); 43 45 let op = EqualityBinOpKind::try_from(bin_expr.operator()?)?; 44 46 45 47 let (bool_side, non_bool_side): (NixBoolean, &SyntaxNode) = 46 - match (boolean_ident(&lhs), boolean_ident(&rhs)) { 48 + match (boolean_ident(lhs), boolean_ident(rhs)) { 47 49 (None, None) => return None, 48 - (None, Some(bool)) => (bool, &lhs), 49 - (Some(bool), _) => (bool, &rhs), 50 + (None, Some(bool)) => (bool, lhs), 51 + (Some(bool), _) => (bool, rhs), 50 52 }; 51 53 52 54 let replacement = match (&bool_side, op) { ··· 59 61 | (NixBoolean::False, EqualityBinOpKind::Equal) => { 60 62 // `a != true`, `a == false` replace with `!a` 61 63 match non_bool_side.kind() { 62 - SyntaxKind::NODE_APPLY | SyntaxKind::NODE_PAREN | SyntaxKind::NODE_IDENT => { 64 + SyntaxKind::NODE_APPLY 65 + | SyntaxKind::NODE_PAREN 66 + | SyntaxKind::NODE_IDENT 67 + | SyntaxKind::NODE_HAS_ATTR => { 63 68 // do not parenthsize the replacement 64 - make::unary_not(non_bool_side).node().clone() 65 - } 66 - SyntaxKind::NODE_BIN_OP => { 67 - let inner = BinOp::cast(non_bool_side.clone()).unwrap(); 68 - // `!a ? b`, no paren required 69 - if inner.operator()? == BinOpKind::IsSet { 70 - make::unary_not(non_bool_side).node().clone() 71 - } else { 72 - let parens = make::parenthesize(non_bool_side); 73 - make::unary_not(parens.node()).node().clone() 74 - } 69 + make::unary_not(non_bool_side).syntax().clone() 75 70 } 76 71 _ => { 77 72 let parens = make::parenthesize(non_bool_side); 78 - make::unary_not(parens.node()).node().clone() 73 + make::unary_not(parens.syntax()).syntax().clone() 79 74 } 80 75 } 81 76 } ··· 125 120 126 121 // not entirely accurate, underhanded nix programmers might write `true = false` 127 122 fn boolean_ident(node: &SyntaxNode) -> Option<NixBoolean> { 128 - Ident::cast(node.clone()).and_then(|ident_expr| match ident_expr.as_str() { 123 + Ident::cast(node.clone()).and_then(|ident_expr| match ident_expr.to_string().as_str() { 129 124 "true" => Some(NixBoolean::True), 130 125 "false" => Some(NixBoolean::False), 131 126 _ => None,
+17 -7
lib/src/lints/bool_simplification.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{BinOp, BinOpKind, Paren, TypedNode, UnaryOp, UnaryOpKind, Wrapper}, 6 + ast::{BinOpKind, Expr, UnaryOp, UnaryOpKind}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for boolean expressions that can be simplified. ··· 38 39 39 40 let unary_expr = UnaryOp::cast(node.clone())?; 40 41 41 - if unary_expr.operator() != UnaryOpKind::Invert { 42 + if unary_expr.operator() != Some(UnaryOpKind::Invert) { 42 43 return None; 43 44 } 44 45 45 - let value_expr = unary_expr.value()?; 46 - let paren_expr = Paren::cast(value_expr)?; 47 - let inner_expr = paren_expr.inner()?; 48 - let bin_expr = BinOp::cast(inner_expr)?; 46 + let value_expr = unary_expr.expr()?; 47 + 48 + let Expr::Paren(paren_expr) = value_expr else { 49 + return None; 50 + }; 51 + 52 + let inner_expr = paren_expr.expr()?; 53 + 54 + let Expr::BinOp(bin_expr) = inner_expr else { 55 + return None; 56 + }; 49 57 50 58 let Some(BinOpKind::Equal) = bin_expr.operator() else { 51 59 return None; ··· 56 64 57 65 let lhs = bin_expr.lhs()?; 58 66 let rhs = bin_expr.rhs()?; 59 - let replacement = make::binary(&lhs, "!=", &rhs).node().clone(); 67 + let replacement = make::binary(lhs.syntax(), "!=", rhs.syntax()) 68 + .syntax() 69 + .clone(); 60 70 Some( 61 71 self.report() 62 72 .suggest(at, message, Suggestion::with_replacement(at, replacement)),
+8 -4
lib/src/lints/collapsible_let_in.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, TextRange, 6 - types::{LetIn, TypedNode}, 6 + ast::{Expr, LetIn}, 7 7 }; 8 - use rowan::Direction; 8 + use rowan::{Direction, ast::AstNode as _}; 9 9 10 10 /// ## What it does 11 11 /// Checks for `let-in` expressions whose body is another `let-in` ··· 52 52 let let_in_expr = LetIn::cast(node.clone())?; 53 53 let body = let_in_expr.body()?; 54 54 55 - LetIn::cast(body.clone())?; 55 + let Expr::LetIn(_) = body else { 56 + return None; 57 + }; 56 58 57 59 let first_annotation = node.text_range(); 58 60 let first_message = "This `let in` expression contains a nested `let in` expression"; 59 61 60 - let second_annotation = body.text_range(); 62 + let second_annotation = body.syntax().text_range(); 61 63 let second_message = "This `let in` expression is nested"; 62 64 63 65 let replacement_at = { 64 66 let start = body 67 + .syntax() 65 68 .siblings_with_tokens(Direction::Prev) 66 69 .find(|elem| elem.kind() == SyntaxKind::TOKEN_IN)? 67 70 .text_range() 68 71 .start(); 69 72 let end = body 73 + .syntax() 70 74 .descendants_with_tokens() 71 75 .find(|elem| elem.kind() == SyntaxKind::TOKEN_LET)? 72 76 .text_range()
+2 -4
lib/src/lints/deprecated_to_path.rs
··· 1 1 use crate::{Metadata, Report, Rule, session::SessionInfo}; 2 2 3 3 use macros::lint; 4 - use rnix::{ 5 - NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{Apply, TypedNode}, 7 - }; 4 + use rnix::{NodeOrToken, SyntaxElement, SyntaxKind, ast::Apply}; 5 + use rowan::ast::AstNode as _; 8 6 9 7 /// ## What it does 10 8 /// Checks for usage of the `toPath` function.
+3 -5
lib/src/lints/empty_inherit.rs
··· 1 1 use crate::{Metadata, Report, Rule, Suggestion, session::SessionInfo, utils}; 2 2 3 3 use macros::lint; 4 - use rnix::{ 5 - NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{Inherit, TypedNode}, 7 - }; 4 + use rnix::{NodeOrToken, SyntaxElement, SyntaxKind, ast::Inherit}; 5 + use rowan::ast::AstNode as _; 8 6 9 7 /// ## What it does 10 8 /// Checks for empty inherit statements. ··· 39 37 return None; 40 38 } 41 39 42 - if inherit_stmt.idents().count() != 0 { 40 + if inherit_stmt.attrs().count() != 0 { 43 41 return None; 44 42 } 45 43
+8 -4
lib/src/lints/empty_let_in.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{EntryHolder, LetIn, TypedNode}, 6 + ast::{HasEntry as _, LetIn}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for `let-in` expressions which create no new bindings. ··· 48 49 .any(|el| el.kind() == SyntaxKind::TOKEN_COMMENT); 49 50 50 51 let at = node.text_range(); 51 - let replacement = body; 52 + let replacement = body.syntax(); 52 53 let message = "This let-in expression has no entries"; 53 54 Some(if has_comments { 54 55 self.report().diagnostic(at, message) 55 56 } else { 56 - self.report() 57 - .suggest(at, message, Suggestion::with_replacement(at, replacement)) 57 + self.report().suggest( 58 + at, 59 + message, 60 + Suggestion::with_replacement(at, replacement.clone()), 61 + ) 58 62 }) 59 63 } else { 60 64 None
+13 -8
lib/src/lints/empty_list_concat.rs
··· 2 2 3 3 use macros::lint; 4 4 use rnix::{ 5 - NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, 6 - types::{BinOp, BinOpKind, List, TypedNode}, 5 + NodeOrToken, SyntaxElement, SyntaxKind, 6 + ast::{BinOp, BinOpKind, Expr}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for concatenations to empty lists ··· 54 55 return None; 55 56 }; 56 57 57 - Some( 58 - self.report() 59 - .suggest(at, message, Suggestion::with_replacement(at, empty_array)), 60 - ) 58 + Some(self.report().suggest( 59 + at, 60 + message, 61 + Suggestion::with_replacement(at, empty_array.syntax().clone()), 62 + )) 61 63 } 62 64 } 63 65 64 - fn is_empty_array(node: &SyntaxNode) -> bool { 65 - List::cast(node.clone()).is_some_and(|list| list.items().count() == 0) 66 + fn is_empty_array(expr: &Expr) -> bool { 67 + let Expr::List(list) = expr else { 68 + return false; 69 + }; 70 + list.items().count() == 0 66 71 }
+20 -10
lib/src/lints/empty_pattern.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, 6 - types::{AttrSet, EntryHolder, Lambda, Pattern, TypedNode}, 6 + ast::{AttrSet, Entry, HasEntry as _, Lambda, Param}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for an empty variadic pattern: `{...}`, in a function ··· 46 47 }; 47 48 48 49 let lambda_expr = Lambda::cast(node.clone())?; 49 - let pattern = Pattern::cast(lambda_expr.arg()?)?; 50 + 51 + let Some(Param::Pattern(pattern)) = lambda_expr.param() else { 52 + return None; 53 + }; 50 54 51 55 // no patterns within `{ }` 52 - if pattern.entries().count() != 0 { 56 + if pattern.pat_entries().count() != 0 { 53 57 return None; 54 58 } 55 59 56 60 // pattern is not bound 57 - if pattern.at().is_some() { 61 + if pattern.pat_bind().is_some() { 58 62 return None; 59 63 } 60 64 61 - if is_module(&lambda_expr.body()?) { 65 + if is_module(lambda_expr.body()?.syntax()) { 62 66 return None; 63 67 } 64 68 65 69 Some(self.report().suggest( 66 - pattern.node().text_range(), 70 + pattern.syntax().text_range(), 67 71 "This pattern is empty, use `_` instead", 68 72 Suggestion::with_replacement( 69 - pattern.node().text_range(), 70 - make::ident("_").node().clone(), 73 + pattern.syntax().text_range(), 74 + make::ident("_").syntax().clone(), 71 75 ), 72 76 )) 73 77 } ··· 80 84 81 85 attr_set 82 86 .entries() 83 - .filter_map(|e| e.key()) 84 - .any(|k| k.node().to_string() == "imports") 87 + .filter_map(|e| { 88 + let Entry::AttrpathValue(attrpath_value) = e else { 89 + return None; 90 + }; 91 + 92 + attrpath_value.attrpath() 93 + }) 94 + .any(|k| k.to_string() == "imports") 85 95 }
+28 -12
lib/src/lints/eta_reduction.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, 6 - types::{Apply, Ident, Lambda, TokenWrapper, TypedNode}, 6 + ast::{Expr, Ident, Lambda, Param}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for eta-reducible functions, i.e.: converts lambda ··· 47 48 }; 48 49 49 50 let lambda_expr = Lambda::cast(node.clone())?; 50 - let ident = Ident::cast(lambda_expr.arg()?)?; 51 - let body = Apply::cast(lambda_expr.body()?)?; 51 + 52 + let Some(Param::IdentParam(ident_param)) = lambda_expr.param() else { 53 + return None; 54 + }; 55 + 56 + let ident = ident_param.ident()?; 57 + 58 + let Some(Expr::Apply(body)) = lambda_expr.body() else { 59 + return None; 60 + }; 52 61 53 - if ident.as_str() != Ident::cast(body.value()?)?.as_str() { 62 + let Some(Expr::Ident(body_ident)) = body.argument() else { 63 + return None; 64 + }; 65 + 66 + if ident.to_string() != body_ident.to_string() { 54 67 return None; 55 68 } 56 69 57 70 let lambda_node = body.lambda()?; 58 71 59 - if mentions_ident(&ident, &lambda_node) { 72 + if mentions_ident(&ident, lambda_node.syntax()) { 60 73 return None; 61 74 } 62 75 63 76 // lambda body should be no more than a single Ident to 64 77 // retain code readability 65 - Ident::cast(lambda_node)?; 78 + let Expr::Ident(_) = lambda_node else { 79 + return None; 80 + }; 66 81 67 82 let at = node.text_range(); 68 83 let replacement = body.lambda()?; 69 - let message = format!("Found eta-reduction: `{}`", replacement.text()); 70 - Some( 71 - self.report() 72 - .suggest(at, message, Suggestion::with_replacement(at, replacement)), 73 - ) 84 + let message = format!("Found eta-reduction: `{}`", replacement.syntax().text()); 85 + Some(self.report().suggest( 86 + at, 87 + message, 88 + Suggestion::with_replacement(at, replacement.syntax().clone()), 89 + )) 74 90 } 75 91 } 76 92 77 93 fn mentions_ident(ident: &Ident, node: &SyntaxNode) -> bool { 78 94 if let Some(node_ident) = Ident::cast(node.clone()) { 79 - node_ident.as_str() == ident.as_str() 95 + node_ident.to_string() == ident.to_string() 80 96 } else { 81 97 node.children().any(|child| mentions_ident(ident, &child)) 82 98 }
+18 -9
lib/src/lints/legacy_let_syntax.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{EntryHolder, Ident, LegacyLet, TokenWrapper, TypedNode}, 6 + ast::{Attr, Entry, HasEntry, LegacyLet}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for legacy-let syntax that was never formalized. ··· 52 53 53 54 if !legacy_let 54 55 .entries() 55 - .filter_map(|kv| { 56 - let key = kv.key()?; 57 - let first_component = key.path().next()?; 58 - Ident::cast(first_component) 56 + .filter_map(|entry| { 57 + let Entry::AttrpathValue(attrpath_value) = entry else { 58 + return None; 59 + }; 60 + 61 + let first_component = attrpath_value.attrpath()?.attrs().next()?; 62 + 63 + let Attr::Ident(ident) = first_component else { 64 + return None; 65 + }; 66 + 67 + Some(ident) 59 68 }) 60 - .any(|ident| ident.as_str() == "body") 69 + .any(|ident| ident.to_string() == "body") 61 70 { 62 71 return None; 63 72 } ··· 65 74 let inherits = legacy_let.inherits(); 66 75 let entries = legacy_let.entries(); 67 76 let attrset = make::attrset(inherits, entries, true); 68 - let parenthesized = make::parenthesize(attrset.node()); 69 - let selected = make::select(parenthesized.node(), make::ident("body").node()); 77 + let parenthesized = make::parenthesize(attrset.syntax()); 78 + let selected = make::select(parenthesized.syntax(), make::ident("body").syntax()); 70 79 71 80 let at = node.text_range(); 72 81 let message = "Prefer `rec` over undocumented `let` syntax"; 73 - let replacement = selected.node().clone(); 82 + let replacement = selected.syntax().clone(); 74 83 75 84 Some( 76 85 self.report()
+17 -11
lib/src/lints/manual_inherit.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{Ident, KeyValue, TokenWrapper, TypedNode}, 6 + ast::{Attr, AttrpathValue, Expr}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for bindings of the form `a = a`. ··· 34 35 name = "manual_inherit", 35 36 note = "Assignment instead of inherit", 36 37 code = 3, 37 - match_with = SyntaxKind::NODE_KEY_VALUE 38 + match_with = SyntaxKind::NODE_ATTRPATH_VALUE 38 39 )] 39 40 struct ManualInherit; 40 41 ··· 44 45 return None; 45 46 }; 46 47 47 - let key = KeyValue::cast(node.clone())?.key()?; 48 - let mut key_path = key.path(); 49 - let key_node = key_path.next()?; 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()?; 50 52 51 - if key_path.next().is_some() { 53 + if attrs.next().is_some() { 52 54 return None; 53 55 } 54 56 55 - let key = Ident::cast(key_node)?; 56 - let value_node = KeyValue::cast(node.clone())?.value()?; 57 - let value = Ident::cast(value_node)?; 57 + let Attr::Ident(key) = first_attr else { 58 + return None; 59 + }; 58 60 59 - if key.as_str() != value.as_str() { 61 + let Some(Expr::Ident(value)) = attrpath_value.value() else { 62 + return None; 63 + }; 64 + 65 + if key.to_string() != value.to_string() { 60 66 return None; 61 67 } 62 68 63 - let replacement = make::inherit_stmt(&[key]).node().clone(); 69 + let replacement = make::inherit_stmt(&[key]).syntax().clone(); 64 70 65 71 Some(self.report().suggest( 66 72 node.text_range(),
+30 -11
lib/src/lints/manual_inherit_from.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{Ident, KeyValue, Select, TokenWrapper, TypedNode}, 6 + ast::{Attr, AttrpathValue, Expr}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for bindings of the form `a = someAttr.a`. ··· 34 35 name = "manual_inherit_from", 35 36 note = "Assignment instead of inherit from", 36 37 code = 4, 37 - match_with = SyntaxKind::NODE_KEY_VALUE 38 + match_with = SyntaxKind::NODE_ATTRPATH_VALUE, 38 39 )] 39 40 struct ManualInherit; 40 41 ··· 44 45 return None; 45 46 }; 46 47 47 - let key_value_stmt = KeyValue::cast(node.clone())?; 48 - let key = key_value_stmt.key()?; 49 - let mut key_path = key.path(); 48 + let key_value_stmt = AttrpathValue::cast(node.clone())?; 49 + let key = key_value_stmt.attrpath()?; 50 + let mut key_path = key.attrs(); 50 51 let key_node = key_path.next()?; 51 52 52 53 if key_path.next().is_some() { 53 54 return None; 54 55 } 55 56 56 - let key = Ident::cast(key_node)?; 57 - let value = Select::cast(key_value_stmt.value()?)?; 58 - let index = Ident::cast(value.index()?)?; 57 + let Attr::Ident(key) = key_node else { 58 + return None; 59 + }; 60 + 61 + let Some(Expr::Select(value)) = key_value_stmt.value() else { 62 + return None; 63 + }; 64 + let select_attrpath = value.attrpath()?; 65 + let mut select_attrpath_attrs = select_attrpath.attrs(); 66 + let first_attr = select_attrpath_attrs.next()?; 59 67 60 - if key.as_str() != index.as_str() { 68 + if select_attrpath_attrs.next().is_some() { 69 + return None; 70 + } 71 + 72 + let Attr::Ident(index) = first_attr else { 73 + return None; 74 + }; 75 + 76 + if key.to_string() != index.to_string() { 61 77 return None; 62 78 } 63 79 64 80 let at = node.text_range(); 81 + 65 82 let replacement = { 66 - let set = value.set()?; 67 - make::inherit_from_stmt(&set, &[key]).node().clone() 83 + let set = value.expr()?; 84 + make::inherit_from_stmt(set.syntax(), &[key]) 85 + .syntax() 86 + .clone() 68 87 }; 69 88 70 89 Some(self.report().suggest(
+8 -14
lib/src/lints/redundant_pattern_bind.rs
··· 1 1 use crate::{Metadata, Report, Rule, Suggestion, session::SessionInfo}; 2 2 3 3 use macros::lint; 4 - use rnix::{ 5 - NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{Pattern, TokenWrapper, TypedNode}, 7 - }; 4 + use rnix::{NodeOrToken, SyntaxElement, SyntaxKind, ast::Pattern}; 5 + use rowan::ast::AstNode as _; 8 6 9 7 /// ## What it does 10 8 /// Checks for binds of the form `inputs @ { ... }` in function ··· 42 40 let pattern = Pattern::cast(node.clone())?; 43 41 44 42 // no patterns within `{ }` 45 - if pattern.entries().count() != 0 { 43 + if pattern.pat_entries().count() != 0 { 46 44 return None; 47 45 } 48 46 49 47 // pattern is just ellipsis 50 - if !pattern.ellipsis() { 51 - return None; 52 - } 48 + pattern.ellipsis_token()?; 53 49 54 50 // pattern is bound 55 - let ident = pattern.at()?; 51 + let pat_bind = pattern.pat_bind()?; 52 + let ident = pat_bind.ident()?; 56 53 let at = node.text_range(); 57 - let message = format!( 58 - "This pattern bind is redundant, use `{}` instead", 59 - ident.as_str() 60 - ); 61 - let replacement = ident.node().clone(); 54 + let message = format!("This pattern bind is redundant, use `{ident}` instead"); 55 + let replacement = ident.syntax().clone(); 62 56 63 57 Some( 64 58 self.report()
+26 -22
lib/src/lints/repeated_keys.rs
··· 5 5 use macros::lint; 6 6 use rnix::{ 7 7 NodeOrToken, SyntaxElement, SyntaxKind, 8 - types::{AttrSet, EntryHolder, Ident, KeyValue, TokenWrapper, TypedNode}, 8 + ast::{Attr, AttrSet, AttrpathValue, Entry, HasEntry as _}, 9 9 }; 10 + use rowan::ast::AstNode as _; 10 11 11 12 /// ## What it does 12 13 /// Checks for keys in attribute sets with repetitive keys, and suggests using ··· 39 40 name = "repeated_keys", 40 41 note = "Avoid repeated keys in attribute sets", 41 42 code = 20, 42 - match_with = SyntaxKind::NODE_KEY_VALUE 43 + match_with = SyntaxKind::NODE_ATTRPATH_VALUE 43 44 )] 44 45 struct RepeatedKeys; 45 46 ··· 49 50 return None; 50 51 }; 51 52 52 - let key_value = KeyValue::cast(node.clone())?; 53 - let key = key_value.key()?; 54 - let mut components = key.path(); 53 + let attrpath_value = AttrpathValue::cast(node.clone())?; 54 + let attrpath = attrpath_value.attrpath()?; 55 + let mut components = attrpath.attrs(); 55 56 let first_component = components.next()?; 56 - let first_component_ident = Ident::cast(first_component)?; 57 + 58 + let Attr::Ident(first_component_ident) = first_component else { 59 + return None; 60 + }; 57 61 58 62 // ensure that there are >1 components 59 63 components.next()?; ··· 61 65 let parent_node = node.parent()?; 62 66 let parent_attr_set = AttrSet::cast(parent_node)?; 63 67 64 - if parent_attr_set.recursive() { 68 + if parent_attr_set.rec_token().is_some() { 65 69 return None; 66 70 } 67 71 68 72 let occurrences = parent_attr_set 69 73 .entries() 70 74 .filter_map(|kv_scrutinee| { 71 - let scrutinee_key = kv_scrutinee.key()?; 72 - let mut kv_scrutinee_components = scrutinee_key.path(); 75 + let Entry::AttrpathValue(kv_scrutinee) = kv_scrutinee else { 76 + return None; 77 + }; 78 + 79 + let scrutinee_key = kv_scrutinee.attrpath()?; 80 + let mut kv_scrutinee_components = scrutinee_key.attrs(); 73 81 let kv_scrutinee_first_component = kv_scrutinee_components.next()?; 74 - let kv_scrutinee_ident = Ident::cast(kv_scrutinee_first_component)?; 75 82 76 - if kv_scrutinee_ident.as_str() != first_component_ident.as_str() { 83 + let Attr::Ident(kv_scrutinee_ident) = kv_scrutinee_first_component else { 84 + return None; 85 + }; 86 + 87 + if kv_scrutinee_ident.to_string() != first_component_ident.to_string() { 77 88 return None; 78 89 } 79 90 80 91 Some(( 81 - kv_scrutinee.key()?.node().text_range(), 92 + scrutinee_key.syntax().text_range(), 82 93 kv_scrutinee_components 83 94 .map(|n| n.to_string()) 84 95 .collect::<Vec<_>>() ··· 87 98 }) 88 99 .collect::<Vec<_>>(); 89 100 90 - if occurrences.first()?.0 != key.node().text_range() { 101 + if occurrences.first()?.0 != attrpath.syntax().text_range() { 91 102 return None; 92 103 } 93 104 ··· 98 109 let mut iter = occurrences.into_iter(); 99 110 100 111 let (first_annotation, first_subkey) = iter.next().unwrap(); 101 - let first_message = format!( 102 - "The key `{}` is first assigned here ...", 103 - first_component_ident.as_str() 104 - ); 112 + let first_message = format!("The key `{first_component_ident}` is first assigned here ..."); 105 113 106 114 let (second_annotation, second_subkey) = iter.next().unwrap(); 107 115 let second_message = "... repeated here ..."; ··· 116 124 }; 117 125 write!( 118 126 message, 119 - " Try `{} = {{ {}=...; {}=...; {}=...; }}` instead.", 120 - first_component_ident.as_str(), 121 - first_subkey, 122 - second_subkey, 123 - third_subkey 127 + " Try `{first_component_ident} = {{ {first_subkey}=...; {second_subkey}=...; {third_subkey}=...; }}` instead." 124 128 ) 125 129 .unwrap(); 126 130 message
+9 -7
lib/src/lints/unquoted_uri.rs
··· 1 1 use crate::{Metadata, Report, Rule, Suggestion, make, session::SessionInfo}; 2 + use rowan::ast::AstNode as _; 2 3 3 4 use macros::lint; 4 - use rnix::{NodeOrToken, SyntaxElement, SyntaxKind, types::TypedNode}; 5 + use rnix::{NodeOrToken, SyntaxElement, SyntaxKind}; 5 6 6 7 /// ## What it does 7 8 /// Checks for URI expressions that are not quoted. ··· 50 51 return None; 51 52 }; 52 53 53 - let parent_node = token.parent(); 54 + let parent_node = token.parent()?; 54 55 let at = token.text_range(); 55 - let replacement = make::quote(&parent_node).node().clone(); 56 + let replacement = make::quote(&parent_node); 56 57 let message = "Consider quoting this URI expression"; 57 - Some( 58 - self.report() 59 - .suggest(at, message, Suggestion::with_replacement(at, replacement)), 60 - ) 58 + Some(self.report().suggest( 59 + at, 60 + message, 61 + Suggestion::with_replacement(at, replacement.syntax().clone()), 62 + )) 61 63 } 62 64 }
+27 -21
lib/src/lints/useless_has_attr.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{BinOp, BinOpKind, IfElse, Select, TypedNode}, 6 + ast::{Expr, IfElse}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for expressions that use the "has attribute" operator: `?`, ··· 40 41 let if_else_expr = IfElse::cast(node.clone())?; 41 42 let condition_expr = if_else_expr.condition()?; 42 43 let default_expr = if_else_expr.else_body()?; 43 - let cond_bin_expr = BinOp::cast(condition_expr)?; 44 - let Some(BinOpKind::IsSet) = cond_bin_expr.operator() else { 44 + 45 + let Expr::HasAttr(has_attr) = condition_expr else { 45 46 return None; 46 47 }; 47 48 48 49 // set ? attr_path 49 - // ^^^--------------- lhs 50 - // ^^^^^^^^^^--- rhs 51 - let set = cond_bin_expr.lhs()?; 52 - let attr_path = cond_bin_expr.rhs()?; 50 + let set = has_attr.expr()?; 51 + let attr_path = has_attr.attrpath()?; 53 52 54 53 // check if body of the `if` expression is of the form `set.attr_path` 55 54 let body_expr = if_else_expr.body()?; 56 - let body_select_expr = Select::cast(body_expr)?; 57 - let expected_body = make::select(&set, &attr_path); 55 + let Expr::Select(body_select_expr) = body_expr else { 56 + return None; 57 + }; 58 + 59 + let expected_body = make::select(set.syntax(), attr_path.syntax()); 58 60 59 61 // text comparison will do for now 60 - if body_select_expr.node().text() != expected_body.node().text() { 62 + if body_select_expr.syntax().text() != expected_body.syntax().text() { 61 63 return None; 62 64 } 63 65 64 66 let at = node.text_range(); 65 67 66 68 // `or` is tightly binding, we need to parenthesize non-literal exprs 67 - let default_with_parens = match default_expr.kind() { 68 - SyntaxKind::NODE_LIST 69 - | SyntaxKind::NODE_PAREN 70 - | SyntaxKind::NODE_STRING 71 - | SyntaxKind::NODE_ATTR_SET 72 - | SyntaxKind::NODE_IDENT 73 - | SyntaxKind::NODE_SELECT => default_expr, 74 - _ => make::parenthesize(&default_expr).node().clone(), 69 + let default_with_parens = match default_expr { 70 + Expr::List(_) 71 + | Expr::Paren(_) 72 + | Expr::Str(_) 73 + | Expr::AttrSet(_) 74 + | Expr::Ident(_) 75 + | Expr::Select(_) => default_expr, 76 + _ => Expr::Paren(make::parenthesize(default_expr.syntax())), 75 77 }; 76 78 77 - let replacement = make::or_default(&set, &attr_path, &default_with_parens) 78 - .node() 79 - .clone(); 79 + let replacement = make::or_default( 80 + set.syntax(), 81 + attr_path.syntax(), 82 + default_with_parens.syntax(), 83 + ) 84 + .syntax() 85 + .clone(); 80 86 let message = format!("Consider using `{replacement}` instead of this `if` expression"); 81 87 Some( 82 88 self.report()
+32 -28
lib/src/lints/useless_parens.rs
··· 3 3 use macros::lint; 4 4 use rnix::{ 5 5 NodeOrToken, SyntaxElement, SyntaxKind, 6 - types::{KeyValue, LetIn, Paren, ParsedType, TypedNode, Wrapper}, 6 + ast::{AttrpathValue, Expr, LetIn, Paren}, 7 7 }; 8 + use rowan::ast::AstNode as _; 8 9 9 10 /// ## What it does 10 11 /// Checks for unnecessary parentheses. ··· 36 37 note = "These parentheses can be omitted", 37 38 code = 8, 38 39 match_with = [ 39 - SyntaxKind::NODE_KEY_VALUE, 40 + SyntaxKind::NODE_ATTRPATH_VALUE, 40 41 SyntaxKind::NODE_PAREN, 41 42 SyntaxKind::NODE_LET_IN, 42 43 ] ··· 49 50 return None; 50 51 }; 51 52 52 - let parsed_type_node = ParsedType::cast(node.clone())?; 53 - 54 - let diagnostic = match parsed_type_node { 55 - ParsedType::KeyValue(kv) => { 56 - let value_node = kv.value()?; 57 - let value_range = value_node.text_range(); 53 + let diagnostic = match (AttrpathValue::cast(node.clone()), Expr::cast(node.clone())) { 54 + (Some(attrpath_value), _) => { 55 + let value_node = attrpath_value.value()?; 56 + let value_range = value_node.syntax().text_range(); 57 + let paren = Paren::cast(value_node.syntax().clone())?; 58 + let suggestion = 59 + Suggestion::with_replacement(value_range, paren.expr()?.syntax().clone()); 58 60 59 61 Diagnostic::suggest( 60 62 value_range, 61 63 "Useless parentheses around value in binding", 62 - Suggestion::with_replacement(value_range, Paren::cast(value_node)?.inner()?), 64 + suggestion, 63 65 ) 64 66 } 65 - ParsedType::LetIn(let_in) => { 67 + (_, Some(Expr::LetIn(let_in))) => { 66 68 let body_node = let_in.body()?; 67 - let body_range = body_node.text_range(); 69 + let body_range = body_node.syntax().text_range(); 70 + let paren = Paren::cast(body_node.syntax().clone())?; 71 + let suggestion = 72 + Suggestion::with_replacement(body_range, paren.expr()?.syntax().clone()); 73 + 68 74 Diagnostic::suggest( 69 75 body_range, 70 76 "Useless parentheses around body of `let` expression", 71 - Suggestion::with_replacement(body_range, Paren::cast(body_node)?.inner()?), 77 + suggestion, 72 78 ) 73 79 } 74 - ParsedType::Paren(paren_expr) => { 75 - let paren_expr_range = paren_expr.node().text_range(); 76 - let father_node = paren_expr.node().parent()?; 80 + (_, Some(Expr::Paren(paren_expr))) => { 81 + let paren_expr_range = paren_expr.syntax().text_range(); 82 + let father_node = paren_expr.syntax().parent()?; 77 83 78 84 // ensure that we don't lint inside let-in statements 79 85 // we already lint such cases in previous match stmt 80 - if KeyValue::cast(father_node.clone()).is_some() { 86 + if AttrpathValue::cast(father_node.clone()).is_some() { 81 87 return None; 82 88 } 83 89 ··· 87 93 return None; 88 94 } 89 95 90 - let parsed_inner = ParsedType::cast(paren_expr.inner()?)?; 96 + let parsed_inner = Expr::cast(paren_expr.expr()?.syntax().clone())?; 91 97 92 - if !matches!( 93 - parsed_inner, 94 - ParsedType::List(_) 95 - | ParsedType::Paren(_) 96 - | ParsedType::Str(_) 97 - | ParsedType::AttrSet(_) 98 - | ParsedType::Select(_) 99 - | ParsedType::Ident(_) 100 - ) { 101 - return None; 98 + match &parsed_inner { 99 + Expr::List(_) 100 + | Expr::Paren(_) 101 + | Expr::Str(_) 102 + | Expr::AttrSet(_) 103 + | Expr::Ident(_) => {} 104 + Expr::Select(select) if select.or_token().is_none() => {} 105 + _ => return None, 102 106 } 103 107 104 108 Diagnostic::suggest( 105 109 paren_expr_range, 106 110 "Useless parentheses around primitive expression", 107 - Suggestion::with_replacement(paren_expr_range, parsed_inner.node().clone()), 111 + Suggestion::with_replacement(paren_expr_range, parsed_inner.syntax().clone()), 108 112 ) 109 113 } 110 114 _ => return None,
+27 -22
lib/src/make.rs
··· 1 1 use std::{fmt::Write, iter::IntoIterator}; 2 2 3 3 use rnix::{ 4 - SyntaxNode, 5 - types::{self, TokenWrapper, TypedNode}, 4 + Root, SyntaxNode, 5 + ast::{self, AstNode}, 6 6 }; 7 + use rowan::ast::AstNode as _; 7 8 8 - fn ast_from_text<N: TypedNode>(text: &str) -> N { 9 - let parse = rnix::parse(text); 9 + fn ast_from_text<N: AstNode>(text: &str) -> N { 10 + let parse = Root::parse(text).ok(); 11 + 12 + let Ok(parse) = parse else { 13 + panic!("Failed to parse `{text:?}`") 14 + }; 10 15 11 - let Some(node) = parse.node().descendants().find_map(N::cast) else { 16 + let Some(node) = parse.syntax().descendants().find_map(N::cast) else { 12 17 panic!( 13 18 "Failed to make ast node `{}` from text `{}`", 14 19 std::any::type_name::<N>(), ··· 19 24 node 20 25 } 21 26 22 - pub fn parenthesize(node: &SyntaxNode) -> types::Paren { 27 + pub fn parenthesize(node: &SyntaxNode) -> ast::Paren { 23 28 ast_from_text(&format!("({node})")) 24 29 } 25 30 26 - pub fn quote(node: &SyntaxNode) -> types::Str { 31 + pub fn quote(node: &SyntaxNode) -> ast::Str { 27 32 ast_from_text(&format!("\"{node}\"")) 28 33 } 29 34 30 - pub fn unary_not(node: &SyntaxNode) -> types::UnaryOp { 35 + pub fn unary_not(node: &SyntaxNode) -> ast::UnaryOp { 31 36 ast_from_text(&format!("!{node}")) 32 37 } 33 38 34 - pub fn inherit_stmt<'a>(nodes: impl IntoIterator<Item = &'a types::Ident>) -> types::Inherit { 39 + pub fn inherit_stmt<'a>(nodes: impl IntoIterator<Item = &'a ast::Ident>) -> ast::Inherit { 35 40 let inherited_idents = nodes 36 41 .into_iter() 37 - .map(|i| i.as_str().to_owned()) 42 + .map(std::string::ToString::to_string) 38 43 .collect::<Vec<_>>() 39 44 .join(" "); 40 45 ast_from_text(&format!("{{ inherit {inherited_idents}; }}")) ··· 42 47 43 48 pub fn inherit_from_stmt<'a>( 44 49 from: &SyntaxNode, 45 - nodes: impl IntoIterator<Item = &'a types::Ident>, 46 - ) -> types::Inherit { 50 + nodes: impl IntoIterator<Item = &'a ast::Ident>, 51 + ) -> ast::Inherit { 47 52 let inherited_idents = nodes 48 53 .into_iter() 49 - .map(|i| i.as_str().to_owned()) 54 + .map(std::string::ToString::to_string) 50 55 .collect::<Vec<_>>() 51 56 .join(" "); 52 57 ast_from_text(&format!("{{ inherit ({from}) {inherited_idents}; }}")) 53 58 } 54 59 55 60 pub fn attrset( 56 - inherits: impl IntoIterator<Item = types::Inherit>, 57 - entries: impl IntoIterator<Item = types::KeyValue>, 61 + inherits: impl IntoIterator<Item = ast::Inherit>, 62 + entries: impl IntoIterator<Item = ast::Entry>, 58 63 recursive: bool, 59 - ) -> types::AttrSet { 64 + ) -> ast::AttrSet { 60 65 let mut buffer = String::new(); 61 66 62 67 writeln!(buffer, "{}{{", if recursive { "rec " } else { "" }).unwrap(); 63 68 for inherit in inherits { 64 - writeln!(buffer, " {}", inherit.node().text()).unwrap(); 69 + writeln!(buffer, " {inherit}").unwrap(); 65 70 } 66 71 for entry in entries { 67 - writeln!(buffer, " {}", entry.node().text()).unwrap(); 72 + writeln!(buffer, " {entry}").unwrap(); 68 73 } 69 74 write!(buffer, "}}").unwrap(); 70 75 71 76 ast_from_text(&buffer) 72 77 } 73 78 74 - pub fn select(set: &SyntaxNode, index: &SyntaxNode) -> types::Select { 79 + pub fn select(set: &SyntaxNode, index: &SyntaxNode) -> ast::Select { 75 80 ast_from_text(&format!("{set}.{index}")) 76 81 } 77 82 78 - pub fn ident(text: &str) -> types::Ident { 83 + pub fn ident(text: &str) -> ast::Ident { 79 84 ast_from_text(text) 80 85 } 81 86 82 87 // LATER: make `op` strongly typed here 83 - pub fn binary(lhs: &SyntaxNode, op: &str, rhs: &SyntaxNode) -> types::BinOp { 88 + pub fn binary(lhs: &SyntaxNode, op: &str, rhs: &SyntaxNode) -> ast::BinOp { 84 89 ast_from_text(&format!("{lhs} {op} {rhs}")) 85 90 } 86 91 87 - pub fn or_default(set: &SyntaxNode, index: &SyntaxNode, default: &SyntaxNode) -> types::OrDefault { 92 + pub fn or_default(set: &SyntaxNode, index: &SyntaxNode, default: &SyntaxNode) -> ast::Select { 88 93 ast_from_text(&format!("{set}.{index} or {default}")) 89 94 }