aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock10
-rw-r--r--bin/Cargo.toml10
-rw-r--r--bin/src/config.rs168
-rw-r--r--bin/src/err.rs2
-rw-r--r--bin/src/explain.rs7
-rw-r--r--bin/src/fix.rs12
-rw-r--r--bin/src/fix/all.rs18
-rw-r--r--bin/src/fix/single.rs8
-rw-r--r--bin/src/lib.rs9
-rw-r--r--bin/src/lint.rs17
-rw-r--r--bin/src/utils.rs24
-rw-r--r--flake.nix2
-rw-r--r--lib/src/lib.rs19
-rw-r--r--lib/src/lints.rs4
-rw-r--r--lib/src/lints/collapsible_let_in.rs2
-rw-r--r--lib/src/lints/deprecated_is_null.rs2
-rw-r--r--lib/src/lints/empty_let_in.rs2
-rw-r--r--lib/src/lints/empty_pattern.rs2
-rw-r--r--lib/src/lints/eta_reduction.rs2
-rw-r--r--lib/src/lints/legacy_let_syntax.rs2
-rw-r--r--lib/src/lints/manual_inherit.rs2
-rw-r--r--lib/src/lints/manual_inherit_from.rs2
-rw-r--r--lib/src/lints/redundant_pattern_bind.rs2
-rw-r--r--lib/src/lints/unquoted_splice.rs2
-rw-r--r--lib/src/lints/unquoted_uri.rs2
-rw-r--r--lib/src/lints/useless_parens.rs2
26 files changed, 231 insertions, 103 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 08881fd..2da3c9d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -520,6 +520,7 @@ dependencies = [
520 "similar 2.1.0", 520 "similar 2.1.0",
521 "strip-ansi-escapes", 521 "strip-ansi-escapes",
522 "thiserror", 522 "thiserror",
523 "toml",
523 "vfs", 524 "vfs",
524] 525]
525 526
@@ -613,6 +614,15 @@ dependencies = [
613] 614]
614 615
615[[package]] 616[[package]]
617name = "toml"
618version = "0.5.8"
619source = "registry+https://github.com/rust-lang/crates.io-index"
620checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
621dependencies = [
622 "serde",
623]
624
625[[package]]
616name = "unicase" 626name = "unicase"
617version = "2.6.0" 627version = "2.6.0"
618source = "registry+https://github.com/rust-lang/crates.io-index" 628source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/bin/Cargo.toml b/bin/Cargo.toml
index 7c48083..c898792 100644
--- a/bin/Cargo.toml
+++ b/bin/Cargo.toml
@@ -23,14 +23,14 @@ thiserror = "1.0.30"
23similar = "2.1.0" 23similar = "2.1.0"
24vfs = { path = "../vfs" } 24vfs = { path = "../vfs" }
25lib = { path = "../lib" } 25lib = { path = "../lib" }
26 26toml = "0.5.8"
27[dependencies.serde_json]
28version = "1.0.68"
29optional = true
30 27
31[dependencies.serde] 28[dependencies.serde]
32version = "1.0.68" 29version = "1.0.68"
33features = [ "derive" ] 30features = [ "derive" ]
31
32[dependencies.serde_json]
33version = "1.0.68"
34optional = true 34optional = true
35 35
36[dev-dependencies] 36[dev-dependencies]
@@ -38,4 +38,4 @@ insta = "1.8.0"
38strip-ansi-escapes = "0.1.1" 38strip-ansi-escapes = "0.1.1"
39 39
40[features] 40[features]
41json = [ "lib/json-out", "serde_json", "serde" ] 41json = [ "lib/json-out", "serde_json" ]
diff --git a/bin/src/config.rs b/bin/src/config.rs
index d3944ac..b6310e6 100644
--- a/bin/src/config.rs
+++ b/bin/src/config.rs
@@ -1,8 +1,15 @@
1use std::{default::Default, fmt, fs, path::PathBuf, str::FromStr}; 1use std::{
2 default::Default,
3 fmt, fs,
4 path::{Path, PathBuf},
5 str::FromStr,
6};
2 7
3use crate::{dirs, err::ConfigErr}; 8use crate::{dirs, err::ConfigErr, utils, LintMap};
4 9
5use clap::Parser; 10use clap::Parser;
11use lib::LINTS;
12use serde::{Deserialize, Serialize};
6use vfs::ReadOnlyVfs; 13use vfs::ReadOnlyVfs;
7 14
8#[derive(Parser, Debug)] 15#[derive(Parser, Debug)]
@@ -43,6 +50,10 @@ pub struct Check {
43 #[clap(short = 'o', long, default_value_t, parse(try_from_str))] 50 #[clap(short = 'o', long, default_value_t, parse(try_from_str))]
44 pub format: OutFormat, 51 pub format: OutFormat,
45 52
53 /// Path to statix.toml
54 #[clap(short = 'c', long = "config", default_value = ".")]
55 pub conf_path: PathBuf,
56
46 /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout 57 /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout
47 #[clap(short, long = "stdin")] 58 #[clap(short, long = "stdin")]
48 pub streaming: bool, 59 pub streaming: bool,
@@ -65,6 +76,10 @@ impl Check {
65 vfs(files.collect::<Vec<_>>()) 76 vfs(files.collect::<Vec<_>>())
66 } 77 }
67 } 78 }
79
80 pub fn lints(&self) -> Result<LintMap, ConfigErr> {
81 lints(&self.conf_path)
82 }
68} 83}
69 84
70#[derive(Parser, Debug)] 85#[derive(Parser, Debug)]
@@ -85,6 +100,10 @@ pub struct Fix {
85 #[clap(short, long = "dry-run")] 100 #[clap(short, long = "dry-run")]
86 pub diff_only: bool, 101 pub diff_only: bool,
87 102
103 /// Path to statix.toml
104 #[clap(short = 'c', long = "config", default_value = ".")]
105 pub conf_path: PathBuf,
106
88 /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout 107 /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout
89 #[clap(short, long = "stdin")] 108 #[clap(short, long = "stdin")]
90 pub streaming: bool, 109 pub streaming: bool,
@@ -125,6 +144,10 @@ impl Fix {
125 FixOut::Write 144 FixOut::Write
126 } 145 }
127 } 146 }
147
148 pub fn lints(&self) -> Result<LintMap, ConfigErr> {
149 lints(&self.conf_path)
150 }
128} 151}
129 152
130#[derive(Parser, Debug)] 153#[derive(Parser, Debug)]
@@ -181,50 +204,6 @@ pub struct Explain {
181 pub target: u32, 204 pub target: u32,
182} 205}
183 206
184fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> {
185 let parts = src.split(',');
186 match parts.collect::<Vec<_>>().as_slice() {
187 [line, col] => {
188 let l = line
189 .parse::<usize>()
190 .map_err(|_| ConfigErr::InvalidPosition(src.to_owned()))?;
191 let c = col
192 .parse::<usize>()
193 .map_err(|_| ConfigErr::InvalidPosition(src.to_owned()))?;
194 Ok((l, c))
195 }
196 _ => Err(ConfigErr::InvalidPosition(src.to_owned())),
197 }
198}
199
200fn parse_warning_code(src: &str) -> Result<u32, ConfigErr> {
201 let mut char_stream = src.chars();
202 let severity = char_stream
203 .next()
204 .ok_or_else(|| ConfigErr::InvalidWarningCode(src.to_owned()))?
205 .to_ascii_lowercase();
206 match severity {
207 'w' => char_stream
208 .collect::<String>()
209 .parse::<u32>()
210 .map_err(|_| ConfigErr::InvalidWarningCode(src.to_owned())),
211 _ => Ok(0),
212 }
213}
214
215fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> {
216 let mut vfs = ReadOnlyVfs::default();
217 for file in files.iter() {
218 if let Ok(data) = fs::read_to_string(&file) {
219 let _id = vfs.alloc_file_id(&file);
220 vfs.set_file_contents(&file, data.as_bytes());
221 } else {
222 println!("{} contains non-utf8 content", file.display());
223 };
224 }
225 Ok(vfs)
226}
227
228#[derive(Debug, Copy, Clone)] 207#[derive(Debug, Copy, Clone)]
229pub enum OutFormat { 208pub enum OutFormat {
230 #[cfg(feature = "json")] 209 #[cfg(feature = "json")]
@@ -269,3 +248,100 @@ impl FromStr for OutFormat {
269 } 248 }
270 } 249 }
271} 250}
251
252#[derive(Serialize, Deserialize, Debug)]
253pub struct ConfFile {
254 disabled: Vec<String>,
255}
256
257impl Default for ConfFile {
258 fn default() -> Self {
259 let disabled = vec![];
260 Self { disabled }
261 }
262}
263
264impl ConfFile {
265 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, ConfigErr> {
266 let path = path.as_ref();
267 let config_file = fs::read_to_string(path).map_err(ConfigErr::InvalidPath)?;
268 toml::de::from_str(&config_file).map_err(|err| {
269 let pos = err.line_col();
270 let msg = if let Some((line, col)) = pos {
271 format!("line {}, col {}", line, col)
272 } else {
273 "unknown".to_string()
274 };
275 ConfigErr::ConfFileParse(msg)
276 })
277 }
278 pub fn discover<P: AsRef<Path>>(path: P) -> Result<Self, ConfigErr> {
279 let cannonical_path = fs::canonicalize(path.as_ref()).map_err(ConfigErr::InvalidPath)?;
280 for p in cannonical_path.ancestors() {
281 let statix_toml_path = p.with_file_name("statix.toml");
282 if statix_toml_path.exists() {
283 return Self::from_path(statix_toml_path);
284 };
285 }
286 Ok(Self::default())
287 }
288 pub fn dump(&self) -> String {
289 toml::ser::to_string_pretty(&self).unwrap()
290 }
291}
292
293fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> {
294 let parts = src.split(',');
295 match parts.collect::<Vec<_>>().as_slice() {
296 [line, col] => {
297 let do_parse = |val: &str| {
298 val.parse::<usize>()
299 .map_err(|_| ConfigErr::InvalidPosition(src.to_owned()))
300 };
301 let l = do_parse(line)?;
302 let c = do_parse(col)?;
303 Ok((l, c))
304 }
305 _ => Err(ConfigErr::InvalidPosition(src.to_owned())),
306 }
307}
308
309fn parse_warning_code(src: &str) -> Result<u32, ConfigErr> {
310 let mut char_stream = src.chars();
311 let severity = char_stream
312 .next()
313 .ok_or_else(|| ConfigErr::InvalidWarningCode(src.to_owned()))?
314 .to_ascii_lowercase();
315 match severity {
316 'w' => char_stream
317 .collect::<String>()
318 .parse::<u32>()
319 .map_err(|_| ConfigErr::InvalidWarningCode(src.to_owned())),
320 _ => Ok(0),
321 }
322}
323
324fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> {
325 let mut vfs = ReadOnlyVfs::default();
326 for file in files.iter() {
327 if let Ok(data) = fs::read_to_string(&file) {
328 let _id = vfs.alloc_file_id(&file);
329 vfs.set_file_contents(&file, data.as_bytes());
330 } else {
331 println!("{} contains non-utf8 content", file.display());
332 };
333 }
334 Ok(vfs)
335}
336
337fn lints(conf_path: &PathBuf) -> Result<LintMap, ConfigErr> {
338 let config_file = ConfFile::discover(conf_path)?;
339 Ok(utils::lint_map_of(
340 (&*LINTS)
341 .into_iter()
342 .filter(|l| !config_file.disabled.iter().any(|check| check == l.name()))
343 .cloned()
344 .collect::<Vec<_>>()
345 .as_slice(),
346 ))
347}
diff --git a/bin/src/err.rs b/bin/src/err.rs
index b776d9f..f44d27b 100644
--- a/bin/src/err.rs
+++ b/bin/src/err.rs
@@ -14,6 +14,8 @@ pub enum ConfigErr {
14 InvalidPosition(String), 14 InvalidPosition(String),
15 #[error("unable to parse `{0}` as warning code")] 15 #[error("unable to parse `{0}` as warning code")]
16 InvalidWarningCode(String), 16 InvalidWarningCode(String),
17 #[error("unable to parse config file, error at: `{0}`")]
18 ConfFileParse(String),
17} 19}
18 20
19// #[derive(Error, Debug)] 21// #[derive(Error, Debug)]
diff --git a/bin/src/explain.rs b/bin/src/explain.rs
index de171aa..bcb8eed 100644
--- a/bin/src/explain.rs
+++ b/bin/src/explain.rs
@@ -1,11 +1,10 @@
1use crate::err::ExplainErr; 1use crate::{err::ExplainErr, utils};
2
3use lib::LINTS;
4 2
5pub fn explain(code: u32) -> Result<&'static str, ExplainErr> { 3pub fn explain(code: u32) -> Result<&'static str, ExplainErr> {
4 let lints = utils::lint_map();
6 match code { 5 match code {
7 0 => Ok("syntax error"), 6 0 => Ok("syntax error"),
8 _ => LINTS 7 _ => lints
9 .values() 8 .values()
10 .flatten() 9 .flatten()
11 .find(|l| l.code() == code) 10 .find(|l| l.code() == code)
diff --git a/bin/src/fix.rs b/bin/src/fix.rs
index e4ea94d..4268567 100644
--- a/bin/src/fix.rs
+++ b/bin/src/fix.rs
@@ -1,19 +1,21 @@
1use std::borrow::Cow; 1use std::borrow::Cow;
2 2
3use crate::LintMap;
4
3use rnix::TextRange; 5use rnix::TextRange;
4 6
5mod all; 7mod all;
6use all::all; 8use all::all_with;
7 9
8mod single; 10mod single;
9use single::single; 11use single::single;
10 12
11type Source<'a> = Cow<'a, str>; 13type Source<'a> = Cow<'a, str>;
12 14
13#[derive(Debug)]
14pub struct FixResult<'a> { 15pub struct FixResult<'a> {
15 pub src: Source<'a>, 16 pub src: Source<'a>,
16 pub fixed: Vec<Fixed>, 17 pub fixed: Vec<Fixed>,
18 pub lints: &'a LintMap,
17} 19}
18 20
19#[derive(Debug, Clone)] 21#[derive(Debug, Clone)]
@@ -23,10 +25,11 @@ pub struct Fixed {
23} 25}
24 26
25impl<'a> FixResult<'a> { 27impl<'a> FixResult<'a> {
26 fn empty(src: Source<'a>) -> Self { 28 fn empty(src: Source<'a>, lints: &'a LintMap) -> Self {
27 Self { 29 Self {
28 src, 30 src,
29 fixed: Vec::new(), 31 fixed: Vec::new(),
32 lints,
30 } 33 }
31 } 34 }
32} 35}
@@ -43,8 +46,9 @@ pub mod main {
43 46
44 pub fn all(fix_config: FixConfig) -> Result<(), StatixErr> { 47 pub fn all(fix_config: FixConfig) -> Result<(), StatixErr> {
45 let vfs = fix_config.vfs()?; 48 let vfs = fix_config.vfs()?;
49 let lints = fix_config.lints()?;
46 for entry in vfs.iter() { 50 for entry in vfs.iter() {
47 match (fix_config.out(), super::all(entry.contents)) { 51 match (fix_config.out(), super::all_with(entry.contents, &lints)) {
48 (FixOut::Diff, fix_result) => { 52 (FixOut::Diff, fix_result) => {
49 let src = fix_result 53 let src = fix_result
50 .map(|r| r.src) 54 .map(|r| r.src)
diff --git a/bin/src/fix/all.rs b/bin/src/fix/all.rs
index 7f04f2c..bbc39e8 100644
--- a/bin/src/fix/all.rs
+++ b/bin/src/fix/all.rs
@@ -1,18 +1,21 @@
1use std::borrow::Cow; 1use std::borrow::Cow;
2 2
3use lib::{Report, LINTS}; 3use lib::Report;
4use rnix::{parser::ParseError as RnixParseErr, WalkEvent}; 4use rnix::{parser::ParseError as RnixParseErr, WalkEvent};
5 5
6use crate::fix::{FixResult, Fixed}; 6use crate::{
7 fix::{FixResult, Fixed},
8 LintMap,
9};
7 10
8fn collect_fixes(source: &str) -> Result<Vec<Report>, RnixParseErr> { 11fn collect_fixes(source: &str, lints: &LintMap) -> Result<Vec<Report>, RnixParseErr> {
9 let parsed = rnix::parse(source).as_result()?; 12 let parsed = rnix::parse(source).as_result()?;
10 13
11 Ok(parsed 14 Ok(parsed
12 .node() 15 .node()
13 .preorder_with_tokens() 16 .preorder_with_tokens()
14 .filter_map(|event| match event { 17 .filter_map(|event| match event {
15 WalkEvent::Enter(child) => LINTS.get(&child.kind()).map(|rules| { 18 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
16 rules 19 rules
17 .iter() 20 .iter()
18 .filter_map(|rule| rule.validate(&child)) 21 .filter_map(|rule| rule.validate(&child))
@@ -54,7 +57,7 @@ fn reorder(mut reports: Vec<Report>) -> Vec<Report> {
54impl<'a> Iterator for FixResult<'a> { 57impl<'a> Iterator for FixResult<'a> {
55 type Item = FixResult<'a>; 58 type Item = FixResult<'a>;
56 fn next(&mut self) -> Option<Self::Item> { 59 fn next(&mut self) -> Option<Self::Item> {
57 let all_reports = collect_fixes(&self.src).ok()?; 60 let all_reports = collect_fixes(&self.src, &self.lints).ok()?;
58 if all_reports.is_empty() { 61 if all_reports.is_empty() {
59 return None; 62 return None;
60 } 63 }
@@ -74,13 +77,14 @@ impl<'a> Iterator for FixResult<'a> {
74 Some(FixResult { 77 Some(FixResult {
75 src: self.src.clone(), 78 src: self.src.clone(),
76 fixed, 79 fixed,
80 lints: self.lints,
77 }) 81 })
78 } 82 }
79} 83}
80 84
81pub fn all(src: &str) -> Option<FixResult> { 85pub fn all_with<'a>(src: &'a str, lints: &'a LintMap) -> Option<FixResult<'a>> {
82 let src = Cow::from(src); 86 let src = Cow::from(src);
83 let _ = rnix::parse(&src).as_result().ok()?; 87 let _ = rnix::parse(&src).as_result().ok()?;
84 let initial = FixResult::empty(src); 88 let initial = FixResult::empty(src, lints);
85 initial.into_iter().last() 89 initial.into_iter().last()
86} 90}
diff --git a/bin/src/fix/single.rs b/bin/src/fix/single.rs
index b91dc45..d95cfda 100644
--- a/bin/src/fix/single.rs
+++ b/bin/src/fix/single.rs
@@ -1,10 +1,9 @@
1use std::{borrow::Cow, convert::TryFrom}; 1use std::{borrow::Cow, convert::TryFrom};
2 2
3use lib::{Report, LINTS}; 3use lib::Report;
4use rnix::{TextSize, WalkEvent}; 4use rnix::{TextSize, WalkEvent};
5 5
6use crate::err::SingleFixErr; 6use crate::{err::SingleFixErr, fix::Source, utils};
7use crate::fix::Source;
8 7
9pub struct SingleFixResult<'δ> { 8pub struct SingleFixResult<'δ> {
10 pub src: Source<'δ>, 9 pub src: Source<'δ>,
@@ -31,12 +30,13 @@ fn pos_to_byte(line: usize, col: usize, src: &str) -> Result<TextSize, SingleFix
31fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> { 30fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> {
32 // 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
33 let parsed = rnix::parse(src); 32 let parsed = rnix::parse(src);
33 let lints = utils::lint_map();
34 34
35 parsed 35 parsed
36 .node() 36 .node()
37 .preorder_with_tokens() 37 .preorder_with_tokens()
38 .filter_map(|event| match event { 38 .filter_map(|event| match event {
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))
diff --git a/bin/src/lib.rs b/bin/src/lib.rs
index 49c1a41..0105334 100644
--- a/bin/src/lib.rs
+++ b/bin/src/lib.rs
@@ -5,3 +5,12 @@ pub mod explain;
5pub mod fix; 5pub mod fix;
6pub mod lint; 6pub mod lint;
7pub mod traits; 7pub mod traits;
8
9mod utils;
10
11use std::collections::HashMap;
12
13use lib::Lint;
14use rnix::SyntaxKind;
15
16pub type LintMap = HashMap<SyntaxKind, Vec<&'static Box<dyn Lint>>>;
diff --git a/bin/src/lint.rs b/bin/src/lint.rs
index 1138c23..3482d46 100644
--- a/bin/src/lint.rs
+++ b/bin/src/lint.rs
@@ -1,4 +1,6 @@
1use lib::{Report, LINTS}; 1use crate::{utils, LintMap};
2
3use lib::Report;
2use rnix::WalkEvent; 4use rnix::WalkEvent;
3use vfs::{FileId, VfsEntry}; 5use vfs::{FileId, VfsEntry};
4 6
@@ -8,18 +10,17 @@ pub struct LintResult {
8 pub reports: Vec<Report>, 10 pub reports: Vec<Report>,
9} 11}
10 12
11pub fn lint(vfs_entry: VfsEntry) -> LintResult { 13pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap) -> LintResult {
12 let file_id = vfs_entry.file_id; 14 let file_id = vfs_entry.file_id;
13 let source = vfs_entry.contents; 15 let source = vfs_entry.contents;
14 let parsed = rnix::parse(source); 16 let parsed = rnix::parse(source);
15 17
16 let error_reports = parsed.errors().into_iter().map(Report::from_parse_err); 18 let error_reports = parsed.errors().into_iter().map(Report::from_parse_err);
17
18 let reports = parsed 19 let reports = parsed
19 .node() 20 .node()
20 .preorder_with_tokens() 21 .preorder_with_tokens()
21 .filter_map(|event| match event { 22 .filter_map(|event| match event {
22 WalkEvent::Enter(child) => LINTS.get(&child.kind()).map(|rules| { 23 WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| {
23 rules 24 rules
24 .iter() 25 .iter()
25 .filter_map(|rule| rule.validate(&child)) 26 .filter_map(|rule| rule.validate(&child))
@@ -34,15 +35,21 @@ pub fn lint(vfs_entry: VfsEntry) -> LintResult {
34 LintResult { file_id, reports } 35 LintResult { file_id, reports }
35} 36}
36 37
38pub fn lint(vfs_entry: VfsEntry) -> LintResult {
39 lint_with(vfs_entry, &utils::lint_map())
40}
41
37pub mod main { 42pub mod main {
38 use std::io; 43 use std::io;
39 44
40 use super::lint; 45 use super::lint_with;
41 use crate::{config::Check as CheckConfig, err::StatixErr, traits::WriteDiagnostic}; 46 use crate::{config::Check as CheckConfig, err::StatixErr, traits::WriteDiagnostic};
42 47
43 pub fn main(check_config: CheckConfig) -> Result<(), StatixErr> { 48 pub fn main(check_config: CheckConfig) -> Result<(), StatixErr> {
44 let vfs = check_config.vfs()?; 49 let vfs = check_config.vfs()?;
45 let mut stdout = io::stdout(); 50 let mut stdout = io::stdout();
51 let lints = check_config.lints()?;
52 let lint = |vfs_entry| lint_with(vfs_entry, &lints);
46 vfs.iter().map(lint).for_each(|r| { 53 vfs.iter().map(lint).for_each(|r| {
47 stdout.write(&r, &vfs, check_config.format).unwrap(); 54 stdout.write(&r, &vfs, check_config.format).unwrap();
48 }); 55 });
diff --git a/bin/src/utils.rs b/bin/src/utils.rs
new file mode 100644
index 0000000..747a761
--- /dev/null
+++ b/bin/src/utils.rs
@@ -0,0 +1,24 @@
1use std::collections::HashMap;
2
3use lib::{Lint, LINTS};
4use rnix::SyntaxKind;
5
6pub fn lint_map_of(
7 lints: &[&'static Box<dyn Lint>],
8) -> HashMap<SyntaxKind, Vec<&'static Box<dyn Lint>>> {
9 let mut map = HashMap::new();
10 for lint in lints.iter() {
11 let lint = *lint;
12 let matches = lint.match_kind();
13 for m in matches {
14 map.entry(m)
15 .and_modify(|v: &mut Vec<_>| v.push(lint))
16 .or_insert_with(|| vec![lint]);
17 }
18 }
19 map
20}
21
22pub fn lint_map() -> HashMap<SyntaxKind, Vec<&'static Box<dyn Lint>>> {
23 lint_map_of(&*LINTS)
24}
diff --git a/flake.nix b/flake.nix
index b166817..241eb2b 100644
--- a/flake.nix
+++ b/flake.nix
@@ -97,7 +97,7 @@
97 in 97 in
98 pkgs.mkShell { 98 pkgs.mkShell {
99 nativeBuildInputs = [ 99 nativeBuildInputs = [
100 pkgs.cargo-watch 100 pkgs.bacon
101 pkgs.cargo-insta 101 pkgs.cargo-insta
102 rust-analyzer 102 rust-analyzer
103 toolchain 103 toolchain
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index 5347666..d96d9eb 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -255,31 +255,24 @@ pub trait Lint: Metadata + Explain + Rule + Send + Sync {}
255/// 255///
256/// See `lints.rs` for usage. 256/// See `lints.rs` for usage.
257#[macro_export] 257#[macro_export]
258macro_rules! lint_map { 258macro_rules! lints {
259 ($($s:ident),*,) => { 259 ($($s:ident),*,) => {
260 lint_map!($($s),*); 260 lints!($($s),*);
261 }; 261 };
262 ($($s:ident),*) => { 262 ($($s:ident),*) => {
263 use ::std::collections::HashMap;
264 use ::rnix::SyntaxKind;
265 $( 263 $(
266 mod $s; 264 mod $s;
267 )* 265 )*
268 ::lazy_static::lazy_static! { 266 ::lazy_static::lazy_static! {
269 pub static ref LINTS: HashMap<SyntaxKind, Vec<&'static Box<dyn $crate::Lint>>> = { 267 pub static ref LINTS: Vec<&'static Box<dyn $crate::Lint>> = {
270 let mut map = HashMap::new(); 268 let mut v = Vec::new();
271 $( 269 $(
272 { 270 {
273 let temp_lint = &*$s::LINT; 271 let temp_lint = &*$s::LINT;
274 let temp_matches = temp_lint.match_kind(); 272 v.push(temp_lint);
275 for temp_match in temp_matches {
276 map.entry(temp_match)
277 .and_modify(|v: &mut Vec<_>| v.push(temp_lint))
278 .or_insert_with(|| vec![temp_lint]);
279 }
280 } 273 }
281 )* 274 )*
282 map 275 v
283 }; 276 };
284 } 277 }
285 } 278 }
diff --git a/lib/src/lints.rs b/lib/src/lints.rs
index 8b0f92b..b4d4af3 100644
--- a/lib/src/lints.rs
+++ b/lib/src/lints.rs
@@ -1,6 +1,6 @@
1use crate::lint_map; 1use crate::lints;
2 2
3lint_map! { 3lints! {
4 bool_comparison, 4 bool_comparison,
5 empty_let_in, 5 empty_let_in,
6 manual_inherit, 6 manual_inherit,
diff --git a/lib/src/lints/collapsible_let_in.rs b/lib/src/lints/collapsible_let_in.rs
index 26a3102..aa7e5a7 100644
--- a/lib/src/lints/collapsible_let_in.rs
+++ b/lib/src/lints/collapsible_let_in.rs
@@ -37,7 +37,7 @@ use rowan::Direction;
37/// a + b 37/// a + b
38/// ``` 38/// ```
39#[lint( 39#[lint(
40 name = "collapsible let in", 40 name = "collapsible_let_in",
41 note = "These let-in expressions are collapsible", 41 note = "These let-in expressions are collapsible",
42 code = 6, 42 code = 6,
43 match_with = SyntaxKind::NODE_LET_IN 43 match_with = SyntaxKind::NODE_LET_IN
diff --git a/lib/src/lints/deprecated_is_null.rs b/lib/src/lints/deprecated_is_null.rs
index fce6931..c814e87 100644
--- a/lib/src/lints/deprecated_is_null.rs
+++ b/lib/src/lints/deprecated_is_null.rs
@@ -27,7 +27,7 @@ use rnix::{
27/// e == null 27/// e == null
28/// ``` 28/// ```
29#[lint( 29#[lint(
30 name = "deprecated isNull", 30 name = "deprecated_is_null",
31 note = "Found usage of deprecated builtin isNull", 31 note = "Found usage of deprecated builtin isNull",
32 code = 13, 32 code = 13,
33 match_with = SyntaxKind::NODE_APPLY 33 match_with = SyntaxKind::NODE_APPLY
diff --git a/lib/src/lints/empty_let_in.rs b/lib/src/lints/empty_let_in.rs
index 5a51ea3..d33d0ae 100644
--- a/lib/src/lints/empty_let_in.rs
+++ b/lib/src/lints/empty_let_in.rs
@@ -26,7 +26,7 @@ use rnix::{
26/// pkgs.statix 26/// pkgs.statix
27/// ``` 27/// ```
28#[lint( 28#[lint(
29 name = "empty let-in", 29 name = "empty_let_in",
30 note = "Useless let-in expression", 30 note = "Useless let-in expression",
31 code = 2, 31 code = 2,
32 match_with = SyntaxKind::NODE_LET_IN 32 match_with = SyntaxKind::NODE_LET_IN
diff --git a/lib/src/lints/empty_pattern.rs b/lib/src/lints/empty_pattern.rs
index c0cb5a4..f66a3b1 100644
--- a/lib/src/lints/empty_pattern.rs
+++ b/lib/src/lints/empty_pattern.rs
@@ -35,7 +35,7 @@ use rnix::{
35/// }; 35/// };
36/// ``` 36/// ```
37#[lint( 37#[lint(
38 name = "empty pattern", 38 name = "empty_pattern",
39 note = "Found empty pattern in function argument", 39 note = "Found empty pattern in function argument",
40 code = 10, 40 code = 10,
41 match_with = SyntaxKind::NODE_PATTERN 41 match_with = SyntaxKind::NODE_PATTERN
diff --git a/lib/src/lints/eta_reduction.rs b/lib/src/lints/eta_reduction.rs
index 454c78f..580f4a0 100644
--- a/lib/src/lints/eta_reduction.rs
+++ b/lib/src/lints/eta_reduction.rs
@@ -34,7 +34,7 @@ use rnix::{
34/// map double [ 1 2 3 ] 34/// map double [ 1 2 3 ]
35/// ``` 35/// ```
36#[lint( 36#[lint(
37 name = "eta reduction", 37 name = "eta_reduction",
38 note = "This function expression is eta reducible", 38 note = "This function expression is eta reducible",
39 code = 7, 39 code = 7,
40 match_with = SyntaxKind::NODE_LAMBDA 40 match_with = SyntaxKind::NODE_LAMBDA
diff --git a/lib/src/lints/legacy_let_syntax.rs b/lib/src/lints/legacy_let_syntax.rs
index cdd012d..5d0028b 100644
--- a/lib/src/lints/legacy_let_syntax.rs
+++ b/lib/src/lints/legacy_let_syntax.rs
@@ -36,7 +36,7 @@ use rnix::{
36/// }.body 36/// }.body
37/// ``` 37/// ```
38#[lint( 38#[lint(
39 name = "legacy let syntax", 39 name = "legacy_let_syntax",
40 note = "Using undocumented `let` syntax", 40 note = "Using undocumented `let` syntax",
41 code = 5, 41 code = 5,
42 match_with = SyntaxKind::NODE_LEGACY_LET 42 match_with = SyntaxKind::NODE_LEGACY_LET
diff --git a/lib/src/lints/manual_inherit.rs b/lib/src/lints/manual_inherit.rs
index 1813af3..7717dc9 100644
--- a/lib/src/lints/manual_inherit.rs
+++ b/lib/src/lints/manual_inherit.rs
@@ -32,7 +32,7 @@ use rnix::{
32/// { inherit a; b = 3; } 32/// { inherit a; b = 3; }
33/// ``` 33/// ```
34#[lint( 34#[lint(
35 name = "manual inherit", 35 name = "manual_inherit",
36 note = "Assignment instead of inherit", 36 note = "Assignment instead of inherit",
37 code = 3, 37 code = 3,
38 match_with = SyntaxKind::NODE_KEY_VALUE 38 match_with = SyntaxKind::NODE_KEY_VALUE
diff --git a/lib/src/lints/manual_inherit_from.rs b/lib/src/lints/manual_inherit_from.rs
index ab2579d..05d6bc8 100644
--- a/lib/src/lints/manual_inherit_from.rs
+++ b/lib/src/lints/manual_inherit_from.rs
@@ -32,7 +32,7 @@ use rnix::{
32/// null 32/// null
33/// ``` 33/// ```
34#[lint( 34#[lint(
35 name = "manual inherit from", 35 name = "manual_inherit_from",
36 note = "Assignment instead of inherit from", 36 note = "Assignment instead of inherit from",
37 code = 4, 37 code = 4,
38 match_with = SyntaxKind::NODE_KEY_VALUE 38 match_with = SyntaxKind::NODE_KEY_VALUE
diff --git a/lib/src/lints/redundant_pattern_bind.rs b/lib/src/lints/redundant_pattern_bind.rs
index 882a660..88ce4b0 100644
--- a/lib/src/lints/redundant_pattern_bind.rs
+++ b/lib/src/lints/redundant_pattern_bind.rs
@@ -27,7 +27,7 @@ use rnix::{
27/// inputs: inputs.nixpkgs 27/// inputs: inputs.nixpkgs
28/// ``` 28/// ```
29#[lint( 29#[lint(
30 name = "redundant pattern bind", 30 name = "redundant_pattern_bind",
31 note = "Found redundant pattern bind in function argument", 31 note = "Found redundant pattern bind in function argument",
32 code = 11, 32 code = 11,
33 match_with = SyntaxKind::NODE_PATTERN 33 match_with = SyntaxKind::NODE_PATTERN
diff --git a/lib/src/lints/unquoted_splice.rs b/lib/src/lints/unquoted_splice.rs
index ce269b6..7649fbc 100644
--- a/lib/src/lints/unquoted_splice.rs
+++ b/lib/src/lints/unquoted_splice.rs
@@ -32,7 +32,7 @@ use rnix::{
32/// pkgs 32/// pkgs
33/// ``` 33/// ```
34#[lint( 34#[lint(
35 name = "unquoted splice", 35 name = "unquoted_splice",
36 note = "Found unquoted splice expression", 36 note = "Found unquoted splice expression",
37 code = 9, 37 code = 9,
38 match_with = SyntaxKind::NODE_DYNAMIC 38 match_with = SyntaxKind::NODE_DYNAMIC
diff --git a/lib/src/lints/unquoted_uri.rs b/lib/src/lints/unquoted_uri.rs
index b111f78..8835338 100644
--- a/lib/src/lints/unquoted_uri.rs
+++ b/lib/src/lints/unquoted_uri.rs
@@ -38,7 +38,7 @@ use rnix::{types::TypedNode, NodeOrToken, SyntaxElement, SyntaxKind};
38/// } 38/// }
39/// ``` 39/// ```
40#[lint( 40#[lint(
41 name = "unquoted uri", 41 name = "unquoted_uri",
42 note = "Found unquoted URI expression", 42 note = "Found unquoted URI expression",
43 code = 12, 43 code = 12,
44 match_with = SyntaxKind::TOKEN_URI 44 match_with = SyntaxKind::TOKEN_URI
diff --git a/lib/src/lints/useless_parens.rs b/lib/src/lints/useless_parens.rs
index dccc717..09d6f04 100644
--- a/lib/src/lints/useless_parens.rs
+++ b/lib/src/lints/useless_parens.rs
@@ -33,7 +33,7 @@ use rnix::{
33/// 2 + 3 33/// 2 + 3
34/// ``` 34/// ```
35#[lint( 35#[lint(
36 name = "useless parens", 36 name = "useless_parens",
37 note = "These parentheses can be omitted", 37 note = "These parentheses can be omitted",
38 code = 8, 38 code = 8,
39 match_with = [ 39 match_with = [