mirror of
https://github.com/nerdypepper/statix.git
synced 2024-08-16 17:00:27 +03:00
allow for version based lints
This commit is contained in:
parent
94a2edf573
commit
d1ff222bcf
@ -2,6 +2,7 @@ use std::borrow::Cow;
|
||||
|
||||
use crate::LintMap;
|
||||
|
||||
use lib::session::SessionInfo;
|
||||
use rnix::TextRange;
|
||||
|
||||
mod all;
|
||||
@ -16,6 +17,7 @@ pub struct FixResult<'a> {
|
||||
pub src: Source<'a>,
|
||||
pub fixed: Vec<Fixed>,
|
||||
pub lints: &'a LintMap,
|
||||
pub sess: &'a SessionInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -25,11 +27,12 @@ pub struct Fixed {
|
||||
}
|
||||
|
||||
impl<'a> FixResult<'a> {
|
||||
fn empty(src: Source<'a>, lints: &'a LintMap) -> Self {
|
||||
fn empty(src: Source<'a>, lints: &'a LintMap, sess: &'a SessionInfo) -> Self {
|
||||
Self {
|
||||
src,
|
||||
fixed: Vec::new(),
|
||||
lints,
|
||||
sess,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,15 +43,27 @@ pub mod main {
|
||||
use crate::{
|
||||
config::{Fix as FixConfig, FixOut, Single as SingleConfig},
|
||||
err::{FixErr, StatixErr},
|
||||
utils,
|
||||
};
|
||||
|
||||
use lib::session::{SessionInfo, Version};
|
||||
use similar::TextDiff;
|
||||
|
||||
pub fn all(fix_config: FixConfig) -> Result<(), StatixErr> {
|
||||
let vfs = fix_config.vfs()?;
|
||||
let lints = fix_config.lints()?;
|
||||
|
||||
let version = utils::get_version_info()
|
||||
.unwrap()
|
||||
.parse::<Version>()
|
||||
.unwrap();
|
||||
let session = SessionInfo::from_version(version);
|
||||
|
||||
for entry in vfs.iter() {
|
||||
match (fix_config.out(), super::all_with(entry.contents, &lints)) {
|
||||
match (
|
||||
fix_config.out(),
|
||||
super::all_with(entry.contents, &lints, &session),
|
||||
) {
|
||||
(FixOut::Diff, fix_result) => {
|
||||
let src = fix_result
|
||||
.map(|r| r.src)
|
||||
@ -87,7 +102,16 @@ pub mod main {
|
||||
let original_src = entry.contents;
|
||||
let (line, col) = single_config.position;
|
||||
|
||||
match (single_config.out(), super::single(line, col, original_src)) {
|
||||
let version = utils::get_version_info()
|
||||
.unwrap()
|
||||
.parse::<Version>()
|
||||
.unwrap();
|
||||
let session = SessionInfo::from_version(version);
|
||||
|
||||
match (
|
||||
single_config.out(),
|
||||
super::single(line, col, original_src, &session),
|
||||
) {
|
||||
(FixOut::Diff, single_result) => {
|
||||
let fixed_src = single_result
|
||||
.map(|r| r.src)
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use lib::Report;
|
||||
use lib::{session::SessionInfo, Report};
|
||||
use rnix::{parser::ParseError as RnixParseErr, WalkEvent};
|
||||
|
||||
use crate::{
|
||||
@ -8,7 +8,11 @@ use crate::{
|
||||
LintMap,
|
||||
};
|
||||
|
||||
fn collect_fixes(source: &str, lints: &LintMap) -> Result<Vec<Report>, RnixParseErr> {
|
||||
fn collect_fixes(
|
||||
source: &str,
|
||||
lints: &LintMap,
|
||||
sess: &SessionInfo,
|
||||
) -> Result<Vec<Report>, RnixParseErr> {
|
||||
let parsed = rnix::parse(source).as_result()?;
|
||||
|
||||
Ok(parsed
|
||||
@ -18,7 +22,7 @@ fn collect_fixes(source: &str, lints: &LintMap) -> Result<Vec<Report>, RnixParse
|
||||
WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
|
||||
rules
|
||||
.iter()
|
||||
.filter_map(|rule| rule.validate(&child))
|
||||
.filter_map(|rule| rule.validate(&child, sess))
|
||||
.filter(|report| report.total_suggestion_range().is_some())
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
@ -57,7 +61,7 @@ fn reorder(mut reports: Vec<Report>) -> Vec<Report> {
|
||||
impl<'a> Iterator for FixResult<'a> {
|
||||
type Item = FixResult<'a>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let all_reports = collect_fixes(&self.src, self.lints).ok()?;
|
||||
let all_reports = collect_fixes(&self.src, self.lints, &self.sess).ok()?;
|
||||
if all_reports.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@ -78,13 +82,18 @@ impl<'a> Iterator for FixResult<'a> {
|
||||
src: self.src.clone(),
|
||||
fixed,
|
||||
lints: self.lints,
|
||||
sess: self.sess,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_with<'a>(src: &'a str, lints: &'a LintMap) -> Option<FixResult<'a>> {
|
||||
pub fn all_with<'a>(
|
||||
src: &'a str,
|
||||
lints: &'a LintMap,
|
||||
sess: &'a SessionInfo,
|
||||
) -> Option<FixResult<'a>> {
|
||||
let src = Cow::from(src);
|
||||
let _ = rnix::parse(&src).as_result().ok()?;
|
||||
let initial = FixResult::empty(src, lints);
|
||||
let initial = FixResult::empty(src, lints, sess);
|
||||
initial.into_iter().last()
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{borrow::Cow, convert::TryFrom};
|
||||
|
||||
use lib::Report;
|
||||
use lib::{session::SessionInfo, Report};
|
||||
use rnix::{TextSize, WalkEvent};
|
||||
|
||||
use crate::{err::SingleFixErr, fix::Source, utils};
|
||||
@ -27,7 +27,7 @@ fn pos_to_byte(line: usize, col: usize, src: &str) -> Result<TextSize, SingleFix
|
||||
}
|
||||
}
|
||||
|
||||
fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> {
|
||||
fn find(offset: TextSize, src: &str, sess: &SessionInfo) -> Result<Report, SingleFixErr> {
|
||||
// we don't really need the source to form a completely parsed tree
|
||||
let parsed = rnix::parse(src);
|
||||
let lints = utils::lint_map();
|
||||
@ -39,7 +39,7 @@ fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> {
|
||||
WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
|
||||
rules
|
||||
.iter()
|
||||
.filter_map(|rule| rule.validate(&child))
|
||||
.filter_map(|rule| rule.validate(&child, sess))
|
||||
.find(|report| report.total_suggestion_range().is_some())
|
||||
}),
|
||||
_ => None,
|
||||
@ -49,10 +49,15 @@ fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> {
|
||||
.ok_or(SingleFixErr::NoOp)
|
||||
}
|
||||
|
||||
pub fn single(line: usize, col: usize, src: &str) -> Result<SingleFixResult, SingleFixErr> {
|
||||
pub fn single<'a, 'b>(
|
||||
line: usize,
|
||||
col: usize,
|
||||
src: &'a str,
|
||||
sess: &'b SessionInfo,
|
||||
) -> Result<SingleFixResult<'a>, SingleFixErr> {
|
||||
let mut src = Cow::from(src);
|
||||
let offset = pos_to_byte(line, col, &*src)?;
|
||||
let report = find(offset, &*src)?;
|
||||
let report = find(offset, &*src, &sess)?;
|
||||
|
||||
report.apply(src.to_mut());
|
||||
|
||||
|
@ -4,6 +4,7 @@ pub mod err;
|
||||
pub mod explain;
|
||||
pub mod fix;
|
||||
pub mod lint;
|
||||
pub mod session;
|
||||
pub mod traits;
|
||||
|
||||
mod utils;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{utils, LintMap};
|
||||
|
||||
use lib::Report;
|
||||
use lib::{session::SessionInfo, Report};
|
||||
use rnix::WalkEvent;
|
||||
use vfs::{FileId, VfsEntry};
|
||||
|
||||
@ -10,7 +10,7 @@ pub struct LintResult {
|
||||
pub reports: Vec<Report>,
|
||||
}
|
||||
|
||||
pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap) -> LintResult {
|
||||
pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap, sess: &SessionInfo) -> LintResult {
|
||||
let file_id = vfs_entry.file_id;
|
||||
let source = vfs_entry.contents;
|
||||
let parsed = rnix::parse(source);
|
||||
@ -23,7 +23,7 @@ pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap) -> LintResult {
|
||||
WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
|
||||
rules
|
||||
.iter()
|
||||
.filter_map(|rule| rule.validate(&child))
|
||||
.filter_map(|rule| rule.validate(&child, sess))
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
_ => None,
|
||||
@ -35,21 +35,30 @@ pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap) -> LintResult {
|
||||
LintResult { file_id, reports }
|
||||
}
|
||||
|
||||
pub fn lint(vfs_entry: VfsEntry) -> LintResult {
|
||||
lint_with(vfs_entry, &utils::lint_map())
|
||||
pub fn lint(vfs_entry: VfsEntry, sess: &SessionInfo) -> LintResult {
|
||||
lint_with(vfs_entry, &utils::lint_map(), &sess)
|
||||
}
|
||||
|
||||
pub mod main {
|
||||
use std::io;
|
||||
|
||||
use super::lint_with;
|
||||
use crate::{config::Check as CheckConfig, err::StatixErr, traits::WriteDiagnostic};
|
||||
use crate::{config::Check as CheckConfig, err::StatixErr, traits::WriteDiagnostic, utils};
|
||||
|
||||
use lib::session::{SessionInfo, Version};
|
||||
|
||||
pub fn main(check_config: CheckConfig) -> Result<(), StatixErr> {
|
||||
let vfs = check_config.vfs()?;
|
||||
let mut stdout = io::stdout();
|
||||
let lints = check_config.lints()?;
|
||||
let lint = |vfs_entry| lint_with(vfs_entry, &lints);
|
||||
|
||||
let version = utils::get_version_info()
|
||||
.unwrap()
|
||||
.parse::<Version>()
|
||||
.unwrap();
|
||||
let session = SessionInfo::from_version(version);
|
||||
|
||||
let lint = |vfs_entry| lint_with(vfs_entry, &lints, &session);
|
||||
vfs.iter().map(lint).for_each(|r| {
|
||||
stdout.write(&r, &vfs, check_config.format).unwrap();
|
||||
});
|
||||
|
0
bin/src/session.rs
Normal file
0
bin/src/session.rs
Normal file
@ -22,3 +22,14 @@ pub fn lint_map_of(
|
||||
pub fn lint_map() -> HashMap<SyntaxKind, Vec<&'static Box<dyn Lint>>> {
|
||||
lint_map_of(&*LINTS)
|
||||
}
|
||||
|
||||
pub fn get_version_info() -> Option<String> {
|
||||
use std::process::Command;
|
||||
let program = Command::new("nix")
|
||||
.arg("--version")
|
||||
.output()
|
||||
.expect("failed to execute");
|
||||
std::str::from_utf8(&program.stdout)
|
||||
.ok()
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
15
bin/tests/data/faster_groupby.nix
Normal file
15
bin/tests/data/faster_groupby.nix
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
# trivial case
|
||||
_ = lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 ];
|
||||
|
||||
# offer lint heuristically on this too
|
||||
_ = nixpkgs.lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 ];
|
||||
|
||||
# do not lint on `builtins`
|
||||
_ = builtins.groupBy (x: x.name) [
|
||||
{ name = "foo"; idx = 1; }
|
||||
{ name = "foo"; idx = 2; }
|
||||
{ name = "bar"; idx = 1; }
|
||||
{ name = "bar"; idx = 2; }
|
||||
];
|
||||
}
|
@ -1,31 +1,48 @@
|
||||
use lib::session::{SessionInfo, Version};
|
||||
|
||||
macro_rules! session_info {
|
||||
($version:expr) => {{
|
||||
let v: Version = $version.parse().unwrap();
|
||||
SessionInfo::from_version(v)
|
||||
}};
|
||||
}
|
||||
|
||||
mod util {
|
||||
#[macro_export]
|
||||
macro_rules! test_lint {
|
||||
($($tname:ident),*,) => {
|
||||
test_lint!($($tname),*);
|
||||
($tname:ident => $sess:expr, $($tail:tt)*) => {
|
||||
test_lint!($tname => $sess);
|
||||
test_lint!($($tail)*);
|
||||
};
|
||||
($($tname:ident),*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $tname() {
|
||||
use statix::{config::OutFormat, traits::WriteDiagnostic, lint};
|
||||
use vfs::ReadOnlyVfs;
|
||||
($tname:ident, $($tail:tt)*) => {
|
||||
test_lint!($tname);
|
||||
test_lint!($($tail)*);
|
||||
};
|
||||
($tname:ident) => {
|
||||
test_lint!($tname => session_info!("nix (Nix) 2.5"));
|
||||
};
|
||||
($tname:ident => $sess:expr) => {
|
||||
#[test]
|
||||
fn $tname() {
|
||||
use statix::{config::OutFormat, traits::WriteDiagnostic, lint};
|
||||
use vfs::ReadOnlyVfs;
|
||||
|
||||
let file_path = concat!("data/", stringify!($tname), ".nix");
|
||||
let contents = include_str!(concat!("data/", stringify!($tname), ".nix"));
|
||||
let file_path = concat!("data/", stringify!($tname), ".nix");
|
||||
let contents = include_str!(concat!("data/", stringify!($tname), ".nix"));
|
||||
|
||||
let vfs = ReadOnlyVfs::singleton(file_path, contents.as_bytes());
|
||||
let vfs = ReadOnlyVfs::singleton(file_path, contents.as_bytes());
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
vfs.iter().map(lint::lint).for_each(|r| {
|
||||
buffer.write(&r, &vfs, OutFormat::StdErr).unwrap();
|
||||
});
|
||||
let session = $sess;
|
||||
|
||||
let stripped = strip_ansi_escapes::strip(&buffer).unwrap();
|
||||
let out = std::str::from_utf8(&stripped).unwrap();
|
||||
insta::assert_snapshot!(&out);
|
||||
}
|
||||
)*
|
||||
let mut buffer = Vec::new();
|
||||
vfs.iter().map(|entry| lint::lint(entry, &session)).for_each(|r| {
|
||||
buffer.write(&r, &vfs, OutFormat::StdErr).unwrap();
|
||||
});
|
||||
|
||||
let stripped = strip_ansi_escapes::strip(&buffer).unwrap();
|
||||
let out = std::str::from_utf8(&stripped).unwrap();
|
||||
insta::assert_snapshot!(&out);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -44,4 +61,5 @@ test_lint! {
|
||||
unquoted_uri,
|
||||
deprecated_is_null,
|
||||
empty_inherit,
|
||||
faster_groupby => session_info!("nix (Nix) 2.5")
|
||||
}
|
||||
|
20
bin/tests/snapshots/main__faster_groupby.snap
Normal file
20
bin/tests/snapshots/main__faster_groupby.snap
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
source: bin/tests/main.rs
|
||||
expression: "&out"
|
||||
|
||||
---
|
||||
[W15] Warning: Found lib.groupBy
|
||||
╭─[data/faster_groupby.nix:3:7]
|
||||
│
|
||||
3 │ _ = lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 ];
|
||||
· ─────┬─────
|
||||
· ╰─────── Prefer builtins.groupBy over lib.groupBy
|
||||
───╯
|
||||
[W15] Warning: Found lib.groupBy
|
||||
╭─[data/faster_groupby.nix:6:7]
|
||||
│
|
||||
6 │ _ = nixpkgs.lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 ];
|
||||
· ─────────┬─────────
|
||||
· ╰─────────── Prefer builtins.groupBy over nixpkgs.lib.groupBy
|
||||
───╯
|
||||
|
18
flake.lock
18
flake.lock
@ -8,11 +8,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1638080655,
|
||||
"narHash": "sha256-ZPx8e8CukEBx31IcgivAWnN9Jg0r+LTBPHV7fREf+QI=",
|
||||
"lastModified": 1640413491,
|
||||
"narHash": "sha256-3VdQNPd9k4Fcjv/JBlwtJZWXg4fhrqTrEoSqhEOEWaU=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "78a0c55b6f9d8bb6f3b89eb995fa5bbdd73e9475",
|
||||
"rev": "730771574878f1fd5c31b4d7b1595df389f01ea3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -43,11 +43,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1638036523,
|
||||
"narHash": "sha256-ZL6gogsuBmhBvIro+YwRKrypYhwVPCOOO7FmhOV/xyE=",
|
||||
"lastModified": 1640418986,
|
||||
"narHash": "sha256-a8GGtxn2iL3WAkY5H+4E0s3Q7XJt6bTOvos9qqxT5OQ=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9c191ebcdfe917043195c54ab6ae8e934434fe7b",
|
||||
"rev": "5c37ad87222cfc1ec36d6cd1364514a9efc2f7f2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -67,11 +67,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1638036899,
|
||||
"narHash": "sha256-vh7z8jupVxXPOko3sWUsOB7eji/7lKfwJ/CE3iw97Sw=",
|
||||
"lastModified": 1640270361,
|
||||
"narHash": "sha256-g0canOfW6Iu/wSy0XUctgmlHJahfh5NqwDYddcRHXJQ=",
|
||||
"owner": "rust-analyzer",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "d9b2291f546abc77d24499339a72a89127464b95",
|
||||
"rev": "7b7a1ed062edd438caa824b6a71aaaa56b48e7d4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -33,9 +33,9 @@
|
||||
});
|
||||
|
||||
chanspec = {
|
||||
date = "2021-11-01";
|
||||
date = "2021-12-01";
|
||||
channel = "nightly";
|
||||
sha256 = "2BmxGawDNjXHJvnQToxmErMGgEPOfVzUvxhkvuixHYU="; # set zeros after modifying channel or date
|
||||
sha256 = "DhIP1w63/hMbWlgElJGBumEK/ExFWCdLaeBV5F8uWHc="; # set zeros after modifying channel or date
|
||||
};
|
||||
rustChannel = p: (fenix.overlay p p).fenix.toolchainOf chanspec;
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
#![recursion_limit = "1024"]
|
||||
mod lints;
|
||||
mod make;
|
||||
pub mod session;
|
||||
mod utils;
|
||||
|
||||
pub use lints::LINTS;
|
||||
use session::SessionInfo;
|
||||
|
||||
use rnix::{parser::ParseError, SyntaxElement, SyntaxKind, TextRange};
|
||||
use std::{convert::Into, default::Default};
|
||||
@ -221,7 +223,7 @@ impl Serialize for Suggestion {
|
||||
/// Lint logic is defined via this trait. Do not implement manually,
|
||||
/// look at the `lint` attribute macro instead for implementing rules
|
||||
pub trait Rule {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report>;
|
||||
fn validate(&self, node: &SyntaxElement, sess: &SessionInfo) -> Option<Report>;
|
||||
}
|
||||
|
||||
/// Contains information about the lint itself. Do not implement manually,
|
||||
|
@ -15,4 +15,5 @@ lints! {
|
||||
unquoted_uri,
|
||||
deprecated_is_null,
|
||||
empty_inherit,
|
||||
faster_groupby,
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -35,7 +35,7 @@ use rnix::{
|
||||
struct BoolComparison;
|
||||
|
||||
impl Rule for BoolComparison {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(bin_expr) = BinOp::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -45,7 +45,7 @@ use rowan::Direction;
|
||||
struct CollapsibleLetIn;
|
||||
|
||||
impl Rule for CollapsibleLetIn {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(let_in_expr) = LetIn::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -35,7 +35,7 @@ use rnix::{
|
||||
struct DeprecatedIsNull;
|
||||
|
||||
impl Rule for DeprecatedIsNull {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(apply) = Apply::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, utils, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, utils, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -29,7 +29,7 @@ use rnix::{
|
||||
struct EmptyInherit;
|
||||
|
||||
impl Rule for EmptyInherit {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(inherit_stmt) = Inherit::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{Metadata, Report, Rule, Suggestion};
|
||||
use crate::{session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -34,7 +34,7 @@ use rnix::{
|
||||
struct EmptyLetIn;
|
||||
|
||||
impl Rule for EmptyLetIn {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(let_in_expr) = LetIn::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -43,7 +43,7 @@ use rnix::{
|
||||
struct EmptyPattern;
|
||||
|
||||
impl Rule for EmptyPattern {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(pattern) = Pattern::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{Metadata, Report, Rule, Suggestion};
|
||||
use crate::{session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -42,7 +42,7 @@ use rnix::{
|
||||
struct EtaReduction;
|
||||
|
||||
impl Rule for EtaReduction {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(lambda_expr) = Lambda::cast(node.clone());
|
||||
|
72
lib/src/lints/faster_groupby.rs
Normal file
72
lib/src/lints/faster_groupby.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use crate::{
|
||||
make,
|
||||
session::{SessionInfo, Version},
|
||||
Metadata, Report, Rule, Suggestion,
|
||||
};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
use rnix::{
|
||||
types::{Select, TypedNode},
|
||||
NodeOrToken, SyntaxElement, SyntaxKind,
|
||||
};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `lib.groupBy`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Nix 2.5 introduces `builtins.groupBy` which is faster and does
|
||||
/// not require a lib import.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```nix
|
||||
/// lib.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 6 ];
|
||||
/// # { big = [ 3 4 5 6 ]; small = [ 1 2 ]; }
|
||||
/// ```
|
||||
///
|
||||
/// Replace `lib.groupBy` with `builtins.groupBy`:
|
||||
///
|
||||
/// ```
|
||||
/// builtins.groupBy (x: if x > 2 then "big" else "small") [ 1 2 3 4 5 6 ];
|
||||
/// ```
|
||||
#[lint(
|
||||
name = "faster_groupby",
|
||||
note = "Found lib.groupBy",
|
||||
code = 15,
|
||||
match_with = SyntaxKind::NODE_SELECT
|
||||
)]
|
||||
struct FasterGroupBy;
|
||||
|
||||
impl Rule for FasterGroupBy {
|
||||
fn validate(&self, node: &SyntaxElement, sess: &SessionInfo) -> Option<Report> {
|
||||
let lint_version = "nix (Nix) 2.5".parse::<Version>().unwrap();
|
||||
if_chain! {
|
||||
if sess.version() >= &lint_version;
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(select_expr) = Select::cast(node.clone());
|
||||
if let Some(select_from) = select_expr.set();
|
||||
if let Some(group_by_attr) = select_expr.index();
|
||||
|
||||
// a heuristic to lint on nixpkgs.lib.groupBy
|
||||
// and lib.groupBy and its variants
|
||||
if select_from.text().to_string() != "builtins";
|
||||
if group_by_attr.text().to_string() == "groupBy";
|
||||
|
||||
then {
|
||||
let at = node.text_range();
|
||||
let replacement = {
|
||||
let builtins = make::ident("builtins");
|
||||
make::select(builtins.node(), &group_by_attr).node().clone()
|
||||
};
|
||||
let message = format!("Prefer `builtins.groupBy` over `{}.groupBy`", select_from);
|
||||
Some(
|
||||
self.report()
|
||||
.suggest(at, message, Suggestion::new(at, replacement)),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -44,7 +44,7 @@ use rnix::{
|
||||
struct ManualInherit;
|
||||
|
||||
impl Rule for ManualInherit {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(legacy_let) = LegacyLet::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -40,7 +40,7 @@ use rnix::{
|
||||
struct ManualInherit;
|
||||
|
||||
impl Rule for ManualInherit {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(key_value_stmt) = KeyValue::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -40,7 +40,7 @@ use rnix::{
|
||||
struct ManualInherit;
|
||||
|
||||
impl Rule for ManualInherit {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(key_value_stmt) = KeyValue::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{Metadata, Report, Rule, Suggestion};
|
||||
use crate::{session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -35,7 +35,7 @@ use rnix::{
|
||||
struct RedundantPatternBind;
|
||||
|
||||
impl Rule for RedundantPatternBind {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(pattern) = Pattern::cast(node.clone());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -40,7 +40,7 @@ use rnix::{
|
||||
struct UnquotedSplice;
|
||||
|
||||
impl Rule for UnquotedSplice {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if Dynamic::cast(node.clone()).is_some();
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{make, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{make, session::SessionInfo, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -46,7 +46,7 @@ use rnix::{types::TypedNode, NodeOrToken, SyntaxElement, SyntaxKind};
|
||||
struct UnquotedUri;
|
||||
|
||||
impl Rule for UnquotedUri {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Token(token) = node;
|
||||
then {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{Diagnostic, Metadata, Report, Rule, Suggestion};
|
||||
use crate::{session::SessionInfo, Diagnostic, Metadata, Report, Rule, Suggestion};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use macros::lint;
|
||||
@ -45,7 +45,7 @@ use rnix::{
|
||||
struct UselessParens;
|
||||
|
||||
impl Rule for UselessParens {
|
||||
fn validate(&self, node: &SyntaxElement) -> Option<Report> {
|
||||
fn validate(&self, node: &SyntaxElement, _sess: &SessionInfo) -> Option<Report> {
|
||||
if_chain! {
|
||||
if let NodeOrToken::Node(node) = node;
|
||||
if let Some(parsed_type_node) = ParsedType::cast(node.clone());
|
||||
|
103
lib/src/session.rs
Normal file
103
lib/src/session.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use std::{cmp::Ordering, str::FromStr};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Version {
|
||||
major: u16,
|
||||
minor: u16,
|
||||
patch: Option<u16>,
|
||||
}
|
||||
|
||||
impl Ord for Version {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let score = |v: &Version| v.major * 100 + v.minor * 10 + v.patch.unwrap_or(0);
|
||||
score(self).cmp(&score(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Version {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_number(s: &str) -> Option<u16> {
|
||||
s.chars()
|
||||
.take_while(|c| c.is_digit(10))
|
||||
.collect::<String>()
|
||||
.parse::<u16>()
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn parse_version(s: &str) -> Option<Version> {
|
||||
match s.split(' ').collect::<Vec<_>>().as_slice() {
|
||||
[_, _, version] => {
|
||||
let mut parts = version.split('.');
|
||||
let major = parse_number(parts.next()?)?;
|
||||
let minor = parse_number(parts.next()?)?;
|
||||
let patch = parts.next().map(|p| parse_number(p)).flatten();
|
||||
Some(Version {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
parse_version(s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct SessionInfo {
|
||||
nix_version: Version,
|
||||
}
|
||||
|
||||
impl SessionInfo {
|
||||
pub fn from_version(nix_version: Version) -> Self {
|
||||
Self { nix_version }
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &Version {
|
||||
&self.nix_version
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_nix_version() -> Option<Version> {
|
||||
"nix (Nix) 2.4pre20211006_53e4794".parse::<Version>().ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_trivial() {
|
||||
let v = "nix (Nix) 1.6.1".parse::<Version>().ok();
|
||||
assert!(v.is_some())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
let v = "nix (Nix) 2.4pre20211006_53e4794".parse::<Version>().ok();
|
||||
assert!(v.is_some())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compare_trivial() {
|
||||
let v1 = "nix (Nix) 1.6.1".parse::<Version>().ok();
|
||||
let v2 = "nix (Nix) 1.7.2".parse::<Version>().ok();
|
||||
assert!(v2 > v1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compare() {
|
||||
let v1 = "nix (Nix) 1.7".parse::<Version>().ok();
|
||||
let v2 = "nix (Nix) 2.4pre20211006_53e4794".parse::<Version>().ok();
|
||||
assert!(v2 >= v1);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user