From 2b6012a79cb092e5d88c050cb494057efef28fc2 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 20 Nov 2021 18:56:26 +0530 Subject: introduce --config flag --- bin/Cargo.toml | 10 +-- bin/src/config.rs | 168 ++++++++++++++++++++++++++++++++++++-------------- bin/src/err.rs | 2 + bin/src/explain.rs | 7 +-- bin/src/fix.rs | 12 ++-- bin/src/fix/all.rs | 18 +++--- bin/src/fix/single.rs | 8 +-- bin/src/lib.rs | 9 +++ bin/src/lint.rs | 17 +++-- bin/src/utils.rs | 24 ++++++++ 10 files changed, 200 insertions(+), 75 deletions(-) create mode 100644 bin/src/utils.rs (limited to 'bin') 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" similar = "2.1.0" vfs = { path = "../vfs" } lib = { path = "../lib" } - -[dependencies.serde_json] -version = "1.0.68" -optional = true +toml = "0.5.8" [dependencies.serde] version = "1.0.68" features = [ "derive" ] + +[dependencies.serde_json] +version = "1.0.68" optional = true [dev-dependencies] @@ -38,4 +38,4 @@ insta = "1.8.0" strip-ansi-escapes = "0.1.1" [features] -json = [ "lib/json-out", "serde_json", "serde" ] +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 @@ -use std::{default::Default, fmt, fs, path::PathBuf, str::FromStr}; +use std::{ + default::Default, + fmt, fs, + path::{Path, PathBuf}, + str::FromStr, +}; -use crate::{dirs, err::ConfigErr}; +use crate::{dirs, err::ConfigErr, utils, LintMap}; use clap::Parser; +use lib::LINTS; +use serde::{Deserialize, Serialize}; use vfs::ReadOnlyVfs; #[derive(Parser, Debug)] @@ -43,6 +50,10 @@ pub struct Check { #[clap(short = 'o', long, default_value_t, parse(try_from_str))] pub format: OutFormat, + /// Path to statix.toml + #[clap(short = 'c', long = "config", default_value = ".")] + pub conf_path: PathBuf, + /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout #[clap(short, long = "stdin")] pub streaming: bool, @@ -65,6 +76,10 @@ impl Check { vfs(files.collect::>()) } } + + pub fn lints(&self) -> Result { + lints(&self.conf_path) + } } #[derive(Parser, Debug)] @@ -85,6 +100,10 @@ pub struct Fix { #[clap(short, long = "dry-run")] pub diff_only: bool, + /// Path to statix.toml + #[clap(short = 'c', long = "config", default_value = ".")] + pub conf_path: PathBuf, + /// Enable "streaming" mode, accept file on stdin, output diagnostics on stdout #[clap(short, long = "stdin")] pub streaming: bool, @@ -125,6 +144,10 @@ impl Fix { FixOut::Write } } + + pub fn lints(&self) -> Result { + lints(&self.conf_path) + } } #[derive(Parser, Debug)] @@ -181,50 +204,6 @@ pub struct Explain { pub target: u32, } -fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { - let parts = src.split(','); - match parts.collect::>().as_slice() { - [line, col] => { - let l = line - .parse::() - .map_err(|_| ConfigErr::InvalidPosition(src.to_owned()))?; - let c = col - .parse::() - .map_err(|_| ConfigErr::InvalidPosition(src.to_owned()))?; - Ok((l, c)) - } - _ => Err(ConfigErr::InvalidPosition(src.to_owned())), - } -} - -fn parse_warning_code(src: &str) -> Result { - let mut char_stream = src.chars(); - let severity = char_stream - .next() - .ok_or_else(|| ConfigErr::InvalidWarningCode(src.to_owned()))? - .to_ascii_lowercase(); - match severity { - 'w' => char_stream - .collect::() - .parse::() - .map_err(|_| ConfigErr::InvalidWarningCode(src.to_owned())), - _ => Ok(0), - } -} - -fn vfs(files: Vec) -> Result { - let mut vfs = ReadOnlyVfs::default(); - for file in files.iter() { - if let Ok(data) = fs::read_to_string(&file) { - let _id = vfs.alloc_file_id(&file); - vfs.set_file_contents(&file, data.as_bytes()); - } else { - println!("{} contains non-utf8 content", file.display()); - }; - } - Ok(vfs) -} - #[derive(Debug, Copy, Clone)] pub enum OutFormat { #[cfg(feature = "json")] @@ -269,3 +248,100 @@ impl FromStr for OutFormat { } } } + +#[derive(Serialize, Deserialize, Debug)] +pub struct ConfFile { + disabled: Vec, +} + +impl Default for ConfFile { + fn default() -> Self { + let disabled = vec![]; + Self { disabled } + } +} + +impl ConfFile { + pub fn from_path>(path: P) -> Result { + let path = path.as_ref(); + let config_file = fs::read_to_string(path).map_err(ConfigErr::InvalidPath)?; + toml::de::from_str(&config_file).map_err(|err| { + let pos = err.line_col(); + let msg = if let Some((line, col)) = pos { + format!("line {}, col {}", line, col) + } else { + "unknown".to_string() + }; + ConfigErr::ConfFileParse(msg) + }) + } + pub fn discover>(path: P) -> Result { + let cannonical_path = fs::canonicalize(path.as_ref()).map_err(ConfigErr::InvalidPath)?; + for p in cannonical_path.ancestors() { + let statix_toml_path = p.with_file_name("statix.toml"); + if statix_toml_path.exists() { + return Self::from_path(statix_toml_path); + }; + } + Ok(Self::default()) + } + pub fn dump(&self) -> String { + toml::ser::to_string_pretty(&self).unwrap() + } +} + +fn parse_line_col(src: &str) -> Result<(usize, usize), ConfigErr> { + let parts = src.split(','); + match parts.collect::>().as_slice() { + [line, col] => { + let do_parse = |val: &str| { + val.parse::() + .map_err(|_| ConfigErr::InvalidPosition(src.to_owned())) + }; + let l = do_parse(line)?; + let c = do_parse(col)?; + Ok((l, c)) + } + _ => Err(ConfigErr::InvalidPosition(src.to_owned())), + } +} + +fn parse_warning_code(src: &str) -> Result { + let mut char_stream = src.chars(); + let severity = char_stream + .next() + .ok_or_else(|| ConfigErr::InvalidWarningCode(src.to_owned()))? + .to_ascii_lowercase(); + match severity { + 'w' => char_stream + .collect::() + .parse::() + .map_err(|_| ConfigErr::InvalidWarningCode(src.to_owned())), + _ => Ok(0), + } +} + +fn vfs(files: Vec) -> Result { + let mut vfs = ReadOnlyVfs::default(); + for file in files.iter() { + if let Ok(data) = fs::read_to_string(&file) { + let _id = vfs.alloc_file_id(&file); + vfs.set_file_contents(&file, data.as_bytes()); + } else { + println!("{} contains non-utf8 content", file.display()); + }; + } + Ok(vfs) +} + +fn lints(conf_path: &PathBuf) -> Result { + let config_file = ConfFile::discover(conf_path)?; + Ok(utils::lint_map_of( + (&*LINTS) + .into_iter() + .filter(|l| !config_file.disabled.iter().any(|check| check == l.name())) + .cloned() + .collect::>() + .as_slice(), + )) +} 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 { InvalidPosition(String), #[error("unable to parse `{0}` as warning code")] InvalidWarningCode(String), + #[error("unable to parse config file, error at: `{0}`")] + ConfFileParse(String), } // #[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 @@ -use crate::err::ExplainErr; - -use lib::LINTS; +use crate::{err::ExplainErr, utils}; pub fn explain(code: u32) -> Result<&'static str, ExplainErr> { + let lints = utils::lint_map(); match code { 0 => Ok("syntax error"), - _ => LINTS + _ => lints .values() .flatten() .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 @@ use std::borrow::Cow; +use crate::LintMap; + use rnix::TextRange; mod all; -use all::all; +use all::all_with; mod single; use single::single; type Source<'a> = Cow<'a, str>; -#[derive(Debug)] pub struct FixResult<'a> { pub src: Source<'a>, pub fixed: Vec, + pub lints: &'a LintMap, } #[derive(Debug, Clone)] @@ -23,10 +25,11 @@ pub struct Fixed { } impl<'a> FixResult<'a> { - fn empty(src: Source<'a>) -> Self { + fn empty(src: Source<'a>, lints: &'a LintMap) -> Self { Self { src, fixed: Vec::new(), + lints, } } } @@ -43,8 +46,9 @@ pub mod main { pub fn all(fix_config: FixConfig) -> Result<(), StatixErr> { let vfs = fix_config.vfs()?; + let lints = fix_config.lints()?; for entry in vfs.iter() { - match (fix_config.out(), super::all(entry.contents)) { + match (fix_config.out(), super::all_with(entry.contents, &lints)) { (FixOut::Diff, fix_result) => { let src = fix_result .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 @@ use std::borrow::Cow; -use lib::{Report, LINTS}; +use lib::Report; use rnix::{parser::ParseError as RnixParseErr, WalkEvent}; -use crate::fix::{FixResult, Fixed}; +use crate::{ + fix::{FixResult, Fixed}, + LintMap, +}; -fn collect_fixes(source: &str) -> Result, RnixParseErr> { +fn collect_fixes(source: &str, lints: &LintMap) -> Result, RnixParseErr> { let parsed = rnix::parse(source).as_result()?; Ok(parsed .node() .preorder_with_tokens() .filter_map(|event| match event { - WalkEvent::Enter(child) => LINTS.get(&child.kind()).map(|rules| { + WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { rules .iter() .filter_map(|rule| rule.validate(&child)) @@ -54,7 +57,7 @@ fn reorder(mut reports: Vec) -> Vec { impl<'a> Iterator for FixResult<'a> { type Item = FixResult<'a>; fn next(&mut self) -> Option { - let all_reports = collect_fixes(&self.src).ok()?; + let all_reports = collect_fixes(&self.src, &self.lints).ok()?; if all_reports.is_empty() { return None; } @@ -74,13 +77,14 @@ impl<'a> Iterator for FixResult<'a> { Some(FixResult { src: self.src.clone(), fixed, + lints: self.lints, }) } } -pub fn all(src: &str) -> Option { +pub fn all_with<'a>(src: &'a str, lints: &'a LintMap) -> Option> { let src = Cow::from(src); let _ = rnix::parse(&src).as_result().ok()?; - let initial = FixResult::empty(src); + let initial = FixResult::empty(src, lints); initial.into_iter().last() } 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 @@ use std::{borrow::Cow, convert::TryFrom}; -use lib::{Report, LINTS}; +use lib::Report; use rnix::{TextSize, WalkEvent}; -use crate::err::SingleFixErr; -use crate::fix::Source; +use crate::{err::SingleFixErr, fix::Source, utils}; pub struct SingleFixResult<'δ> { pub src: Source<'δ>, @@ -31,12 +30,13 @@ fn pos_to_byte(line: usize, col: usize, src: &str) -> Result Result { // we don't really need the source to form a completely parsed tree let parsed = rnix::parse(src); + let lints = utils::lint_map(); parsed .node() .preorder_with_tokens() .filter_map(|event| match event { - WalkEvent::Enter(child) => LINTS.get(&child.kind()).map(|rules| { + WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { rules .iter() .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; pub mod fix; pub mod lint; pub mod traits; + +mod utils; + +use std::collections::HashMap; + +use lib::Lint; +use rnix::SyntaxKind; + +pub type LintMap = HashMap>>; 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 @@ -use lib::{Report, LINTS}; +use crate::{utils, LintMap}; + +use lib::Report; use rnix::WalkEvent; use vfs::{FileId, VfsEntry}; @@ -8,18 +10,17 @@ pub struct LintResult { pub reports: Vec, } -pub fn lint(vfs_entry: VfsEntry) -> LintResult { +pub fn lint_with(vfs_entry: VfsEntry, lints: &LintMap) -> LintResult { let file_id = vfs_entry.file_id; let source = vfs_entry.contents; let parsed = rnix::parse(source); let error_reports = parsed.errors().into_iter().map(Report::from_parse_err); - let reports = parsed .node() .preorder_with_tokens() .filter_map(|event| match event { - WalkEvent::Enter(child) => LINTS.get(&child.kind()).map(|rules| { + WalkEvent::Enter(child) => lints.get(&child.kind()).map(|rules| { rules .iter() .filter_map(|rule| rule.validate(&child)) @@ -34,15 +35,21 @@ pub fn lint(vfs_entry: VfsEntry) -> LintResult { LintResult { file_id, reports } } +pub fn lint(vfs_entry: VfsEntry) -> LintResult { + lint_with(vfs_entry, &utils::lint_map()) +} + pub mod main { use std::io; - use super::lint; + use super::lint_with; use crate::{config::Check as CheckConfig, err::StatixErr, traits::WriteDiagnostic}; 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); vfs.iter().map(lint).for_each(|r| { stdout.write(&r, &vfs, check_config.format).unwrap(); }); 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 @@ +use std::collections::HashMap; + +use lib::{Lint, LINTS}; +use rnix::SyntaxKind; + +pub fn lint_map_of( + lints: &[&'static Box], +) -> HashMap>> { + let mut map = HashMap::new(); + for lint in lints.iter() { + let lint = *lint; + let matches = lint.match_kind(); + for m in matches { + map.entry(m) + .and_modify(|v: &mut Vec<_>| v.push(lint)) + .or_insert_with(|| vec![lint]); + } + } + map +} + +pub fn lint_map() -> HashMap>> { + lint_map_of(&*LINTS) +} -- cgit v1.2.3