aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-12-29 05:23:38 +0000
committerAkshay <[email protected]>2022-01-08 10:33:10 +0000
commitd1ff222bcf94152cd657233cffd8c14a45788c26 (patch)
treefb8c94daefe0384a48b503fdd4bfaff905d78e2f
parent94a2edf57340ac3f3a2276c88a221ba3125172af (diff)
allow for version based lints
-rw-r--r--bin/src/fix.rs30
-rw-r--r--bin/src/fix/all.rs21
-rw-r--r--bin/src/fix/single.rs15
-rw-r--r--bin/src/lib.rs1
-rw-r--r--bin/src/lint.rs23
-rw-r--r--bin/src/session.rs0
-rw-r--r--bin/src/utils.rs11
-rw-r--r--bin/tests/data/faster_groupby.nix15
-rw-r--r--bin/tests/main.rs66
-rw-r--r--bin/tests/snapshots/main__faster_groupby.snap20
-rw-r--r--flake.lock18
-rw-r--r--flake.nix4
-rw-r--r--lib/src/lib.rs4
-rw-r--r--lib/src/lints.rs1
-rw-r--r--lib/src/lints/bool_comparison.rs4
-rw-r--r--lib/src/lints/collapsible_let_in.rs4
-rw-r--r--lib/src/lints/deprecated_is_null.rs4
-rw-r--r--lib/src/lints/empty_inherit.rs4
-rw-r--r--lib/src/lints/empty_let_in.rs4
-rw-r--r--lib/src/lints/empty_pattern.rs4
-rw-r--r--lib/src/lints/eta_reduction.rs4
-rw-r--r--lib/src/lints/faster_groupby.rs72
-rw-r--r--lib/src/lints/legacy_let_syntax.rs4
-rw-r--r--lib/src/lints/manual_inherit.rs4
-rw-r--r--lib/src/lints/manual_inherit_from.rs4
-rw-r--r--lib/src/lints/redundant_pattern_bind.rs4
-rw-r--r--lib/src/lints/unquoted_splice.rs4
-rw-r--r--lib/src/lints/unquoted_uri.rs4
-rw-r--r--lib/src/lints/useless_parens.rs4
-rw-r--r--lib/src/session.rs103
30 files changed, 375 insertions, 85 deletions
diff --git a/bin/src/fix.rs b/bin/src/fix.rs
index a035379..4b2ce0c 100644
--- a/bin/src/fix.rs
+++ b/bin/src/fix.rs
@@ -2,6 +2,7 @@ use std::borrow::Cow;
2 2
3use crate::LintMap; 3use crate::LintMap;
4 4
5use lib::session::SessionInfo;
5use rnix::TextRange; 6use rnix::TextRange;
6 7
7mod all; 8mod all;
@@ -16,6 +17,7 @@ pub struct FixResult<'a> {
16 pub src: Source<'a>, 17 pub src: Source<'a>,
17 pub fixed: Vec<Fixed>, 18 pub fixed: Vec<Fixed>,
18 pub lints: &'a LintMap, 19 pub lints: &'a LintMap,
20 pub sess: &'a SessionInfo,
19} 21}
20 22
21#[derive(Debug, Clone)] 23#[derive(Debug, Clone)]
@@ -25,11 +27,12 @@ pub struct Fixed {
25} 27}
26 28
27impl<'a> FixResult<'a> { 29impl<'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 Self { 31 Self {
30 src, 32 src,
31 fixed: Vec::new(), 33 fixed: Vec::new(),
32 lints, 34 lints,
35 sess,
33 } 36 }
34 } 37 }
35} 38}
@@ -40,15 +43,27 @@ pub mod main {
40 use crate::{ 43 use crate::{
41 config::{Fix as FixConfig, FixOut, Single as SingleConfig}, 44 config::{Fix as FixConfig, FixOut, Single as SingleConfig},
42 err::{FixErr, StatixErr}, 45 err::{FixErr, StatixErr},
46 utils,
43 }; 47 };
44 48
49 use lib::session::{SessionInfo, Version};
45 use similar::TextDiff; 50 use similar::TextDiff;
46 51
47 pub fn all(fix_config: FixConfig) -> Result<(), StatixErr> { 52 pub fn all(fix_config: FixConfig) -> Result<(), StatixErr> {
48 let vfs = fix_config.vfs()?; 53 let vfs = fix_config.vfs()?;
49 let lints = fix_config.lints()?; 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 for entry in vfs.iter() { 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 (FixOut::Diff, fix_result) => { 67 (FixOut::Diff, fix_result) => {
53 let src = fix_result 68 let src = fix_result
54 .map(|r| r.src) 69 .map(|r| r.src)
@@ -87,7 +102,16 @@ pub mod main {
87 let original_src = entry.contents; 102 let original_src = entry.contents;
88 let (line, col) = single_config.position; 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 (FixOut::Diff, single_result) => { 115 (FixOut::Diff, single_result) => {
92 let fixed_src = single_result 116 let fixed_src = single_result
93 .map(|r| r.src) 117 .map(|r| r.src)
diff --git a/bin/src/fix/all.rs b/bin/src/fix/all.rs
index 7e51d16..d7f7fff 100644
--- a/bin/src/fix/all.rs
+++ b/bin/src/fix/all.rs
@@ -1,6 +1,6 @@
1use std::borrow::Cow; 1use std::borrow::Cow;
2 2
3use lib::Report; 3use lib::{session::SessionInfo, Report};
4use rnix::{parser::ParseError as RnixParseErr, WalkEvent}; 4use rnix::{parser::ParseError as RnixParseErr, WalkEvent};
5 5
6use crate::{ 6use crate::{
@@ -8,7 +8,11 @@ use crate::{
8 LintMap, 8 LintMap,
9}; 9};
10 10
11fn collect_fixes(source: &str, lints: &LintMap) -> Result<Vec<Report>, RnixParseErr> { 11fn collect_fixes(
12 source: &str,
13 lints: &LintMap,
14 sess: &SessionInfo,
15) -> Result<Vec<Report>, RnixParseErr> {
12 let parsed = rnix::parse(source).as_result()?; 16 let parsed = rnix::parse(source).as_result()?;
13 17
14 Ok(parsed 18 Ok(parsed
@@ -18,7 +22,7 @@ fn collect_fixes(source: &str, lints: &LintMap) -> Result<Vec<Report>, RnixParse
18 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { 22 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
19 rules 23 rules
20 .iter() 24 .iter()
21 .filter_map(|rule| rule.validate(&child)) 25 .filter_map(|rule| rule.validate(&child, sess))
22 .filter(|report| report.total_suggestion_range().is_some()) 26 .filter(|report| report.total_suggestion_range().is_some())
23 .collect::<Vec<_>>() 27 .collect::<Vec<_>>()
24 }), 28 }),
@@ -57,7 +61,7 @@ fn reorder(mut reports: Vec<Report>) -> Vec<Report> {
57impl<'a> Iterator for FixResult<'a> { 61impl<'a> Iterator for FixResult<'a> {
58 type Item = FixResult<'a>; 62 type Item = FixResult<'a>;
59 fn next(&mut self) -> Option<Self::Item> { 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 if all_reports.is_empty() { 65 if all_reports.is_empty() {
62 return None; 66 return None;
63 } 67 }
@@ -78,13 +82,18 @@ impl<'a> Iterator for FixResult<'a> {
78 src: self.src.clone(), 82 src: self.src.clone(),
79 fixed, 83 fixed,
80 lints: self.lints, 84 lints: self.lints,
85 sess: self.sess,
81 }) 86 })
82 } 87 }
83} 88}
84 89
85pub fn all_with<'a>(src: &'a str, lints: &'a LintMap) -> Option<FixResult<'a>> { 90pub fn all_with<'a>(
91 src: &'a str,
92 lints: &'a LintMap,
93 sess: &'a SessionInfo,
94) -> Option<FixResult<'a>> {
86 let src = Cow::from(src); 95 let src = Cow::from(src);
87 let _ = rnix::parse(&src).as_result().ok()?; 96 let _ = rnix::parse(&src).as_result().ok()?;
88 let initial = FixResult::empty(src, lints); 97 let initial = FixResult::empty(src, lints, sess);
89 initial.into_iter().last() 98 initial.into_iter().last()
90} 99}
diff --git a/bin/src/fix/single.rs b/bin/src/fix/single.rs
index d95cfda..67b6b8f 100644
--- a/bin/src/fix/single.rs
+++ b/bin/src/fix/single.rs
@@ -1,6 +1,6 @@
1use std::{borrow::Cow, convert::TryFrom}; 1use std::{borrow::Cow, convert::TryFrom};
2 2
3use lib::Report; 3use lib::{session::SessionInfo, Report};
4use rnix::{TextSize, WalkEvent}; 4use rnix::{TextSize, WalkEvent};
5 5
6use crate::{err::SingleFixErr, fix::Source, utils}; 6use crate::{err::SingleFixErr, fix::Source, utils};
@@ -27,7 +27,7 @@ fn pos_to_byte(line: usize, col: usize, src: &str) -> Result<TextSize, SingleFix
27 } 27 }
28} 28}
29 29
30fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> { 30fn find(offset: TextSize, src: &str, sess: &SessionInfo) -> Result<Report, SingleFixErr> {
31 // we don't really need the source to form a completely parsed tree 31 // we don't really need the source to form a completely parsed tree
32 let parsed = rnix::parse(src); 32 let parsed = rnix::parse(src);
33 let lints = utils::lint_map(); 33 let lints = utils::lint_map();
@@ -39,7 +39,7 @@ fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> {
39 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { 39 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
40 rules 40 rules
41 .iter() 41 .iter()
42 .filter_map(|rule| rule.validate(&child)) 42 .filter_map(|rule| rule.validate(&child, sess))
43 .find(|report| report.total_suggestion_range().is_some()) 43 .find(|report| report.total_suggestion_range().is_some())
44 }), 44 }),
45 _ => None, 45 _ => None,
@@ -49,10 +49,15 @@ fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> {
49 .ok_or(SingleFixErr::NoOp) 49 .ok_or(SingleFixErr::NoOp)
50} 50}
51 51
52pub fn single(line: usize, col: usize, src: &str) -> Result<SingleFixResult, SingleFixErr> { 52pub fn single<'a, 'b>(
53 line: usize,
54 col: usize,
55 src: &'a str,
56 sess: &'b SessionInfo,
57) -> Result<SingleFixResult<'a>, SingleFixErr> {
53 let mut src = Cow::from(src); 58 let mut src = Cow::from(src);
54 let offset = pos_to_byte(line, col, &*src)?; 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 report.apply(src.to_mut()); 62 report.apply(src.to_mut());
58 63
diff --git a/bin/src/lib.rs b/bin/src/lib.rs
index 0105334..01d3ea7 100644
--- a/bin/src/lib.rs
+++ b/bin/src/lib.rs
@@ -4,6 +4,7 @@ pub mod err;
4pub mod explain; 4pub mod explain;
5pub mod fix; 5pub mod fix;
6pub mod lint; 6pub mod lint;
7pub mod session;
7pub mod traits; 8pub mod traits;
8 9
9mod utils; 10mod utils;
diff --git a/bin/src/lint.rs b/bin/src/lint.rs
index 3482d46..c385007 100644
--- a/bin/src/lint.rs
+++ b/bin/src/lint.rs
@@ -1,6 +1,6 @@
1use crate::{utils, LintMap}; 1use crate::{utils, LintMap};
2 2
3use lib::Report; 3use lib::{session::SessionInfo, Report};
4use rnix::WalkEvent; 4use rnix::WalkEvent;
5use vfs::{FileId, VfsEntry}; 5use vfs::{FileId, VfsEntry};
6 6
@@ -10,7 +10,7 @@ pub struct LintResult {
10 pub reports: Vec<Report>, 10 pub reports: Vec<Report>,
11} 11}
12 12
13pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap) -> LintResult { 13pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap, sess: &SessionInfo) -> LintResult {
14 let file_id = vfs_entry.file_id; 14 let file_id = vfs_entry.file_id;
15 let source = vfs_entry.contents; 15 let source = vfs_entry.contents;
16 let parsed = rnix::parse(source); 16 let parsed = rnix::parse(source);
@@ -23,7 +23,7 @@ pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap) -> LintResult {
23 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { 23 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
24 rules 24 rules
25 .iter() 25 .iter()
26 .filter_map(|rule| rule.validate(&child)) 26 .filter_map(|rule| rule.validate(&child, sess))
27 .collect::<Vec<_>>() 27 .collect::<Vec<_>>()
28 }), 28 }),
29 _ => None, 29 _ => None,
@@ -35,21 +35,30 @@ pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap) -> LintResult {
35 LintResult { file_id, reports } 35 LintResult { file_id, reports }
36} 36}
37 37
38pub fn lint(vfs_entry: VfsEntry) -> LintResult { 38pub fn lint(vfs_entry: VfsEntry, sess: &SessionInfo) -> LintResult {
39 lint_with(vfs_entry, &utils::lint_map()) 39 lint_with(vfs_entry, &utils::lint_map(), &sess)
40} 40}
41 41
42pub mod main { 42pub mod main {
43 use std::io; 43 use std::io;
44 44
45 use super::lint_with; 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 pub fn main(check_config: CheckConfig) -> Result<(), StatixErr> { 50 pub fn main(check_config: CheckConfig) -> Result<(), StatixErr> {
49 let vfs = check_config.vfs()?; 51 let vfs = check_config.vfs()?;
50 let mut stdout = io::stdout(); 52 let mut stdout = io::stdout();
51 let lints = check_config.lints()?; 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 vfs.iter().map(lint).for_each(|r| { 62 vfs.iter().map(lint).for_each(|r| {
54 stdout.write(&r, &vfs, check_config.format).unwrap(); 63 stdout.write(&r, &vfs, check_config.format).unwrap();
55 }); 64 });
diff --git a/bin/src/session.rs b/bin/src/session.rs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/src/session.rs
diff --git a/bin/src/utils.rs b/bin/src/utils.rs
index 747a761..d374b4b 100644
--- a/bin/src/utils.rs
+++ b/bin/src/utils.rs
@@ -22,3 +22,14 @@ pub fn lint_map_of(
22pub fn lint_map() -> HashMap<SyntaxKind, Vec<&'static Box<dyn Lint>>> { 22pub fn lint_map() -> HashMap<SyntaxKind, Vec<&'static Box<dyn Lint>>> {
23 lint_map_of(&*LINTS) 23 lint_map_of(&*LINTS)
24} 24}
25
26pub 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}
diff --git a/bin/tests/data/faster_groupby.nix b/bin/tests/data/faster_groupby.nix
new file mode 100644
index 0000000..30d1031
--- /dev/null
+++ b/bin/tests/data/faster_groupby.nix
@@ -0,0 +1,15 @@
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}
diff --git a/bin/tests/main.rs b/bin/tests/main.rs
index de5266f..991c876 100644
--- a/bin/tests/main.rs
+++ b/bin/tests/main.rs
@@ -1,31 +1,48 @@
1use lib::session::{SessionInfo, Version};
2
3macro_rules! session_info {
4 ($version:expr) => {{
5 let v: Version = $version.parse().unwrap();
6 SessionInfo::from_version(v)
7 }};
8}
9
1mod util { 10mod util {
2 #[macro_export] 11 #[macro_export]
3 macro_rules! test_lint { 12 macro_rules! test_lint {
4 ($($tname:ident),*,) => { 13 ($tname:ident => $sess:expr, $($tail:tt)*) => {
5 test_lint!($($tname),*); 14 test_lint!($tname => $sess);
15 test_lint!($($tail)*);
16 };
17 ($tname:ident, $($tail:tt)*) => {
18 test_lint!($tname);
19 test_lint!($($tail)*);
20 };
21 ($tname:ident) => {
22 test_lint!($tname => session_info!("nix (Nix) 2.5"));
6 }; 23 };
7 ($($tname:ident),*) => { 24 ($tname:ident => $sess:expr) => {
8 $( 25 #[test]
9 #[test] 26 fn $tname() {
10 fn $tname() { 27 use statix::{config::OutFormat, traits::WriteDiagnostic, lint};
11 use statix::{config::OutFormat, traits::WriteDiagnostic, lint}; 28 use vfs::ReadOnlyVfs;
12 use vfs::ReadOnlyVfs; 29
13 30 let file_path = concat!("data/", stringify!($tname), ".nix");
14 let file_path = concat!("data/", stringify!($tname), ".nix"); 31 let contents = include_str!(concat!("data/", stringify!($tname), ".nix"));
15 let contents = include_str!(concat!("data/", stringify!($tname), ".nix")); 32
16 33 let vfs = ReadOnlyVfs::singleton(file_path, contents.as_bytes());
17 let vfs = ReadOnlyVfs::singleton(file_path, contents.as_bytes()); 34
18 35 let session = $sess;
19 let mut buffer = Vec::new(); 36
20 vfs.iter().map(lint::lint).for_each(|r| { 37 let mut buffer = Vec::new();
21 buffer.write(&r, &vfs, OutFormat::StdErr).unwrap(); 38 vfs.iter().map(|entry| lint::lint(entry, &session)).for_each(|r| {
22 }); 39 buffer.write(&r, &vfs, OutFormat::StdErr).unwrap();
23 40 });
24 let stripped = strip_ansi_escapes::strip(&buffer).unwrap(); 41
25 let out = std::str::from_utf8(&stripped).unwrap(); 42 let stripped = strip_ansi_escapes::strip(&buffer).unwrap();
26 insta::assert_snapshot!(&out); 43 let out = std::str::from_utf8(&stripped).unwrap();
27 } 44 insta::assert_snapshot!(&out);
28 )* 45 }
29 }; 46 };
30 } 47 }
31} 48}
@@ -44,4 +61,5 @@ test_lint! {
44 unquoted_uri, 61 unquoted_uri,
45 deprecated_is_null, 62 deprecated_is_null,
46 empty_inherit, 63 empty_inherit,
64 faster_groupby => session_info!("nix (Nix) 2.5")
47} 65}
diff --git a/bin/tests/snapshots/main__faster_groupby.snap b/bin/tests/snapshots/main__faster_groupby.snap
new file mode 100644
index 0000000..6ff3380
--- /dev/null
+++ b/bin/tests/snapshots/main__faster_groupby.snap
@@ -0,0 +1,20 @@
1---
2source: bin/tests/main.rs
3expression: "&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
diff --git a/flake.lock b/flake.lock
index ace5efe..ee2d078 100644
--- a/flake.lock
+++ b/flake.lock
@@ -8,11 +8,11 @@
8 "rust-analyzer-src": "rust-analyzer-src" 8 "rust-analyzer-src": "rust-analyzer-src"
9 }, 9 },
10 "locked": { 10 "locked": {
11 "lastModified": 1638080655, 11 "lastModified": 1640413491,
12 "narHash": "sha256-ZPx8e8CukEBx31IcgivAWnN9Jg0r+LTBPHV7fREf+QI=", 12 "narHash": "sha256-3VdQNPd9k4Fcjv/JBlwtJZWXg4fhrqTrEoSqhEOEWaU=",
13 "owner": "nix-community", 13 "owner": "nix-community",
14 "repo": "fenix", 14 "repo": "fenix",
15 "rev": "78a0c55b6f9d8bb6f3b89eb995fa5bbdd73e9475", 15 "rev": "730771574878f1fd5c31b4d7b1595df389f01ea3",
16 "type": "github" 16 "type": "github"
17 }, 17 },
18 "original": { 18 "original": {
@@ -43,11 +43,11 @@
43 }, 43 },
44 "nixpkgs": { 44 "nixpkgs": {
45 "locked": { 45 "locked": {
46 "lastModified": 1638036523, 46 "lastModified": 1640418986,
47 "narHash": "sha256-ZL6gogsuBmhBvIro+YwRKrypYhwVPCOOO7FmhOV/xyE=", 47 "narHash": "sha256-a8GGtxn2iL3WAkY5H+4E0s3Q7XJt6bTOvos9qqxT5OQ=",
48 "owner": "nixos", 48 "owner": "nixos",
49 "repo": "nixpkgs", 49 "repo": "nixpkgs",
50 "rev": "9c191ebcdfe917043195c54ab6ae8e934434fe7b", 50 "rev": "5c37ad87222cfc1ec36d6cd1364514a9efc2f7f2",
51 "type": "github" 51 "type": "github"
52 }, 52 },
53 "original": { 53 "original": {
@@ -67,11 +67,11 @@
67 "rust-analyzer-src": { 67 "rust-analyzer-src": {
68 "flake": false, 68 "flake": false,
69 "locked": { 69 "locked": {
70 "lastModified": 1638036899, 70 "lastModified": 1640270361,
71 "narHash": "sha256-vh7z8jupVxXPOko3sWUsOB7eji/7lKfwJ/CE3iw97Sw=", 71 "narHash": "sha256-g0canOfW6Iu/wSy0XUctgmlHJahfh5NqwDYddcRHXJQ=",
72 "owner": "rust-analyzer", 72 "owner": "rust-analyzer",
73 "repo": "rust-analyzer", 73 "repo": "rust-analyzer",
74 "rev": "d9b2291f546abc77d24499339a72a89127464b95", 74 "rev": "7b7a1ed062edd438caa824b6a71aaaa56b48e7d4",
75 "type": "github" 75 "type": "github"
76 }, 76 },
77 "original": { 77 "original": {
diff --git a/flake.nix b/flake.nix
index c598bdf..9bb9332 100644
--- a/flake.nix
+++ b/flake.nix
@@ -33,9 +33,9 @@
33 }); 33 });
34 34
35 chanspec = { 35 chanspec = {
36 date = "2021-11-01"; 36 date = "2021-12-01";
37 channel = "nightly"; 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 rustChannel = p: (fenix.overlay p p).fenix.toolchainOf chanspec; 40 rustChannel = p: (fenix.overlay p p).fenix.toolchainOf chanspec;
41 41
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index a25b814..cc4818a 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -1,9 +1,11 @@
1#![recursion_limit = "1024"] 1#![recursion_limit = "1024"]
2mod lints; 2mod lints;
3mod make; 3mod make;
4pub mod session;
4mod utils; 5mod utils;
5 6
6pub use lints::LINTS; 7pub use lints::LINTS;
8use session::SessionInfo;
7 9
8use rnix::{parser::ParseError, SyntaxElement, SyntaxKind, TextRange}; 10use rnix::{parser::ParseError, SyntaxElement, SyntaxKind, TextRange};
9use std::{convert::Into, default::Default}; 11use std::{convert::Into, default::Default};
@@ -221,7 +223,7 @@ impl Serialize for Suggestion {
221/// Lint logic is defined via this trait. Do not implement manually, 223/// Lint logic is defined via this trait. Do not implement manually,
222/// look at the `lint` attribute macro instead for implementing rules 224/// look at the `lint` attribute macro instead for implementing rules
223pub trait Rule { 225pub 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/// Contains information about the lint itself. Do not implement manually, 229/// Contains information about the lint itself. Do not implement manually,
diff --git a/lib/src/lints.rs b/lib/src/lints.rs
index 5de6b65..0add458 100644
--- a/lib/src/lints.rs
+++ b/lib/src/lints.rs
@@ -15,4 +15,5 @@ lints! {
15 unquoted_uri, 15 unquoted_uri,
16 deprecated_is_null, 16 deprecated_is_null,
17 empty_inherit, 17 empty_inherit,
18 faster_groupby,
18} 19}
diff --git a/lib/src/lints/bool_comparison.rs b/lib/src/lints/bool_comparison.rs
index ed1e8bb..ef7f5d2 100644
--- a/lib/src/lints/bool_comparison.rs
+++ b/lib/src/lints/bool_comparison.rs
@@ -1,4 +1,4 @@
1use crate::{make, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -35,7 +35,7 @@ use rnix::{
35struct BoolComparison; 35struct BoolComparison;
36 36
37impl Rule for BoolComparison { 37impl Rule for BoolComparison {
38 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 38 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
39 if_chain! { 39 if_chain! {
40 if let NodeOrToken::Node(node) = node; 40 if let NodeOrToken::Node(node) = node;
41 if let Some(bin_expr) = BinOp::cast(node.clone()); 41 if let Some(bin_expr) = BinOp::cast(node.clone());
diff --git a/lib/src/lints/collapsible_let_in.rs b/lib/src/lints/collapsible_let_in.rs
index aa7e5a7..1c04d9c 100644
--- a/lib/src/lints/collapsible_let_in.rs
+++ b/lib/src/lints/collapsible_let_in.rs
@@ -1,4 +1,4 @@
1use crate::{make, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -45,7 +45,7 @@ use rowan::Direction;
45struct CollapsibleLetIn; 45struct CollapsibleLetIn;
46 46
47impl Rule for CollapsibleLetIn { 47impl Rule for CollapsibleLetIn {
48 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 48 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
49 if_chain! { 49 if_chain! {
50 if let NodeOrToken::Node(node) = node; 50 if let NodeOrToken::Node(node) = node;
51 if let Some(let_in_expr) = LetIn::cast(node.clone()); 51 if let Some(let_in_expr) = LetIn::cast(node.clone());
diff --git a/lib/src/lints/deprecated_is_null.rs b/lib/src/lints/deprecated_is_null.rs
index c814e87..9e7c293 100644
--- a/lib/src/lints/deprecated_is_null.rs
+++ b/lib/src/lints/deprecated_is_null.rs
@@ -1,4 +1,4 @@
1use crate::{make, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -35,7 +35,7 @@ use rnix::{
35struct DeprecatedIsNull; 35struct DeprecatedIsNull;
36 36
37impl Rule for DeprecatedIsNull { 37impl Rule for DeprecatedIsNull {
38 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 38 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
39 if_chain! { 39 if_chain! {
40 if let NodeOrToken::Node(node) = node; 40 if let NodeOrToken::Node(node) = node;
41 if let Some(apply) = Apply::cast(node.clone()); 41 if let Some(apply) = Apply::cast(node.clone());
diff --git a/lib/src/lints/empty_inherit.rs b/lib/src/lints/empty_inherit.rs
index 9ae4cf2..871c218 100644
--- a/lib/src/lints/empty_inherit.rs
+++ b/lib/src/lints/empty_inherit.rs
@@ -1,4 +1,4 @@
1use crate::{make, utils, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, utils, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -29,7 +29,7 @@ use rnix::{
29struct EmptyInherit; 29struct EmptyInherit;
30 30
31impl Rule for EmptyInherit { 31impl Rule for EmptyInherit {
32 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 32 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
33 if_chain! { 33 if_chain! {
34 if let NodeOrToken::Node(node) = node; 34 if let NodeOrToken::Node(node) = node;
35 if let Some(inherit_stmt) = Inherit::cast(node.clone()); 35 if let Some(inherit_stmt) = Inherit::cast(node.clone());
diff --git a/lib/src/lints/empty_let_in.rs b/lib/src/lints/empty_let_in.rs
index d33d0ae..e42f658 100644
--- a/lib/src/lints/empty_let_in.rs
+++ b/lib/src/lints/empty_let_in.rs
@@ -1,4 +1,4 @@
1use crate::{Metadata, Report, Rule, Suggestion}; 1use crate::{session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -34,7 +34,7 @@ use rnix::{
34struct EmptyLetIn; 34struct EmptyLetIn;
35 35
36impl Rule for EmptyLetIn { 36impl Rule for EmptyLetIn {
37 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 37 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
38 if_chain! { 38 if_chain! {
39 if let NodeOrToken::Node(node) = node; 39 if let NodeOrToken::Node(node) = node;
40 if let Some(let_in_expr) = LetIn::cast(node.clone()); 40 if let Some(let_in_expr) = LetIn::cast(node.clone());
diff --git a/lib/src/lints/empty_pattern.rs b/lib/src/lints/empty_pattern.rs
index f66a3b1..e03708b 100644
--- a/lib/src/lints/empty_pattern.rs
+++ b/lib/src/lints/empty_pattern.rs
@@ -1,4 +1,4 @@
1use crate::{make, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -43,7 +43,7 @@ use rnix::{
43struct EmptyPattern; 43struct EmptyPattern;
44 44
45impl Rule for EmptyPattern { 45impl Rule for EmptyPattern {
46 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 46 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
47 if_chain! { 47 if_chain! {
48 if let NodeOrToken::Node(node) = node; 48 if let NodeOrToken::Node(node) = node;
49 if let Some(pattern) = Pattern::cast(node.clone()); 49 if let Some(pattern) = Pattern::cast(node.clone());
diff --git a/lib/src/lints/eta_reduction.rs b/lib/src/lints/eta_reduction.rs
index 580f4a0..8e9d2a3 100644
--- a/lib/src/lints/eta_reduction.rs
+++ b/lib/src/lints/eta_reduction.rs
@@ -1,4 +1,4 @@
1use crate::{Metadata, Report, Rule, Suggestion}; 1use crate::{session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -42,7 +42,7 @@ use rnix::{
42struct EtaReduction; 42struct EtaReduction;
43 43
44impl Rule for EtaReduction { 44impl Rule for EtaReduction {
45 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 45 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
46 if_chain! { 46 if_chain! {
47 if let NodeOrToken::Node(node) = node; 47 if let NodeOrToken::Node(node) = node;
48 if let Some(lambda_expr) = Lambda::cast(node.clone()); 48 if let Some(lambda_expr) = Lambda::cast(node.clone());
diff --git a/lib/src/lints/faster_groupby.rs b/lib/src/lints/faster_groupby.rs
new file mode 100644
index 0000000..c496125
--- /dev/null
+++ b/lib/src/lints/faster_groupby.rs
@@ -0,0 +1,72 @@
1use crate::{
2 make,
3 session::{SessionInfo, Version},
4 Metadata, Report, Rule, Suggestion,
5};
6
7use if_chain::if_chain;
8use macros::lint;
9use 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)]
39struct FasterGroupBy;
40
41impl 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}
diff --git a/lib/src/lints/legacy_let_syntax.rs b/lib/src/lints/legacy_let_syntax.rs
index 5d0028b..e0b8980 100644
--- a/lib/src/lints/legacy_let_syntax.rs
+++ b/lib/src/lints/legacy_let_syntax.rs
@@ -1,4 +1,4 @@
1use crate::{make, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -44,7 +44,7 @@ use rnix::{
44struct ManualInherit; 44struct ManualInherit;
45 45
46impl Rule for ManualInherit { 46impl Rule for ManualInherit {
47 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 47 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
48 if_chain! { 48 if_chain! {
49 if let NodeOrToken::Node(node) = node; 49 if let NodeOrToken::Node(node) = node;
50 if let Some(legacy_let) = LegacyLet::cast(node.clone()); 50 if let Some(legacy_let) = LegacyLet::cast(node.clone());
diff --git a/lib/src/lints/manual_inherit.rs b/lib/src/lints/manual_inherit.rs
index 7717dc9..4fddce5 100644
--- a/lib/src/lints/manual_inherit.rs
+++ b/lib/src/lints/manual_inherit.rs
@@ -1,4 +1,4 @@
1use crate::{make, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -40,7 +40,7 @@ use rnix::{
40struct ManualInherit; 40struct ManualInherit;
41 41
42impl Rule for ManualInherit { 42impl Rule for ManualInherit {
43 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 43 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
44 if_chain! { 44 if_chain! {
45 if let NodeOrToken::Node(node) = node; 45 if let NodeOrToken::Node(node) = node;
46 if let Some(key_value_stmt) = KeyValue::cast(node.clone()); 46 if let Some(key_value_stmt) = KeyValue::cast(node.clone());
diff --git a/lib/src/lints/manual_inherit_from.rs b/lib/src/lints/manual_inherit_from.rs
index 05d6bc8..a62a6c7 100644
--- a/lib/src/lints/manual_inherit_from.rs
+++ b/lib/src/lints/manual_inherit_from.rs
@@ -1,4 +1,4 @@
1use crate::{make, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -40,7 +40,7 @@ use rnix::{
40struct ManualInherit; 40struct ManualInherit;
41 41
42impl Rule for ManualInherit { 42impl Rule for ManualInherit {
43 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 43 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
44 if_chain! { 44 if_chain! {
45 if let NodeOrToken::Node(node) = node; 45 if let NodeOrToken::Node(node) = node;
46 if let Some(key_value_stmt) = KeyValue::cast(node.clone()); 46 if let Some(key_value_stmt) = KeyValue::cast(node.clone());
diff --git a/lib/src/lints/redundant_pattern_bind.rs b/lib/src/lints/redundant_pattern_bind.rs
index 88ce4b0..56957ce 100644
--- a/lib/src/lints/redundant_pattern_bind.rs
+++ b/lib/src/lints/redundant_pattern_bind.rs
@@ -1,4 +1,4 @@
1use crate::{Metadata, Report, Rule, Suggestion}; 1use crate::{session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -35,7 +35,7 @@ use rnix::{
35struct RedundantPatternBind; 35struct RedundantPatternBind;
36 36
37impl Rule for RedundantPatternBind { 37impl Rule for RedundantPatternBind {
38 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 38 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
39 if_chain! { 39 if_chain! {
40 if let NodeOrToken::Node(node) = node; 40 if let NodeOrToken::Node(node) = node;
41 if let Some(pattern) = Pattern::cast(node.clone()); 41 if let Some(pattern) = Pattern::cast(node.clone());
diff --git a/lib/src/lints/unquoted_splice.rs b/lib/src/lints/unquoted_splice.rs
index 7649fbc..ba1641a 100644
--- a/lib/src/lints/unquoted_splice.rs
+++ b/lib/src/lints/unquoted_splice.rs
@@ -1,4 +1,4 @@
1use crate::{make, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -40,7 +40,7 @@ use rnix::{
40struct UnquotedSplice; 40struct UnquotedSplice;
41 41
42impl Rule for UnquotedSplice { 42impl Rule for UnquotedSplice {
43 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 43 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
44 if_chain! { 44 if_chain! {
45 if let NodeOrToken::Node(node) = node; 45 if let NodeOrToken::Node(node) = node;
46 if Dynamic::cast(node.clone()).is_some(); 46 if Dynamic::cast(node.clone()).is_some();
diff --git a/lib/src/lints/unquoted_uri.rs b/lib/src/lints/unquoted_uri.rs
index 8835338..440b278 100644
--- a/lib/src/lints/unquoted_uri.rs
+++ b/lib/src/lints/unquoted_uri.rs
@@ -1,4 +1,4 @@
1use crate::{make, Metadata, Report, Rule, Suggestion}; 1use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -46,7 +46,7 @@ use rnix::{types::TypedNode, NodeOrToken, SyntaxElement, SyntaxKind};
46struct UnquotedUri; 46struct UnquotedUri;
47 47
48impl Rule for UnquotedUri { 48impl Rule for UnquotedUri {
49 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 49 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
50 if_chain! { 50 if_chain! {
51 if let NodeOrToken::Token(token) = node; 51 if let NodeOrToken::Token(token) = node;
52 then { 52 then {
diff --git a/lib/src/lints/useless_parens.rs b/lib/src/lints/useless_parens.rs
index 09d6f04..9cba4b3 100644
--- a/lib/src/lints/useless_parens.rs
+++ b/lib/src/lints/useless_parens.rs
@@ -1,4 +1,4 @@
1use crate::{Diagnostic, Metadata, Report, Rule, Suggestion}; 1use crate::{session::SessionInfo, Diagnostic, Metadata, Report, Rule, Suggestion};
2 2
3use if_chain::if_chain; 3use if_chain::if_chain;
4use macros::lint; 4use macros::lint;
@@ -45,7 +45,7 @@ use rnix::{
45struct UselessParens; 45struct UselessParens;
46 46
47impl Rule for UselessParens { 47impl Rule for UselessParens {
48 fn validate(&self, node: &SyntaxElement) -> Option<Report> { 48 fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
49 if_chain! { 49 if_chain! {
50 if let NodeOrToken::Node(node) = node; 50 if let NodeOrToken::Node(node) = node;
51 if let Some(parsed_type_node) = ParsedType::cast(node.clone()); 51 if let Some(parsed_type_node) = ParsedType::cast(node.clone());
diff --git a/lib/src/session.rs b/lib/src/session.rs
new file mode 100644
index 0000000..8d142ec
--- /dev/null
+++ b/lib/src/session.rs
@@ -0,0 +1,103 @@
1use std::{cmp::Ordering, str::FromStr};
2
3#[derive(Copy, Clone, Debug, Eq, PartialEq)]
4pub struct Version {
5 major: u16,
6 minor: u16,
7 patch: Option<u16>,
8}
9
10impl 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
17impl PartialOrd for Version {
18 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
19 Some(self.cmp(other))
20 }
21}
22
23fn 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
31fn 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
48impl 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]
56pub struct SessionInfo {
57 nix_version: Version,
58}
59
60impl 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
70pub fn get_nix_version() -> Option<Version> {
71 "nix (Nix) 2.4pre20211006_53e4794".parse::<Version>().ok()
72}
73
74#[cfg(test)]
75mod 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}