mirror of
https://github.com/nerdypepper/statix.git
synced 2024-09-19 09:28:12 +03:00
fully flesh out CLI
This commit is contained in:
parent
0076b3a37d
commit
214e3bc4b6
306
Cargo.lock
generated
306
Cargo.lock
generated
@ -3,10 +3,13 @@
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.44"
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ariadne"
|
||||
@ -17,12 +20,38 @@ dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbitset"
|
||||
version = "0.2.0"
|
||||
@ -32,24 +61,114 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.0-beta.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcd70aa5597dbc42f7217a543f9ef2768b2ef823ba29036072d30e1d88e98406"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.0.0-beta.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5bb0d655624a0b8770d1c178fb8ffcb1f91cc722cb08f451e3dc72465421ac"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "countme"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "if_chain"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@ -67,6 +186,21 @@ dependencies = [
|
||||
"rowan",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macros"
|
||||
version = "0.1.0"
|
||||
@ -76,6 +210,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.4"
|
||||
@ -94,6 +234,36 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6acbef58a60fe69ab50510a55bc8cdd4d6cf2283d27ad338f54cb52747a9cf2d"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.29"
|
||||
@ -112,6 +282,23 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "rnix"
|
||||
version = "0.9.0"
|
||||
@ -130,7 +317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1b36e449f3702f3b0c821411db1cbdf30fb451726a9456dce5dabcd44420043"
|
||||
dependencies = [
|
||||
"countme",
|
||||
"hashbrown",
|
||||
"hashbrown 0.9.1",
|
||||
"memoffset",
|
||||
"rustc-hash",
|
||||
"text-size",
|
||||
@ -152,12 +339,21 @@ checksum = "b203e79e90905594272c1c97c7af701533d42adaab0beb3859018e477d54a3b0"
|
||||
name = "statix"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ariadne",
|
||||
"clap",
|
||||
"globset",
|
||||
"lib",
|
||||
"rnix",
|
||||
"thiserror",
|
||||
"vfs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.76"
|
||||
@ -169,18 +365,118 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text-size"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a"
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "vfs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.0"
|
||||
|
@ -8,5 +8,8 @@ edition = "2018"
|
||||
[dependencies]
|
||||
lib = { path = "../lib" }
|
||||
ariadne = "0.1.3"
|
||||
anyhow = "1.0"
|
||||
rnix = "0.9.0"
|
||||
clap = "3.0.0-beta.4"
|
||||
globset = "0.4.8"
|
||||
thiserror = "1.0.30"
|
||||
vfs = { path = "../vfs" }
|
||||
|
170
bin/src/config.rs
Normal file
170
bin/src/config.rs
Normal file
@ -0,0 +1,170 @@
|
||||
use std::{default::Default, fs, path::PathBuf, str::FromStr};
|
||||
|
||||
use clap::Clap;
|
||||
use globset::{GlobBuilder, GlobSetBuilder};
|
||||
use vfs::ReadOnlyVfs;
|
||||
|
||||
use crate::err::ConfigErr;
|
||||
|
||||
/// Static analysis and linting for the nix programming language
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(version = "0.1.0", author = "Akshay <nerdy@peppe.rs>")]
|
||||
pub struct Opts {
|
||||
/// File or directory to run statix on
|
||||
#[clap(default_value = ".")]
|
||||
target: String,
|
||||
|
||||
// /// Path to statix config
|
||||
// #[clap(short, long, default_value = ".statix.toml")]
|
||||
// config: String,
|
||||
/// Regex of file patterns to not lint
|
||||
#[clap(short, long)]
|
||||
ignore: Vec<String>,
|
||||
|
||||
/// Output format. Supported values: json, errfmt
|
||||
#[clap(short = 'o', long)]
|
||||
format: Option<OutFormat>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
pub subcmd: Option<SubCommand>,
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(version = "0.1.0", author = "Akshay <nerdy@peppe.rs>")]
|
||||
pub enum SubCommand {
|
||||
/// Find and fix issues raised by statix
|
||||
Fix(Fix),
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Fix {
|
||||
/// Do not write to files, display a diff instead
|
||||
#[clap(short = 'd', long = "dry-run")]
|
||||
diff_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum OutFormat {
|
||||
Json,
|
||||
Errfmt,
|
||||
StdErr,
|
||||
}
|
||||
|
||||
impl Default for OutFormat {
|
||||
fn default() -> Self {
|
||||
OutFormat::StdErr
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for OutFormat {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value.to_ascii_lowercase().as_str() {
|
||||
"json" => Ok(Self::Json),
|
||||
"errfmt" => Ok(Self::Errfmt),
|
||||
"stderr" => Ok(Self::StdErr),
|
||||
_ => Err("unknown output format, try: json, errfmt"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LintConfig {
|
||||
pub files: Vec<PathBuf>,
|
||||
pub format: OutFormat,
|
||||
}
|
||||
|
||||
impl LintConfig {
|
||||
pub fn from_opts(opts: Opts) -> Result<Self, ConfigErr> {
|
||||
let ignores = {
|
||||
let mut set = GlobSetBuilder::new();
|
||||
for pattern in opts.ignore {
|
||||
let glob = GlobBuilder::new(&pattern).build().map_err(|err| {
|
||||
ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone())
|
||||
})?;
|
||||
set.add(glob);
|
||||
}
|
||||
set.build().map_err(|err| {
|
||||
ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone())
|
||||
})
|
||||
}?;
|
||||
|
||||
let walker = dirs::Walker::new(opts.target).map_err(ConfigErr::InvalidPath)?;
|
||||
|
||||
let files = walker
|
||||
.filter(|path| matches!(path.extension(), Some(e) if e == "nix"))
|
||||
.filter(|path| !ignores.is_match(path))
|
||||
.collect();
|
||||
Ok(Self {
|
||||
files,
|
||||
format: opts.format.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> {
|
||||
let mut vfs = ReadOnlyVfs::default();
|
||||
for file in self.files.iter() {
|
||||
let _id = vfs.alloc_file_id(&file);
|
||||
let data = fs::read_to_string(&file).map_err(ConfigErr::InvalidPath)?;
|
||||
vfs.set_file_contents(&file, data.as_bytes());
|
||||
}
|
||||
Ok(vfs)
|
||||
}
|
||||
}
|
||||
|
||||
mod dirs {
|
||||
use std::{
|
||||
fs,
|
||||
io::{self, Error, ErrorKind},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Walker {
|
||||
dirs: Vec<PathBuf>,
|
||||
files: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Walker {
|
||||
pub fn new<P: AsRef<Path>>(target: P) -> io::Result<Self> {
|
||||
let target = target.as_ref().to_path_buf();
|
||||
if !target.exists() {
|
||||
Err(Error::new(
|
||||
ErrorKind::NotFound,
|
||||
format!("file not found: {}", target.display()),
|
||||
))
|
||||
} else if target.is_dir() {
|
||||
Ok(Self {
|
||||
dirs: vec![target],
|
||||
..Default::default()
|
||||
})
|
||||
} else {
|
||||
Ok(Self {
|
||||
files: vec![target],
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Walker {
|
||||
type Item = PathBuf;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(dir) = self.dirs.pop() {
|
||||
if dir.is_dir() {
|
||||
for entry in fs::read_dir(dir).ok()? {
|
||||
let entry = entry.ok()?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
self.dirs.push(path);
|
||||
} else if path.is_file() {
|
||||
self.files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.files.pop()
|
||||
}
|
||||
}
|
||||
}
|
28
bin/src/err.rs
Normal file
28
bin/src/err.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use std::{io, path::PathBuf};
|
||||
|
||||
use globset::ErrorKind;
|
||||
use rnix::parser::ParseError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ConfigErr {
|
||||
#[error("error parsing glob `{0:?}`: {1}")]
|
||||
InvalidGlob(Option<String>, ErrorKind),
|
||||
|
||||
#[error("path error: {0}")]
|
||||
InvalidPath(#[from] io::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LintErr {
|
||||
#[error("[{0}] syntax error: {1}")]
|
||||
Parse(PathBuf, ParseError),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum StatixErr {
|
||||
#[error("linter error: {0}")]
|
||||
Lint(#[from] LintErr),
|
||||
#[error("config error: {0}")]
|
||||
Config(#[from] ConfigErr),
|
||||
}
|
118
bin/src/main.rs
118
bin/src/main.rs
@ -1,20 +1,28 @@
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
#![feature(path_try_exists)]
|
||||
|
||||
mod config;
|
||||
mod err;
|
||||
mod traits;
|
||||
|
||||
use std::io;
|
||||
|
||||
use crate::{
|
||||
err::{LintErr, StatixErr},
|
||||
traits::{LintResult, WriteDiagnostic},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use ariadne::{
|
||||
CharSet, Color, Config as CliConfig, Label, LabelAttach, Report as CliReport,
|
||||
ReportKind as CliReportKind, Source,
|
||||
};
|
||||
use lib::{Report, LINTS};
|
||||
use rnix::{TextRange, WalkEvent};
|
||||
use clap::Clap;
|
||||
use config::{LintConfig, Opts, SubCommand};
|
||||
use lib::LINTS;
|
||||
use rnix::WalkEvent;
|
||||
use vfs::VfsEntry;
|
||||
|
||||
fn analyze(source: &str) -> Result<Vec<Report>> {
|
||||
let parsed = rnix::parse(source).as_result()?;
|
||||
|
||||
Ok(parsed
|
||||
fn analyze<'ρ>(vfs_entry: VfsEntry<'ρ>) -> Result<LintResult, LintErr> {
|
||||
let source = vfs_entry.contents;
|
||||
let parsed = rnix::parse(source)
|
||||
.as_result()
|
||||
.map_err(|e| LintErr::Parse(vfs_entry.file_path.to_path_buf(), e))?;
|
||||
let reports = parsed
|
||||
.node()
|
||||
.preorder_with_tokens()
|
||||
.filter_map(|event| match event {
|
||||
@ -27,61 +35,33 @@ fn analyze(source: &str) -> Result<Vec<Report>> {
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn print_report(report: Report, file_src: &str, file_path: &Path) -> Result<()> {
|
||||
let range = |at: TextRange| at.start().into()..at.end().into();
|
||||
let src_id = file_path.to_str().unwrap_or("<unknown>");
|
||||
let offset = report
|
||||
.diagnostics
|
||||
.iter()
|
||||
.map(|d| d.at.start().into())
|
||||
.min()
|
||||
.unwrap_or(0usize);
|
||||
report
|
||||
.diagnostics
|
||||
.iter()
|
||||
.fold(
|
||||
CliReport::build(CliReportKind::Warning, src_id, offset)
|
||||
.with_config(
|
||||
CliConfig::default()
|
||||
.with_cross_gap(true)
|
||||
.with_multiline_arrows(false)
|
||||
.with_label_attach(LabelAttach::Middle)
|
||||
.with_char_set(CharSet::Unicode),
|
||||
)
|
||||
.with_message(report.note)
|
||||
.with_code(report.code),
|
||||
|cli_report, diagnostic| {
|
||||
cli_report.with_label(
|
||||
Label::new((src_id, range(diagnostic.at)))
|
||||
.with_message(&diagnostic.message)
|
||||
.with_color(Color::Magenta),
|
||||
)
|
||||
},
|
||||
)
|
||||
.finish()
|
||||
.eprint((src_id, Source::from(file_src)))
|
||||
.context("failed to print report to stdout")
|
||||
}
|
||||
|
||||
fn _main() -> Result<()> {
|
||||
// TODO: accept cli args, construct a CLI config with a list of files to analyze
|
||||
let args = env::args();
|
||||
for (file_src, file_path, reports) in args
|
||||
.skip(1)
|
||||
.map(|s| PathBuf::from(&s))
|
||||
.filter(|p| p.is_file())
|
||||
.filter_map(|path| {
|
||||
let s = fs::read_to_string(&path).ok()?;
|
||||
analyze(&s)
|
||||
.map(|analysis_result| (s, path, analysis_result))
|
||||
.ok()
|
||||
.collect();
|
||||
Ok(LintResult {
|
||||
file_id: vfs_entry.file_id,
|
||||
reports,
|
||||
})
|
||||
{
|
||||
for r in reports {
|
||||
print_report(r, &file_src, &file_path)?
|
||||
}
|
||||
|
||||
fn _main() -> Result<(), StatixErr> {
|
||||
// TODO: accept cli args, construct a CLI config with a list of files to analyze
|
||||
let opts = Opts::parse();
|
||||
match opts.subcmd {
|
||||
Some(SubCommand::Fix(_)) => {}
|
||||
None => {
|
||||
let lint_config = LintConfig::from_opts(opts)?;
|
||||
let vfs = lint_config.vfs()?;
|
||||
let (reports, errors): (Vec<_>, Vec<_>) =
|
||||
vfs.iter().map(analyze).partition(Result::is_ok);
|
||||
let lint_results: Vec<_> = reports.into_iter().map(Result::unwrap).collect();
|
||||
let errors: Vec<_> = errors.into_iter().map(Result::unwrap_err).collect();
|
||||
|
||||
let mut stderr = io::stderr();
|
||||
lint_results.into_iter().for_each(|r| {
|
||||
stderr.write(&r, &vfs).unwrap();
|
||||
});
|
||||
errors.into_iter().for_each(|e| {
|
||||
eprintln!("{}", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@ -90,6 +70,6 @@ fn _main() -> Result<()> {
|
||||
fn main() {
|
||||
match _main() {
|
||||
Err(e) => eprintln!("{}", e),
|
||||
_ => {}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
83
bin/src/traits.rs
Normal file
83
bin/src/traits.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
str,
|
||||
};
|
||||
|
||||
use ariadne::{
|
||||
CharSet, Color, Config as CliConfig, Label, LabelAttach, Report as CliReport,
|
||||
ReportKind as CliReportKind, Source, Fmt
|
||||
};
|
||||
use lib::Report;
|
||||
use rnix::TextRange;
|
||||
use vfs::{FileId, ReadOnlyVfs};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LintResult {
|
||||
pub file_id: FileId,
|
||||
pub reports: Vec<Report>,
|
||||
}
|
||||
|
||||
pub trait WriteDiagnostic {
|
||||
fn write(&mut self, report: &LintResult, vfs: &ReadOnlyVfs) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl<T> WriteDiagnostic for T
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
fn write(&mut self, lint_result: &LintResult, vfs: &ReadOnlyVfs) -> io::Result<()> {
|
||||
let file_id = lint_result.file_id;
|
||||
let src = str::from_utf8(vfs.get(file_id)).unwrap();
|
||||
let path = vfs.file_path(file_id);
|
||||
let range = |at: TextRange| at.start().into()..at.end().into();
|
||||
let src_id = path.to_str().unwrap_or("<unknown>");
|
||||
for report in lint_result.reports.iter() {
|
||||
let offset = report
|
||||
.diagnostics
|
||||
.iter()
|
||||
.map(|d| d.at.start().into())
|
||||
.min()
|
||||
.unwrap_or(0usize);
|
||||
report
|
||||
.diagnostics
|
||||
.iter()
|
||||
.fold(
|
||||
CliReport::build(CliReportKind::Warning, src_id, offset)
|
||||
.with_config(
|
||||
CliConfig::default()
|
||||
.with_cross_gap(true)
|
||||
.with_multiline_arrows(false)
|
||||
.with_label_attach(LabelAttach::Middle)
|
||||
.with_char_set(CharSet::Unicode),
|
||||
)
|
||||
.with_message(report.note)
|
||||
.with_code(report.code),
|
||||
|cli_report, diagnostic| {
|
||||
cli_report.with_label(
|
||||
Label::new((src_id, range(diagnostic.at)))
|
||||
.with_message(&colorize(&diagnostic.message))
|
||||
.with_color(Color::Magenta),
|
||||
)
|
||||
},
|
||||
)
|
||||
.finish()
|
||||
.write((src_id, Source::from(src)), &mut *self)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// everything within backticks is colorized, backticks are removed
|
||||
fn colorize(message: &str) -> String {
|
||||
message.split('`')
|
||||
.enumerate()
|
||||
.map(|(idx, part)| {
|
||||
if idx % 2 == 1 {
|
||||
part.fg(Color::Cyan).to_string()
|
||||
} else {
|
||||
part.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
Loading…
Reference in New Issue
Block a user