diff options
-rw-r--r-- | Cargo.lock | 10 | ||||
-rw-r--r-- | bin/Cargo.toml | 10 | ||||
-rw-r--r-- | bin/src/config.rs | 168 | ||||
-rw-r--r-- | bin/src/err.rs | 2 | ||||
-rw-r--r-- | bin/src/explain.rs | 7 | ||||
-rw-r--r-- | bin/src/fix.rs | 12 | ||||
-rw-r--r-- | bin/src/fix/all.rs | 18 | ||||
-rw-r--r-- | bin/src/fix/single.rs | 8 | ||||
-rw-r--r-- | bin/src/lib.rs | 9 | ||||
-rw-r--r-- | bin/src/lint.rs | 17 | ||||
-rw-r--r-- | bin/src/utils.rs | 24 | ||||
-rw-r--r-- | flake.nix | 2 | ||||
-rw-r--r-- | lib/src/lib.rs | 19 | ||||
-rw-r--r-- | lib/src/lints.rs | 4 | ||||
-rw-r--r-- | lib/src/lints/collapsible_let_in.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/deprecated_is_null.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/empty_let_in.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/empty_pattern.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/eta_reduction.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/legacy_let_syntax.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/manual_inherit.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/manual_inherit_from.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/redundant_pattern_bind.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/unquoted_splice.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/unquoted_uri.rs | 2 | ||||
-rw-r--r-- | lib/src/lints/useless_parens.rs | 2 |
26 files changed, 231 insertions, 103 deletions
@@ -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]] |
617 | name = "toml" | ||
618 | version = "0.5.8" | ||
619 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
620 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" | ||
621 | dependencies = [ | ||
622 | "serde", | ||
623 | ] | ||
624 | |||
625 | [[package]] | ||
616 | name = "unicase" | 626 | name = "unicase" |
617 | version = "2.6.0" | 627 | version = "2.6.0" |
618 | source = "registry+https://github.com/rust-lang/crates.io-index" | 628 | source = "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" | |||
23 | similar = "2.1.0" | 23 | similar = "2.1.0" |
24 | vfs = { path = "../vfs" } | 24 | vfs = { path = "../vfs" } |
25 | lib = { path = "../lib" } | 25 | lib = { path = "../lib" } |
26 | 26 | toml = "0.5.8" | |
27 | [dependencies.serde_json] | ||
28 | version = "1.0.68" | ||
29 | optional = true | ||
30 | 27 | ||
31 | [dependencies.serde] | 28 | [dependencies.serde] |
32 | version = "1.0.68" | 29 | version = "1.0.68" |
33 | features = [ "derive" ] | 30 | features = [ "derive" ] |
31 | |||
32 | [dependencies.serde_json] | ||
33 | version = "1.0.68" | ||
34 | optional = true | 34 | optional = true |
35 | 35 | ||
36 | [dev-dependencies] | 36 | [dev-dependencies] |
@@ -38,4 +38,4 @@ insta = "1.8.0" | |||
38 | strip-ansi-escapes = "0.1.1" | 38 | strip-ansi-escapes = "0.1.1" |
39 | 39 | ||
40 | [features] | 40 | [features] |
41 | json = [ "lib/json-out", "serde_json", "serde" ] | 41 | json = [ "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 @@ | |||
1 | use std::{default::Default, fmt, fs, path::PathBuf, str::FromStr}; | 1 | use std::{ |
2 | default::Default, | ||
3 | fmt, fs, | ||
4 | path::{Path, PathBuf}, | ||
5 | str::FromStr, | ||
6 | }; | ||
2 | 7 | ||
3 | use crate::{dirs, err::ConfigErr}; | 8 | use crate::{dirs, err::ConfigErr, utils, LintMap}; |
4 | 9 | ||
5 | use clap::Parser; | 10 | use clap::Parser; |
11 | use lib::LINTS; | ||
12 | use serde::{Deserialize, Serialize}; | ||
6 | use vfs::ReadOnlyVfs; | 13 | use 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 | ||
184 | fn 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 | |||
200 | fn 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 | |||
215 | fn 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)] |
229 | pub enum OutFormat { | 208 | pub 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)] | ||
253 | pub struct ConfFile { | ||
254 | disabled: Vec<String>, | ||
255 | } | ||
256 | |||
257 | impl Default for ConfFile { | ||
258 | fn default() -> Self { | ||
259 | let disabled = vec![]; | ||
260 | Self { disabled } | ||
261 | } | ||
262 | } | ||
263 | |||
264 | impl 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 | |||
293 | fn 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 | |||
309 | fn 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 | |||
324 | fn 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 | |||
337 | fn 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 @@ | |||
1 | use crate::err::ExplainErr; | 1 | use crate::{err::ExplainErr, utils}; |
2 | |||
3 | use lib::LINTS; | ||
4 | 2 | ||
5 | pub fn explain(code: u32) -> Result<&'static str, ExplainErr> { | 3 | pub 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 @@ | |||
1 | use std::borrow::Cow; | 1 | use std::borrow::Cow; |
2 | 2 | ||
3 | use crate::LintMap; | ||
4 | |||
3 | use rnix::TextRange; | 5 | use rnix::TextRange; |
4 | 6 | ||
5 | mod all; | 7 | mod all; |
6 | use all::all; | 8 | use all::all_with; |
7 | 9 | ||
8 | mod single; | 10 | mod single; |
9 | use single::single; | 11 | use single::single; |
10 | 12 | ||
11 | type Source<'a> = Cow<'a, str>; | 13 | type Source<'a> = Cow<'a, str>; |
12 | 14 | ||
13 | #[derive(Debug)] | ||
14 | pub struct FixResult<'a> { | 15 | pub 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 | ||
25 | impl<'a> FixResult<'a> { | 27 | impl<'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 @@ | |||
1 | use std::borrow::Cow; | 1 | use std::borrow::Cow; |
2 | 2 | ||
3 | use lib::{Report, LINTS}; | 3 | use lib::Report; |
4 | use rnix::{parser::ParseError as RnixParseErr, WalkEvent}; | 4 | use rnix::{parser::ParseError as RnixParseErr, WalkEvent}; |
5 | 5 | ||
6 | use crate::fix::{FixResult, Fixed}; | 6 | use crate::{ |
7 | fix::{FixResult, Fixed}, | ||
8 | LintMap, | ||
9 | }; | ||
7 | 10 | ||
8 | fn collect_fixes(source: &str) -> Result<Vec<Report>, RnixParseErr> { | 11 | fn 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> { | |||
54 | impl<'a> Iterator for FixResult<'a> { | 57 | impl<'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 | ||
81 | pub fn all(src: &str) -> Option<FixResult> { | 85 | pub 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 @@ | |||
1 | use std::{borrow::Cow, convert::TryFrom}; | 1 | use std::{borrow::Cow, convert::TryFrom}; |
2 | 2 | ||
3 | use lib::{Report, LINTS}; | 3 | use lib::Report; |
4 | use rnix::{TextSize, WalkEvent}; | 4 | use rnix::{TextSize, WalkEvent}; |
5 | 5 | ||
6 | use crate::err::SingleFixErr; | 6 | use crate::{err::SingleFixErr, fix::Source, utils}; |
7 | use crate::fix::Source; | ||
8 | 7 | ||
9 | pub struct SingleFixResult<'δ> { | 8 | pub 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 | |||
31 | fn find(offset: TextSize, src: &str) -> Result<Report, SingleFixErr> { | 30 | fn 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; | |||
5 | pub mod fix; | 5 | pub mod fix; |
6 | pub mod lint; | 6 | pub mod lint; |
7 | pub mod traits; | 7 | pub mod traits; |
8 | |||
9 | mod utils; | ||
10 | |||
11 | use std::collections::HashMap; | ||
12 | |||
13 | use lib::Lint; | ||
14 | use rnix::SyntaxKind; | ||
15 | |||
16 | pub 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 @@ | |||
1 | use lib::{Report, LINTS}; | 1 | use crate::{utils, LintMap}; |
2 | |||
3 | use lib::Report; | ||
2 | use rnix::WalkEvent; | 4 | use rnix::WalkEvent; |
3 | use vfs::{FileId, VfsEntry}; | 5 | use 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 | ||
11 | pub fn lint(vfs_entry: VfsEntry) -> LintResult { | 13 | pub 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 | ||
38 | pub fn lint(vfs_entry: VfsEntry) -> LintResult { | ||
39 | lint_with(vfs_entry, &utils::lint_map()) | ||
40 | } | ||
41 | |||
37 | pub mod main { | 42 | pub 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 @@ | |||
1 | use std::collections::HashMap; | ||
2 | |||
3 | use lib::{Lint, LINTS}; | ||
4 | use rnix::SyntaxKind; | ||
5 | |||
6 | pub 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 | |||
22 | pub fn lint_map() -> HashMap<SyntaxKind, Vec<&'static Box<dyn Lint>>> { | ||
23 | lint_map_of(&*LINTS) | ||
24 | } | ||
@@ -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] |
258 | macro_rules! lint_map { | 258 | macro_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 @@ | |||
1 | use crate::lint_map; | 1 | use crate::lints; |
2 | 2 | ||
3 | lint_map! { | 3 | lints! { |
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 = [ |