Lints and suggestions for the Nix programming language
1
fork

Configure Feed

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

allow for version based lints

Akshay d1ff222b 94a2edf5

+371 -81
+27 -3
bin/src/fix.rs
··· 2 2 3 3 use crate::LintMap; 4 4 5 + use lib::session::SessionInfo; 5 6 use rnix::TextRange; 6 7 7 8 mod all; ··· 16 17 pub src: Source<'a>, 17 18 pub fixed: Vec<Fixed>, 18 19 pub lints: &'a LintMap, 20 + pub sess: &'a SessionInfo, 19 21 } 20 22 21 23 #[derive(Debug, Clone)] ··· 25 27 } 26 28 27 29 impl<'a> FixResult<'a> { 28 - fn empty(src: Source<'a>, lints: &'a LintMap) -> Self { 30 + fn empty(src: Source<'a>, lints: &'a LintMap, sess: &'a SessionInfo) -> Self { 29 31 Self { 30 32 src, 31 33 fixed: Vec::new(), 32 34 lints, 35 + sess, 33 36 } 34 37 } 35 38 } ··· 40 43 use crate::{ 41 44 config::{Fix as FixConfig, FixOut, Single as SingleConfig}, 42 45 err::{FixErr, StatixErr}, 46 + utils, 43 47 }; 44 48 49 + use lib::session::{SessionInfo, Version}; 45 50 use similar::TextDiff; 46 51 47 52 pub fn all(fix_config: FixConfig) -> Result<(), StatixErr> { 48 53 let vfs = fix_config.vfs()?; 49 54 let lints = fix_config.lints()?; 55 + 56 + let version = utils::get_version_info() 57 + .unwrap() 58 + .parse::<Version>() 59 + .unwrap(); 60 + let session = SessionInfo::from_version(version); 61 + 50 62 for entry in vfs.iter() { 51 - match (fix_config.out(), super::all_with(entry.contents, &lints)) { 63 + match ( 64 + fix_config.out(), 65 + super::all_with(entry.contents, &lints, &session), 66 + ) { 52 67 (FixOut::Diff, fix_result) => { 53 68 let src = fix_result 54 69 .map(|r| r.src) ··· 87 102 let original_src = entry.contents; 88 103 let (line, col) = single_config.position; 89 104 90 - match (single_config.out(), super::single(line, col, original_src)) { 105 + let version = utils::get_version_info() 106 + .unwrap() 107 + .parse::<Version>() 108 + .unwrap(); 109 + let session = SessionInfo::from_version(version); 110 + 111 + match ( 112 + single_config.out(), 113 + super::single(line, col, original_src, &session), 114 + ) { 91 115 (FixOut::Diff, single_result) => { 92 116 let fixed_src = single_result 93 117 .map(|r| r.src)
+15 -6
bin/src/fix/all.rs
··· 1 1 use std::borrow::Cow; 2 2 3 - use lib::Report; 3 + use lib::{session::SessionInfo, Report}; 4 4 use rnix::{parser::ParseError as RnixParseErr, WalkEvent}; 5 5 6 6 use crate::{ ··· 8 8 LintMap, 9 9 }; 10 10 11 - fn collect_fixes(source: &str, lints: &LintMap) -> Result<Vec<Report>, RnixParseErr> { 11 + fn collect_fixes( 12 + source: &str, 13 + lints: &LintMap, 14 + sess: &SessionInfo, 15 + ) -> Result<Vec<Report>, RnixParseErr> { 12 16 let parsed = rnix::parse(source).as_result()?; 13 17 14 18 Ok(parsed ··· 18 22 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { 19 23 rules 20 24 .iter() 21 - .filter_map(|rule| rule.validate(&child)) 25 + .filter_map(|rule| rule.validate(&child, sess)) 22 26 .filter(|report| report.total_suggestion_range().is_some()) 23 27 .collect::<Vec<_>>() 24 28 }), ··· 57 61 impl<'a> Iterator for FixResult<'a> { 58 62 type Item = FixResult<'a>; 59 63 fn next(&mut self) -> Option<Self::Item> { 60 - let all_reports = collect_fixes(&self.src, self.lints).ok()?; 64 + let all_reports = collect_fixes(&self.src, self.lints, &self.sess).ok()?; 61 65 if all_reports.is_empty() { 62 66 return None; 63 67 } ··· 78 82 src: self.src.clone(), 79 83 fixed, 80 84 lints: self.lints, 85 + sess: self.sess, 81 86 }) 82 87 } 83 88 } 84 89 85 - pub fn all_with<'a>(src: &'a str, lints: &'a LintMap) -> Option<FixResult<'a>> { 90 + pub fn all_with<'a>( 91 + src: &'a str, 92 + lints: &'a LintMap, 93 + sess: &'a SessionInfo, 94 + ) -> Option<FixResult<'a>> { 86 95 let src = Cow::from(src); 87 96 let _ = rnix::parse(&src).as_result().ok()?; 88 - let initial = FixResult::empty(src, lints); 97 + let initial = FixResult::empty(src, lints, sess); 89 98 initial.into_iter().last() 90 99 }
+10 -5
bin/src/fix/single.rs
··· 1 1 use std::{borrow::Cow, convert::TryFrom}; 2 2 3 - use lib::Report; 3 + use lib::{session::SessionInfo, Report}; 4 4 use rnix::{TextSize, WalkEvent}; 5 5 6 6 use crate::{err::SingleFixErr, fix::Source, utils}; ··· 27 27 } 28 28 } 29 29 30 - fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> { 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 32 let parsed = rnix::parse(src); 33 33 let lints = utils::lint_map(); ··· 39 39 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { 40 40 rules 41 41 .iter() 42 - .filter_map(|rule| rule.validate(&child)) 42 + .filter_map(|rule| rule.validate(&child, sess)) 43 43 .find(|report| report.total_suggestion_range().is_some()) 44 44 }), 45 45 _ => None, ··· 49 49 .ok_or(SingleFixErr::NoOp) 50 50 } 51 51 52 - pub fn single(line: usize, col: usize, src: &str) -> Result<SingleFixResult, SingleFixErr> { 52 + pub fn single<'a, 'b>( 53 + line: usize, 54 + col: usize, 55 + src: &'a str, 56 + sess: &'b SessionInfo, 57 + ) -> Result<SingleFixResult<'a>, SingleFixErr> { 53 58 let mut src = Cow::from(src); 54 59 let offset = pos_to_byte(line, col, &*src)?; 55 - let report = find(offset, &*src)?; 60 + let report = find(offset, &*src, &sess)?; 56 61 57 62 report.apply(src.to_mut()); 58 63
+1
bin/src/lib.rs
··· 4 4 pub mod explain; 5 5 pub mod fix; 6 6 pub mod lint; 7 + pub mod session; 7 8 pub mod traits; 8 9 9 10 mod utils;
+16 -7
bin/src/lint.rs
··· 1 1 use crate::{utils, LintMap}; 2 2 3 - use lib::Report; 3 + use lib::{session::SessionInfo, Report}; 4 4 use rnix::WalkEvent; 5 5 use vfs::{FileId, VfsEntry}; 6 6 ··· 10 10 pub reports: Vec<Report>, 11 11 } 12 12 13 - pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap) -> LintResult { 13 + pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap, sess: &SessionInfo) -> LintResult { 14 14 let file_id = vfs_entry.file_id; 15 15 let source = vfs_entry.contents; 16 16 let parsed = rnix::parse(source); ··· 23 23 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { 24 24 rules 25 25 .iter() 26 - .filter_map(|rule| rule.validate(&child)) 26 + .filter_map(|rule| rule.validate(&child, sess)) 27 27 .collect::<Vec<_>>() 28 28 }), 29 29 _ => None, ··· 35 35 LintResult { file_id, reports } 36 36 } 37 37 38 - pub fn lint(vfs_entry: VfsEntry) -> LintResult { 39 - lint_with(vfs_entry, &utils::lint_map()) 38 + pub fn lint(vfs_entry: VfsEntry, sess: &SessionInfo) -> LintResult { 39 + lint_with(vfs_entry, &utils::lint_map(), &sess) 40 40 } 41 41 42 42 pub mod main { 43 43 use std::io; 44 44 45 45 use super::lint_with; 46 - use crate::{config::Check as CheckConfig, err::StatixErr, traits::WriteDiagnostic}; 46 + use crate::{config::Check as CheckConfig, err::StatixErr, traits::WriteDiagnostic, utils}; 47 + 48 + use lib::session::{SessionInfo, Version}; 47 49 48 50 pub fn main(check_config: CheckConfig) -> Result<(), StatixErr> { 49 51 let vfs = check_config.vfs()?; 50 52 let mut stdout = io::stdout(); 51 53 let lints = check_config.lints()?; 52 - let lint = |vfs_entry| lint_with(vfs_entry, &lints); 54 + 55 + let version = utils::get_version_info() 56 + .unwrap() 57 + .parse::<Version>() 58 + .unwrap(); 59 + let session = SessionInfo::from_version(version); 60 + 61 + let lint = |vfs_entry| lint_with(vfs_entry, &lints, &session); 53 62 vfs.iter().map(lint).for_each(|r| { 54 63 stdout.write(&r, &vfs, check_config.format).unwrap(); 55 64 });
bin/src/session.rs

This is a binary file and will not be displayed.

+11
bin/src/utils.rs
··· 22 22 pub fn lint_map() -> HashMap<SyntaxKind, Vec<&'static Box<dyn Lint>>> { 23 23 lint_map_of(&*LINTS) 24 24 } 25 + 26 + pub fn get_version_info() -> Option<String> { 27 + use std::process::Command; 28 + let program = Command::new("nix") 29 + .arg("--version") 30 + .output() 31 + .expect("failed to execute"); 32 + std::str::from_utf8(&program.stdout) 33 + .ok() 34 + .map(ToOwned::to_owned) 35 + }
+15
bin/tests/data/faster_groupby.nix
··· 1 + { 2 + # trivial case 3 + _ = lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 ]; 4 + 5 + # offer lint heuristically on this too 6 + _ = nixpkgs.lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 ]; 7 + 8 + # do not lint on `builtins` 9 + _ = builtins.groupBy (x: x.name) [ 10 + { name = "foo"; idx = 1; } 11 + { name = "foo"; idx = 2; } 12 + { name = "bar"; idx = 1; } 13 + { name = "bar"; idx = 2; } 14 + ]; 15 + }
+38 -20
bin/tests/main.rs
··· 1 + use lib::session::{SessionInfo, Version}; 2 + 3 + macro_rules! session_info { 4 + ($version:expr) => {{ 5 + let v: Version = $version.parse().unwrap(); 6 + SessionInfo::from_version(v) 7 + }}; 8 + } 9 + 1 10 mod util { 2 11 #[macro_export] 3 12 macro_rules! test_lint { 4 - ($($tname:ident),*,) => { 5 - test_lint!($($tname),*); 13 + ($tname:ident => $sess:expr, $($tail:tt)*) => { 14 + test_lint!($tname => $sess); 15 + test_lint!($($tail)*); 16 + }; 17 + ($tname:ident, $($tail:tt)*) => { 18 + test_lint!($tname); 19 + test_lint!($($tail)*); 6 20 }; 7 - ($($tname:ident),*) => { 8 - $( 9 - #[test] 10 - fn $tname() { 11 - use statix::{config::OutFormat, traits::WriteDiagnostic, lint}; 12 - use vfs::ReadOnlyVfs; 21 + ($tname:ident) => { 22 + test_lint!($tname => session_info!("nix (Nix) 2.5")); 23 + }; 24 + ($tname:ident => $sess:expr) => { 25 + #[test] 26 + fn $tname() { 27 + use statix::{config::OutFormat, traits::WriteDiagnostic, lint}; 28 + use vfs::ReadOnlyVfs; 13 29 14 - let file_path = concat!("data/", stringify!($tname), ".nix"); 15 - let contents = include_str!(concat!("data/", stringify!($tname), ".nix")); 30 + let file_path = concat!("data/", stringify!($tname), ".nix"); 31 + let contents = include_str!(concat!("data/", stringify!($tname), ".nix")); 32 + 33 + let vfs = ReadOnlyVfs::singleton(file_path, contents.as_bytes()); 16 34 17 - let vfs = ReadOnlyVfs::singleton(file_path, contents.as_bytes()); 35 + let session = $sess; 18 36 19 - let mut buffer = Vec::new(); 20 - vfs.iter().map(lint::lint).for_each(|r| { 21 - buffer.write(&r, &vfs, OutFormat::StdErr).unwrap(); 22 - }); 37 + let mut buffer = Vec::new(); 38 + vfs.iter().map(|entry| lint::lint(entry, &session)).for_each(|r| { 39 + buffer.write(&r, &vfs, OutFormat::StdErr).unwrap(); 40 + }); 23 41 24 - let stripped = strip_ansi_escapes::strip(&buffer).unwrap(); 25 - let out = std::str::from_utf8(&stripped).unwrap(); 26 - insta::assert_snapshot!(&out); 27 - } 28 - )* 42 + let stripped = strip_ansi_escapes::strip(&buffer).unwrap(); 43 + let out = std::str::from_utf8(&stripped).unwrap(); 44 + insta::assert_snapshot!(&out); 45 + } 29 46 }; 30 47 } 31 48 } ··· 44 61 unquoted_uri, 45 62 deprecated_is_null, 46 63 empty_inherit, 64 + faster_groupby => session_info!("nix (Nix) 2.5") 47 65 }
+20
bin/tests/snapshots/main__faster_groupby.snap
··· 1 + --- 2 + source: bin/tests/main.rs 3 + expression: "&out" 4 + 5 + --- 6 + [W15] Warning: Found lib.groupBy 7 + ╭─[data/faster_groupby.nix:3:7] 8 + 9 + 3 │ _ = lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 ]; 10 + · ─────┬───── 11 + · ╰─────── Prefer builtins.groupBy over lib.groupBy 12 + ───╯ 13 + [W15] Warning: Found lib.groupBy 14 + ╭─[data/faster_groupby.nix:6:7] 15 + 16 + 6 │ _ = nixpkgs.lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 ]; 17 + · ─────────┬───────── 18 + · ╰─────────── Prefer builtins.groupBy over nixpkgs.lib.groupBy 19 + ───╯ 20 +
+9 -9
flake.lock
··· 8 8 "rust-analyzer-src": "rust-analyzer-src" 9 9 }, 10 10 "locked": { 11 - "lastModified": 1638080655, 12 - "narHash": "sha256-ZPx8e8CukEBx31IcgivAWnN9Jg0r+LTBPHV7fREf+QI=", 11 + "lastModified": 1640413491, 12 + "narHash": "sha256-3VdQNPd9k4Fcjv/JBlwtJZWXg4fhrqTrEoSqhEOEWaU=", 13 13 "owner": "nix-community", 14 14 "repo": "fenix", 15 - "rev": "78a0c55b6f9d8bb6f3b89eb995fa5bbdd73e9475", 15 + "rev": "730771574878f1fd5c31b4d7b1595df389f01ea3", 16 16 "type": "github" 17 17 }, 18 18 "original": { ··· 43 43 }, 44 44 "nixpkgs": { 45 45 "locked": { 46 - "lastModified": 1638036523, 47 - "narHash": "sha256-ZL6gogsuBmhBvIro+YwRKrypYhwVPCOOO7FmhOV/xyE=", 46 + "lastModified": 1640418986, 47 + "narHash": "sha256-a8GGtxn2iL3WAkY5H+4E0s3Q7XJt6bTOvos9qqxT5OQ=", 48 48 "owner": "nixos", 49 49 "repo": "nixpkgs", 50 - "rev": "9c191ebcdfe917043195c54ab6ae8e934434fe7b", 50 + "rev": "5c37ad87222cfc1ec36d6cd1364514a9efc2f7f2", 51 51 "type": "github" 52 52 }, 53 53 "original": { ··· 67 67 "rust-analyzer-src": { 68 68 "flake": false, 69 69 "locked": { 70 - "lastModified": 1638036899, 71 - "narHash": "sha256-vh7z8jupVxXPOko3sWUsOB7eji/7lKfwJ/CE3iw97Sw=", 70 + "lastModified": 1640270361, 71 + "narHash": "sha256-g0canOfW6Iu/wSy0XUctgmlHJahfh5NqwDYddcRHXJQ=", 72 72 "owner": "rust-analyzer", 73 73 "repo": "rust-analyzer", 74 - "rev": "d9b2291f546abc77d24499339a72a89127464b95", 74 + "rev": "7b7a1ed062edd438caa824b6a71aaaa56b48e7d4", 75 75 "type": "github" 76 76 }, 77 77 "original": {
+2 -2
flake.nix
··· 33 33 }); 34 34 35 35 chanspec = { 36 - date = "2021-11-01"; 36 + date = "2021-12-01"; 37 37 channel = "nightly"; 38 - sha256 = "2BmxGawDNjXHJvnQToxmErMGgEPOfVzUvxhkvuixHYU="; # set zeros after modifying channel or date 38 + sha256 = "DhIP1w63/hMbWlgElJGBumEK/ExFWCdLaeBV5F8uWHc="; # set zeros after modifying channel or date 39 39 }; 40 40 rustChannel = p: (fenix.overlay p p).fenix.toolchainOf chanspec; 41 41
+3 -1
lib/src/lib.rs
··· 1 1 #![recursion_limit = "1024"] 2 2 mod lints; 3 3 mod make; 4 + pub mod session; 4 5 mod utils; 5 6 6 7 pub use lints::LINTS; 8 + use session::SessionInfo; 7 9 8 10 use rnix::{parser::ParseError, SyntaxElement, SyntaxKind, TextRange}; 9 11 use std::{convert::Into, default::Default}; ··· 221 223 /// Lint logic is defined via this trait. Do not implement manually, 222 224 /// look at the `lint` attribute macro instead for implementing rules 223 225 pub trait Rule { 224 - fn validate(&self, node: &SyntaxElement) -> Option<Report>; 226 + fn validate(&self, node: &SyntaxElement, sess: &SessionInfo) -> Option<Report>; 225 227 } 226 228 227 229 /// Contains information about the lint itself. Do not implement manually,
+1
lib/src/lints.rs
··· 15 15 unquoted_uri, 16 16 deprecated_is_null, 17 17 empty_inherit, 18 + faster_groupby, 18 19 }
+2 -2
lib/src/lints/bool_comparison.rs
··· 1 - use crate::{make, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 35 35 struct BoolComparison; 36 36 37 37 impl Rule for BoolComparison { 38 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 38 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 39 39 if_chain! { 40 40 if let NodeOrToken::Node(node) = node; 41 41 if let Some(bin_expr) = BinOp::cast(node.clone());
+2 -2
lib/src/lints/collapsible_let_in.rs
··· 1 - use crate::{make, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 45 45 struct CollapsibleLetIn; 46 46 47 47 impl Rule for CollapsibleLetIn { 48 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 48 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 49 49 if_chain! { 50 50 if let NodeOrToken::Node(node) = node; 51 51 if let Some(let_in_expr) = LetIn::cast(node.clone());
+2 -2
lib/src/lints/deprecated_is_null.rs
··· 1 - use crate::{make, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 35 35 struct DeprecatedIsNull; 36 36 37 37 impl Rule for DeprecatedIsNull { 38 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 38 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 39 39 if_chain! { 40 40 if let NodeOrToken::Node(node) = node; 41 41 if let Some(apply) = Apply::cast(node.clone());
+2 -2
lib/src/lints/empty_inherit.rs
··· 1 - use crate::{make, utils, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, utils, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 29 29 struct EmptyInherit; 30 30 31 31 impl Rule for EmptyInherit { 32 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 32 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 33 33 if_chain! { 34 34 if let NodeOrToken::Node(node) = node; 35 35 if let Some(inherit_stmt) = Inherit::cast(node.clone());
+2 -2
lib/src/lints/empty_let_in.rs
··· 1 - use crate::{Metadata, Report, Rule, Suggestion}; 1 + use crate::{session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 34 34 struct EmptyLetIn; 35 35 36 36 impl Rule for EmptyLetIn { 37 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 37 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 38 38 if_chain! { 39 39 if let NodeOrToken::Node(node) = node; 40 40 if let Some(let_in_expr) = LetIn::cast(node.clone());
+2 -2
lib/src/lints/empty_pattern.rs
··· 1 - use crate::{make, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 43 43 struct EmptyPattern; 44 44 45 45 impl Rule for EmptyPattern { 46 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 46 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 47 47 if_chain! { 48 48 if let NodeOrToken::Node(node) = node; 49 49 if let Some(pattern) = Pattern::cast(node.clone());
+2 -2
lib/src/lints/eta_reduction.rs
··· 1 - use crate::{Metadata, Report, Rule, Suggestion}; 1 + use crate::{session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 42 42 struct EtaReduction; 43 43 44 44 impl Rule for EtaReduction { 45 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 45 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 46 46 if_chain! { 47 47 if let NodeOrToken::Node(node) = node; 48 48 if let Some(lambda_expr) = Lambda::cast(node.clone());
+72
lib/src/lints/faster_groupby.rs
··· 1 + use crate::{ 2 + make, 3 + session::{SessionInfo, Version}, 4 + Metadata, Report, Rule, Suggestion, 5 + }; 6 + 7 + use if_chain::if_chain; 8 + use macros::lint; 9 + use rnix::{ 10 + types::{Select, TypedNode}, 11 + NodeOrToken, SyntaxElement, SyntaxKind, 12 + }; 13 + 14 + /// ## What it does 15 + /// Checks for `lib.groupBy`. 16 + /// 17 + /// ## Why is this bad? 18 + /// Nix 2.5 introduces `builtins.groupBy` which is faster and does 19 + /// not require a lib import. 20 + /// 21 + /// ## Example 22 + /// 23 + /// ```nix 24 + /// lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 6 ]; 25 + /// # { big = [ 3 4 5 6 ]; small = [ 1 2 ]; } 26 + /// ``` 27 + /// 28 + /// Replace `lib.groupBy` with `builtins.groupBy`: 29 + /// 30 + /// ``` 31 + /// builtins.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 6 ]; 32 + /// ``` 33 + #[lint( 34 + name = "faster_groupby", 35 + note = "Found lib.groupBy", 36 + code = 15, 37 + match_with = SyntaxKind::NODE_SELECT 38 + )] 39 + struct FasterGroupBy; 40 + 41 + impl Rule for FasterGroupBy { 42 + fn validate(&self, node: &SyntaxElement, sess: &SessionInfo) -> Option<Report> { 43 + let lint_version = "nix (Nix) 2.5".parse::<Version>().unwrap(); 44 + if_chain! { 45 + if sess.version() >= &lint_version; 46 + if let NodeOrToken::Node(node) = node; 47 + if let Some(select_expr) = Select::cast(node.clone()); 48 + if let Some(select_from) = select_expr.set(); 49 + if let Some(group_by_attr) = select_expr.index(); 50 + 51 + // a heuristic to lint on nixpkgs.lib.groupBy 52 + // and lib.groupBy and its variants 53 + if select_from.text().to_string() != "builtins"; 54 + if group_by_attr.text().to_string() == "groupBy"; 55 + 56 + then { 57 + let at = node.text_range(); 58 + let replacement = { 59 + let builtins = make::ident("builtins"); 60 + make::select(builtins.node(), &group_by_attr).node().clone() 61 + }; 62 + let message = format!("Prefer `builtins.groupBy` over `{}.groupBy`", select_from); 63 + Some( 64 + self.report() 65 + .suggest(at, message, Suggestion::new(at, replacement)), 66 + ) 67 + } else { 68 + None 69 + } 70 + } 71 + } 72 + }
+2 -2
lib/src/lints/legacy_let_syntax.rs
··· 1 - use crate::{make, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 44 44 struct ManualInherit; 45 45 46 46 impl Rule for ManualInherit { 47 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 47 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 48 48 if_chain! { 49 49 if let NodeOrToken::Node(node) = node; 50 50 if let Some(legacy_let) = LegacyLet::cast(node.clone());
+2 -2
lib/src/lints/manual_inherit.rs
··· 1 - use crate::{make, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 40 40 struct ManualInherit; 41 41 42 42 impl Rule for ManualInherit { 43 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 43 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 44 44 if_chain! { 45 45 if let NodeOrToken::Node(node) = node; 46 46 if let Some(key_value_stmt) = KeyValue::cast(node.clone());
+2 -2
lib/src/lints/manual_inherit_from.rs
··· 1 - use crate::{make, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 40 40 struct ManualInherit; 41 41 42 42 impl Rule for ManualInherit { 43 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 43 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 44 44 if_chain! { 45 45 if let NodeOrToken::Node(node) = node; 46 46 if let Some(key_value_stmt) = KeyValue::cast(node.clone());
+2 -2
lib/src/lints/redundant_pattern_bind.rs
··· 1 - use crate::{Metadata, Report, Rule, Suggestion}; 1 + use crate::{session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 35 35 struct RedundantPatternBind; 36 36 37 37 impl Rule for RedundantPatternBind { 38 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 38 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 39 39 if_chain! { 40 40 if let NodeOrToken::Node(node) = node; 41 41 if let Some(pattern) = Pattern::cast(node.clone());
+2 -2
lib/src/lints/unquoted_splice.rs
··· 1 - use crate::{make, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 40 40 struct UnquotedSplice; 41 41 42 42 impl Rule for UnquotedSplice { 43 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 43 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 44 44 if_chain! { 45 45 if let NodeOrToken::Node(node) = node; 46 46 if Dynamic::cast(node.clone()).is_some();
+2 -2
lib/src/lints/unquoted_uri.rs
··· 1 - use crate::{make, Metadata, Report, Rule, Suggestion}; 1 + use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 46 46 struct UnquotedUri; 47 47 48 48 impl Rule for UnquotedUri { 49 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 49 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 50 50 if_chain! { 51 51 if let NodeOrToken::Token(token) = node; 52 52 then {
+2 -2
lib/src/lints/useless_parens.rs
··· 1 - use crate::{Diagnostic, Metadata, Report, Rule, Suggestion}; 1 + use crate::{session::SessionInfo, Diagnostic, Metadata, Report, Rule, Suggestion}; 2 2 3 3 use if_chain::if_chain; 4 4 use macros::lint; ··· 45 45 struct UselessParens; 46 46 47 47 impl Rule for UselessParens { 48 - fn validate(&self, node: &SyntaxElement) -> Option<Report> { 48 + fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> { 49 49 if_chain! { 50 50 if let NodeOrToken::Node(node) = node; 51 51 if let Some(parsed_type_node) = ParsedType::cast(node.clone());
+103
lib/src/session.rs
··· 1 + use std::{cmp::Ordering, str::FromStr}; 2 + 3 + #[derive(Copy, Clone, Debug, Eq, PartialEq)] 4 + pub struct Version { 5 + major: u16, 6 + minor: u16, 7 + patch: Option<u16>, 8 + } 9 + 10 + impl Ord for Version { 11 + fn cmp(&self, other: &Self) -> Ordering { 12 + let score = |v: &Version| v.major * 100 + v.minor * 10 + v.patch.unwrap_or(0); 13 + score(self).cmp(&score(other)) 14 + } 15 + } 16 + 17 + impl PartialOrd for Version { 18 + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 19 + Some(self.cmp(other)) 20 + } 21 + } 22 + 23 + fn parse_number(s: &str) -> Option<u16> { 24 + s.chars() 25 + .take_while(|c| c.is_digit(10)) 26 + .collect::<String>() 27 + .parse::<u16>() 28 + .ok() 29 + } 30 + 31 + fn parse_version(s: &str) -> Option<Version> { 32 + match s.split(' ').collect::<Vec<_>>().as_slice() { 33 + [_, _, version] => { 34 + let mut parts = version.split('.'); 35 + let major = parse_number(parts.next()?)?; 36 + let minor = parse_number(parts.next()?)?; 37 + let patch = parts.next().map(|p| parse_number(p)).flatten(); 38 + Some(Version { 39 + major, 40 + minor, 41 + patch, 42 + }) 43 + } 44 + _ => None, 45 + } 46 + } 47 + 48 + impl FromStr for Version { 49 + type Err = (); 50 + fn from_str(s: &str) -> Result<Self, Self::Err> { 51 + parse_version(s).ok_or(()) 52 + } 53 + } 54 + 55 + #[non_exhaustive] 56 + pub struct SessionInfo { 57 + nix_version: Version, 58 + } 59 + 60 + impl SessionInfo { 61 + pub fn from_version(nix_version: Version) -> Self { 62 + Self { nix_version } 63 + } 64 + 65 + pub fn version(&self) -> &Version { 66 + &self.nix_version 67 + } 68 + } 69 + 70 + pub fn get_nix_version() -> Option<Version> { 71 + "nix (Nix) 2.4pre20211006_53e4794".parse::<Version>().ok() 72 + } 73 + 74 + #[cfg(test)] 75 + mod tests { 76 + use super::*; 77 + 78 + #[test] 79 + fn parse_trivial() { 80 + let v = "nix (Nix) 1.6.1".parse::<Version>().ok(); 81 + assert!(v.is_some()) 82 + } 83 + 84 + #[test] 85 + fn parse() { 86 + let v = "nix (Nix) 2.4pre20211006_53e4794".parse::<Version>().ok(); 87 + assert!(v.is_some()) 88 + } 89 + 90 + #[test] 91 + fn compare_trivial() { 92 + let v1 = "nix (Nix) 1.6.1".parse::<Version>().ok(); 93 + let v2 = "nix (Nix) 1.7.2".parse::<Version>().ok(); 94 + assert!(v2 > v1); 95 + } 96 + 97 + #[test] 98 + fn compare() { 99 + let v1 = "nix (Nix) 1.7".parse::<Version>().ok(); 100 + let v2 = "nix (Nix) 2.4pre20211006_53e4794".parse::<Version>().ok(); 101 + assert!(v2 >= v1); 102 + } 103 + }