aboutsummaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-11-20 13:26:26 +0000
committerAkshay <[email protected]>2021-11-28 07:55:23 +0000
commit2b6012a79cb092e5d88c050cb494057efef28fc2 (patch)
treecd31973c32431a5cbf8c12ce574f799d065852be /bin
parent4e063b2abc402ac4d6902647e821978269025c7d (diff)
introduce --config flag
Diffstat (limited to 'bin')
-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
10 files changed, 200 insertions, 75 deletions
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}