aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-10-19 11:28:46 +0100
committerAkshay <[email protected]>2021-10-19 11:28:46 +0100
commit214e3bc4b675b08ce4df76b1373f4548949e67ee (patch)
treedb6584a360d69bc9759e2b8059a013ba9e8db5e1
parent0076b3a37dcca0e11afd05dc98174f646cdb8fa9 (diff)
fully flesh out CLI
-rw-r--r--Cargo.lock306
-rw-r--r--bin/Cargo.toml5
-rw-r--r--bin/src/config.rs170
-rw-r--r--bin/src/err.rs28
-rw-r--r--bin/src/main.rs112
-rw-r--r--bin/src/traits.rs83
6 files changed, 632 insertions, 72 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8b371ea..69e4495 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,10 +3,13 @@
3version = 3 3version = 3
4 4
5[[package]] 5[[package]]
6name = "anyhow" 6name = "aho-corasick"
7version = "1.0.44" 7version = "0.7.18"
8source = "registry+https://github.com/rust-lang/crates.io-index" 8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" 9checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
10dependencies = [
11 "memchr",
12]
10 13
11[[package]] 14[[package]]
12name = "ariadne" 15name = "ariadne"
@@ -18,12 +21,38 @@ dependencies = [
18] 21]
19 22
20[[package]] 23[[package]]
24name = "atty"
25version = "0.2.14"
26source = "registry+https://github.com/rust-lang/crates.io-index"
27checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
28dependencies = [
29 "hermit-abi",
30 "libc",
31 "winapi",
32]
33
34[[package]]
21name = "autocfg" 35name = "autocfg"
22version = "1.0.1" 36version = "1.0.1"
23source = "registry+https://github.com/rust-lang/crates.io-index" 37source = "registry+https://github.com/rust-lang/crates.io-index"
24checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 38checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
25 39
26[[package]] 40[[package]]
41name = "bitflags"
42version = "1.3.2"
43source = "registry+https://github.com/rust-lang/crates.io-index"
44checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
45
46[[package]]
47name = "bstr"
48version = "0.2.17"
49source = "registry+https://github.com/rust-lang/crates.io-index"
50checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
51dependencies = [
52 "memchr",
53]
54
55[[package]]
27name = "cbitset" 56name = "cbitset"
28version = "0.2.0" 57version = "0.2.0"
29source = "registry+https://github.com/rust-lang/crates.io-index" 58source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -33,24 +62,114 @@ dependencies = [
33] 62]
34 63
35[[package]] 64[[package]]
65name = "cfg-if"
66version = "1.0.0"
67source = "registry+https://github.com/rust-lang/crates.io-index"
68checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
69
70[[package]]
71name = "clap"
72version = "3.0.0-beta.4"
73source = "registry+https://github.com/rust-lang/crates.io-index"
74checksum = "fcd70aa5597dbc42f7217a543f9ef2768b2ef823ba29036072d30e1d88e98406"
75dependencies = [
76 "atty",
77 "bitflags",
78 "clap_derive",
79 "indexmap",
80 "lazy_static",
81 "os_str_bytes",
82 "strsim",
83 "termcolor",
84 "textwrap",
85 "vec_map",
86]
87
88[[package]]
89name = "clap_derive"
90version = "3.0.0-beta.4"
91source = "registry+https://github.com/rust-lang/crates.io-index"
92checksum = "0b5bb0d655624a0b8770d1c178fb8ffcb1f91cc722cb08f451e3dc72465421ac"
93dependencies = [
94 "heck",
95 "proc-macro-error",
96 "proc-macro2",
97 "quote",
98 "syn",
99]
100
101[[package]]
36name = "countme" 102name = "countme"
37version = "2.0.4" 103version = "2.0.4"
38source = "registry+https://github.com/rust-lang/crates.io-index" 104source = "registry+https://github.com/rust-lang/crates.io-index"
39checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" 105checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58"
40 106
41[[package]] 107[[package]]
108name = "fnv"
109version = "1.0.7"
110source = "registry+https://github.com/rust-lang/crates.io-index"
111checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
112
113[[package]]
114name = "globset"
115version = "0.4.8"
116source = "registry+https://github.com/rust-lang/crates.io-index"
117checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
118dependencies = [
119 "aho-corasick",
120 "bstr",
121 "fnv",
122 "log",
123 "regex",
124]
125
126[[package]]
42name = "hashbrown" 127name = "hashbrown"
43version = "0.9.1" 128version = "0.9.1"
44source = "registry+https://github.com/rust-lang/crates.io-index" 129source = "registry+https://github.com/rust-lang/crates.io-index"
45checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 130checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
46 131
47[[package]] 132[[package]]
133name = "hashbrown"
134version = "0.11.2"
135source = "registry+https://github.com/rust-lang/crates.io-index"
136checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
137
138[[package]]
139name = "heck"
140version = "0.3.3"
141source = "registry+https://github.com/rust-lang/crates.io-index"
142checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
143dependencies = [
144 "unicode-segmentation",
145]
146
147[[package]]
148name = "hermit-abi"
149version = "0.1.19"
150source = "registry+https://github.com/rust-lang/crates.io-index"
151checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
152dependencies = [
153 "libc",
154]
155
156[[package]]
48name = "if_chain" 157name = "if_chain"
49version = "1.0.2" 158version = "1.0.2"
50source = "registry+https://github.com/rust-lang/crates.io-index" 159source = "registry+https://github.com/rust-lang/crates.io-index"
51checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" 160checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
52 161
53[[package]] 162[[package]]
163name = "indexmap"
164version = "1.7.0"
165source = "registry+https://github.com/rust-lang/crates.io-index"
166checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
167dependencies = [
168 "autocfg",
169 "hashbrown 0.11.2",
170]
171
172[[package]]
54name = "lazy_static" 173name = "lazy_static"
55version = "1.4.0" 174version = "1.4.0"
56source = "registry+https://github.com/rust-lang/crates.io-index" 175source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -68,6 +187,21 @@ dependencies = [
68] 187]
69 188
70[[package]] 189[[package]]
190name = "libc"
191version = "0.2.103"
192source = "registry+https://github.com/rust-lang/crates.io-index"
193checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
194
195[[package]]
196name = "log"
197version = "0.4.14"
198source = "registry+https://github.com/rust-lang/crates.io-index"
199checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
200dependencies = [
201 "cfg-if",
202]
203
204[[package]]
71name = "macros" 205name = "macros"
72version = "0.1.0" 206version = "0.1.0"
73dependencies = [ 207dependencies = [
@@ -77,6 +211,12 @@ dependencies = [
77] 211]
78 212
79[[package]] 213[[package]]
214name = "memchr"
215version = "2.4.1"
216source = "registry+https://github.com/rust-lang/crates.io-index"
217checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
218
219[[package]]
80name = "memoffset" 220name = "memoffset"
81version = "0.6.4" 221version = "0.6.4"
82source = "registry+https://github.com/rust-lang/crates.io-index" 222source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -95,6 +235,36 @@ dependencies = [
95] 235]
96 236
97[[package]] 237[[package]]
238name = "os_str_bytes"
239version = "3.1.0"
240source = "registry+https://github.com/rust-lang/crates.io-index"
241checksum = "6acbef58a60fe69ab50510a55bc8cdd4d6cf2283d27ad338f54cb52747a9cf2d"
242
243[[package]]
244name = "proc-macro-error"
245version = "1.0.4"
246source = "registry+https://github.com/rust-lang/crates.io-index"
247checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
248dependencies = [
249 "proc-macro-error-attr",
250 "proc-macro2",
251 "quote",
252 "syn",
253 "version_check",
254]
255
256[[package]]
257name = "proc-macro-error-attr"
258version = "1.0.4"
259source = "registry+https://github.com/rust-lang/crates.io-index"
260checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
261dependencies = [
262 "proc-macro2",
263 "quote",
264 "version_check",
265]
266
267[[package]]
98name = "proc-macro2" 268name = "proc-macro2"
99version = "1.0.29" 269version = "1.0.29"
100source = "registry+https://github.com/rust-lang/crates.io-index" 270source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -113,6 +283,23 @@ dependencies = [
113] 283]
114 284
115[[package]] 285[[package]]
286name = "regex"
287version = "1.5.4"
288source = "registry+https://github.com/rust-lang/crates.io-index"
289checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
290dependencies = [
291 "aho-corasick",
292 "memchr",
293 "regex-syntax",
294]
295
296[[package]]
297name = "regex-syntax"
298version = "0.6.25"
299source = "registry+https://github.com/rust-lang/crates.io-index"
300checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
301
302[[package]]
116name = "rnix" 303name = "rnix"
117version = "0.9.0" 304version = "0.9.0"
118source = "registry+https://github.com/rust-lang/crates.io-index" 305source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -130,7 +317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
130checksum = "a1b36e449f3702f3b0c821411db1cbdf30fb451726a9456dce5dabcd44420043" 317checksum = "a1b36e449f3702f3b0c821411db1cbdf30fb451726a9456dce5dabcd44420043"
131dependencies = [ 318dependencies = [
132 "countme", 319 "countme",
133 "hashbrown", 320 "hashbrown 0.9.1",
134 "memoffset", 321 "memoffset",
135 "rustc-hash", 322 "rustc-hash",
136 "text-size", 323 "text-size",
@@ -152,13 +339,22 @@ checksum = "b203e79e90905594272c1c97c7af701533d42adaab0beb3859018e477d54a3b0"
152name = "statix" 339name = "statix"
153version = "0.1.0" 340version = "0.1.0"
154dependencies = [ 341dependencies = [
155 "anyhow",
156 "ariadne", 342 "ariadne",
343 "clap",
344 "globset",
157 "lib", 345 "lib",
158 "rnix", 346 "rnix",
347 "thiserror",
348 "vfs",
159] 349]
160 350
161[[package]] 351[[package]]
352name = "strsim"
353version = "0.10.0"
354source = "registry+https://github.com/rust-lang/crates.io-index"
355checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
356
357[[package]]
162name = "syn" 358name = "syn"
163version = "1.0.76" 359version = "1.0.76"
164source = "registry+https://github.com/rust-lang/crates.io-index" 360source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -170,18 +366,118 @@ dependencies = [
170] 366]
171 367
172[[package]] 368[[package]]
369name = "termcolor"
370version = "1.1.2"
371source = "registry+https://github.com/rust-lang/crates.io-index"
372checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
373dependencies = [
374 "winapi-util",
375]
376
377[[package]]
173name = "text-size" 378name = "text-size"
174version = "1.1.0" 379version = "1.1.0"
175source = "registry+https://github.com/rust-lang/crates.io-index" 380source = "registry+https://github.com/rust-lang/crates.io-index"
176checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" 381checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a"
177 382
178[[package]] 383[[package]]
384name = "textwrap"
385version = "0.14.2"
386source = "registry+https://github.com/rust-lang/crates.io-index"
387checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
388dependencies = [
389 "unicode-width",
390]
391
392[[package]]
393name = "thiserror"
394version = "1.0.30"
395source = "registry+https://github.com/rust-lang/crates.io-index"
396checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
397dependencies = [
398 "thiserror-impl",
399]
400
401[[package]]
402name = "thiserror-impl"
403version = "1.0.30"
404source = "registry+https://github.com/rust-lang/crates.io-index"
405checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
406dependencies = [
407 "proc-macro2",
408 "quote",
409 "syn",
410]
411
412[[package]]
413name = "unicode-segmentation"
414version = "1.8.0"
415source = "registry+https://github.com/rust-lang/crates.io-index"
416checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
417
418[[package]]
419name = "unicode-width"
420version = "0.1.9"
421source = "registry+https://github.com/rust-lang/crates.io-index"
422checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
423
424[[package]]
179name = "unicode-xid" 425name = "unicode-xid"
180version = "0.2.2" 426version = "0.2.2"
181source = "registry+https://github.com/rust-lang/crates.io-index" 427source = "registry+https://github.com/rust-lang/crates.io-index"
182checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 428checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
183 429
184[[package]] 430[[package]]
431name = "vec_map"
432version = "0.8.2"
433source = "registry+https://github.com/rust-lang/crates.io-index"
434checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
435
436[[package]]
437name = "version_check"
438version = "0.9.3"
439source = "registry+https://github.com/rust-lang/crates.io-index"
440checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
441
442[[package]]
443name = "vfs"
444version = "0.1.0"
445dependencies = [
446 "indexmap",
447]
448
449[[package]]
450name = "winapi"
451version = "0.3.9"
452source = "registry+https://github.com/rust-lang/crates.io-index"
453checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
454dependencies = [
455 "winapi-i686-pc-windows-gnu",
456 "winapi-x86_64-pc-windows-gnu",
457]
458
459[[package]]
460name = "winapi-i686-pc-windows-gnu"
461version = "0.4.0"
462source = "registry+https://github.com/rust-lang/crates.io-index"
463checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
464
465[[package]]
466name = "winapi-util"
467version = "0.1.5"
468source = "registry+https://github.com/rust-lang/crates.io-index"
469checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
470dependencies = [
471 "winapi",
472]
473
474[[package]]
475name = "winapi-x86_64-pc-windows-gnu"
476version = "0.4.0"
477source = "registry+https://github.com/rust-lang/crates.io-index"
478checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
479
480[[package]]
185name = "yansi" 481name = "yansi"
186version = "0.5.0" 482version = "0.5.0"
187source = "registry+https://github.com/rust-lang/crates.io-index" 483source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/bin/Cargo.toml b/bin/Cargo.toml
index 9452c2b..0d6f970 100644
--- a/bin/Cargo.toml
+++ b/bin/Cargo.toml
@@ -8,5 +8,8 @@ edition = "2018"
8[dependencies] 8[dependencies]
9lib = { path = "../lib" } 9lib = { path = "../lib" }
10ariadne = "0.1.3" 10ariadne = "0.1.3"
11anyhow = "1.0"
12rnix = "0.9.0" 11rnix = "0.9.0"
12clap = "3.0.0-beta.4"
13globset = "0.4.8"
14thiserror = "1.0.30"
15vfs = { path = "../vfs" }
diff --git a/bin/src/config.rs b/bin/src/config.rs
new file mode 100644
index 0000000..f2cf29d
--- /dev/null
+++ b/bin/src/config.rs
@@ -0,0 +1,170 @@
1use std::{default::Default, fs, path::PathBuf, str::FromStr};
2
3use clap::Clap;
4use globset::{GlobBuilder, GlobSetBuilder};
5use vfs::ReadOnlyVfs;
6
7use crate::err::ConfigErr;
8
9/// Static analysis and linting for the nix programming language
10#[derive(Clap, Debug)]
11#[clap(version = "0.1.0", author = "Akshay <[email protected]>")]
12pub struct Opts {
13 /// File or directory to run statix on
14 #[clap(default_value = ".")]
15 target: String,
16
17 // /// Path to statix config
18 // #[clap(short, long, default_value = ".statix.toml")]
19 // config: String,
20 /// Regex of file patterns to not lint
21 #[clap(short, long)]
22 ignore: Vec<String>,
23
24 /// Output format. Supported values: json, errfmt
25 #[clap(short = 'o', long)]
26 format: Option<OutFormat>,
27
28 #[clap(subcommand)]
29 pub subcmd: Option<SubCommand>,
30}
31
32#[derive(Clap, Debug)]
33#[clap(version = "0.1.0", author = "Akshay <[email protected]>")]
34pub enum SubCommand {
35 /// Find and fix issues raised by statix
36 Fix(Fix),
37}
38
39#[derive(Clap, Debug)]
40pub struct Fix {
41 /// Do not write to files, display a diff instead
42 #[clap(short = 'd', long = "dry-run")]
43 diff_only: bool,
44}
45
46#[derive(Debug, Copy, Clone)]
47pub enum OutFormat {
48 Json,
49 Errfmt,
50 StdErr,
51}
52
53impl Default for OutFormat {
54 fn default() -> Self {
55 OutFormat::StdErr
56 }
57}
58
59impl FromStr for OutFormat {
60 type Err = &'static str;
61
62 fn from_str(value: &str) -> Result<Self, Self::Err> {
63 match value.to_ascii_lowercase().as_str() {
64 "json" => Ok(Self::Json),
65 "errfmt" => Ok(Self::Errfmt),
66 "stderr" => Ok(Self::StdErr),
67 _ => Err("unknown output format, try: json, errfmt"),
68 }
69 }
70}
71
72#[derive(Debug)]
73pub struct LintConfig {
74 pub files: Vec<PathBuf>,
75 pub format: OutFormat,
76}
77
78impl LintConfig {
79 pub fn from_opts(opts: Opts) -> Result<Self, ConfigErr> {
80 let ignores = {
81 let mut set = GlobSetBuilder::new();
82 for pattern in opts.ignore {
83 let glob = GlobBuilder::new(&pattern).build().map_err(|err| {
84 ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone())
85 })?;
86 set.add(glob);
87 }
88 set.build().map_err(|err| {
89 ConfigErr::InvalidGlob(err.glob().map(|i| i.to_owned()), err.kind().clone())
90 })
91 }?;
92
93 let walker = dirs::Walker::new(opts.target).map_err(ConfigErr::InvalidPath)?;
94
95 let files = walker
96 .filter(|path| matches!(path.extension(), Some(e) if e == "nix"))
97 .filter(|path| !ignores.is_match(path))
98 .collect();
99 Ok(Self {
100 files,
101 format: opts.format.unwrap_or_default(),
102 })
103 }
104
105 pub fn vfs(&self) -> Result<ReadOnlyVfs, ConfigErr> {
106 let mut vfs = ReadOnlyVfs::default();
107 for file in self.files.iter() {
108 let _id = vfs.alloc_file_id(&file);
109 let data = fs::read_to_string(&file).map_err(ConfigErr::InvalidPath)?;
110 vfs.set_file_contents(&file, data.as_bytes());
111 }
112 Ok(vfs)
113 }
114}
115
116mod dirs {
117 use std::{
118 fs,
119 io::{self, Error, ErrorKind},
120 path::{Path, PathBuf},
121 };
122
123 #[derive(Default, Debug)]
124 pub struct Walker {
125 dirs: Vec<PathBuf>,
126 files: Vec<PathBuf>,
127 }
128
129 impl Walker {
130 pub fn new<P: AsRef<Path>>(target: P) -> io::Result<Self> {
131 let target = target.as_ref().to_path_buf();
132 if !target.exists() {
133 Err(Error::new(
134 ErrorKind::NotFound,
135 format!("file not found: {}", target.display()),
136 ))
137 } else if target.is_dir() {
138 Ok(Self {
139 dirs: vec![target],
140 ..Default::default()
141 })
142 } else {
143 Ok(Self {
144 files: vec![target],
145 ..Default::default()
146 })
147 }
148 }
149 }
150
151 impl Iterator for Walker {
152 type Item = PathBuf;
153 fn next(&mut self) -> Option<Self::Item> {
154 if let Some(dir) = self.dirs.pop() {
155 if dir.is_dir() {
156 for entry in fs::read_dir(dir).ok()? {
157 let entry = entry.ok()?;
158 let path = entry.path();
159 if path.is_dir() {
160 self.dirs.push(path);
161 } else if path.is_file() {
162 self.files.push(path);
163 }
164 }
165 }
166 }
167 self.files.pop()
168 }
169 }
170}
diff --git a/bin/src/err.rs b/bin/src/err.rs
new file mode 100644
index 0000000..b3a79c2
--- /dev/null
+++ b/bin/src/err.rs
@@ -0,0 +1,28 @@
1use std::{io, path::PathBuf};
2
3use globset::ErrorKind;
4use rnix::parser::ParseError;
5use thiserror::Error;
6
7#[derive(Error, Debug)]
8pub enum ConfigErr {
9 #[error("error parsing glob `{0:?}`: {1}")]
10 InvalidGlob(Option<String>, ErrorKind),
11
12 #[error("path error: {0}")]
13 InvalidPath(#[from] io::Error),
14}
15
16#[derive(Error, Debug)]
17pub enum LintErr {
18 #[error("[{0}] syntax error: {1}")]
19 Parse(PathBuf, ParseError),
20}
21
22#[derive(Error, Debug)]
23pub enum StatixErr {
24 #[error("linter error: {0}")]
25 Lint(#[from] LintErr),
26 #[error("config error: {0}")]
27 Config(#[from] ConfigErr),
28}
diff --git a/bin/src/main.rs b/bin/src/main.rs
index ab99aee..b26151d 100644
--- a/bin/src/main.rs
+++ b/bin/src/main.rs
@@ -1,20 +1,28 @@
1use std::{ 1#![feature(path_try_exists)]
2 env, fs, 2
3 path::{Path, PathBuf}, 3mod config;
4}; 4mod err;
5mod traits;
5 6
6use anyhow::{Context, Result}; 7use std::io;
7use ariadne::{ 8
8 CharSet, Color, Config as CliConfig, Label, LabelAttach, Report as CliReport, 9use crate::{
9 ReportKind as CliReportKind, Source, 10 err::{LintErr, StatixErr},
11 traits::{LintResult, WriteDiagnostic},
10}; 12};
11use lib::{Report, LINTS};
12use rnix::{TextRange, WalkEvent};
13 13
14fn analyze(source: &str) -> Result<Vec<Report>> { 14use clap::Clap;
15 let parsed = rnix::parse(source).as_result()?; 15use config::{LintConfig, Opts, SubCommand};
16use lib::LINTS;
17use rnix::WalkEvent;
18use vfs::VfsEntry;
16 19
17 Ok(parsed 20fn analyze<'ρ>(vfs_entry: VfsEntry<'ρ>) -> Result<LintResult, LintErr> {
21 let source = vfs_entry.contents;
22 let parsed = rnix::parse(source)
23 .as_result()
24 .map_err(|e| LintErr::Parse(vfs_entry.file_path.to_path_buf(), e))?;
25 let reports = parsed
18 .node() 26 .node()
19 .preorder_with_tokens() 27 .preorder_with_tokens()
20 .filter_map(|event| match event { 28 .filter_map(|event| match event {
@@ -27,61 +35,33 @@ fn analyze(source: &str) -> Result<Vec<Report>> {
27 _ => None, 35 _ => None,
28 }) 36 })
29 .flatten() 37 .flatten()
30 .collect()) 38 .collect();
39 Ok(LintResult {
40 file_id: vfs_entry.file_id,
41 reports,
42 })
31} 43}
32 44
33fn print_report(report: Report, file_src: &str, file_path: &Path) -> Result<()> { 45fn _main() -> Result<(), StatixErr> {
34 let range = |at: TextRange| at.start().into()..at.end().into();
35 let src_id = file_path.to_str().unwrap_or("<unknown>");
36 let offset = report
37 .diagnostics
38 .iter()
39 .map(|d| d.at.start().into())
40 .min()
41 .unwrap_or(0usize);
42 report
43 .diagnostics
44 .iter()
45 .fold(
46 CliReport::build(CliReportKind::Warning, src_id, offset)
47 .with_config(
48 CliConfig::default()
49 .with_cross_gap(true)
50 .with_multiline_arrows(false)
51 .with_label_attach(LabelAttach::Middle)
52 .with_char_set(CharSet::Unicode),
53 )
54 .with_message(report.note)
55 .with_code(report.code),
56 |cli_report, diagnostic| {
57 cli_report.with_label(
58 Label::new((src_id, range(diagnostic.at)))
59 .with_message(&diagnostic.message)
60 .with_color(Color::Magenta),
61 )
62 },
63 )
64 .finish()
65 .eprint((src_id, Source::from(file_src)))
66 .context("failed to print report to stdout")
67}
68
69fn _main() -> Result<()> {
70 // TODO: accept cli args, construct a CLI config with a list of files to analyze 46 // TODO: accept cli args, construct a CLI config with a list of files to analyze
71 let args = env::args(); 47 let opts = Opts::parse();
72 for (file_src, file_path, reports) in args 48 match opts.subcmd {
73 .skip(1) 49 Some(SubCommand::Fix(_)) => {}
74 .map(|s| PathBuf::from(&s)) 50 None => {
75 .filter(|p| p.is_file()) 51 let lint_config = LintConfig::from_opts(opts)?;
76 .filter_map(|path| { 52 let vfs = lint_config.vfs()?;
77 let s = fs::read_to_string(&path).ok()?; 53 let (reports, errors): (Vec<_>, Vec<_>) =
78 analyze(&s) 54 vfs.iter().map(analyze).partition(Result::is_ok);
79 .map(|analysis_result| (s, path, analysis_result)) 55 let lint_results: Vec<_> = reports.into_iter().map(Result::unwrap).collect();
80 .ok() 56 let errors: Vec<_> = errors.into_iter().map(Result::unwrap_err).collect();
81 }) 57
82 { 58 let mut stderr = io::stderr();
83 for r in reports { 59 lint_results.into_iter().for_each(|r| {
84 print_report(r, &file_src, &file_path)? 60 stderr.write(&r, &vfs).unwrap();
61 });
62 errors.into_iter().for_each(|e| {
63 eprintln!("{}", e);
64 });
85 } 65 }
86 } 66 }
87 Ok(()) 67 Ok(())
@@ -90,6 +70,6 @@ fn _main() -> Result<()> {
90fn main() { 70fn main() {
91 match _main() { 71 match _main() {
92 Err(e) => eprintln!("{}", e), 72 Err(e) => eprintln!("{}", e),
93 _ => {} 73 _ => (),
94 } 74 }
95} 75}
diff --git a/bin/src/traits.rs b/bin/src/traits.rs
new file mode 100644
index 0000000..1807ad0
--- /dev/null
+++ b/bin/src/traits.rs
@@ -0,0 +1,83 @@
1use std::{
2 io::{self, Write},
3 str,
4};
5
6use ariadne::{
7 CharSet, Color, Config as CliConfig, Label, LabelAttach, Report as CliReport,
8 ReportKind as CliReportKind, Source, Fmt
9};
10use lib::Report;
11use rnix::TextRange;
12use vfs::{FileId, ReadOnlyVfs};
13
14#[derive(Debug)]
15pub struct LintResult {
16 pub file_id: FileId,
17 pub reports: Vec<Report>,
18}
19
20pub trait WriteDiagnostic {
21 fn write(&mut self, report: &LintResult, vfs: &ReadOnlyVfs) -> io::Result<()>;
22}
23
24impl<T> WriteDiagnostic for T
25where
26 T: Write,
27{
28 fn write(&mut self, lint_result: &LintResult, vfs: &ReadOnlyVfs) -> io::Result<()> {
29 let file_id = lint_result.file_id;
30 let src = str::from_utf8(vfs.get(file_id)).unwrap();
31 let path = vfs.file_path(file_id);
32 let range = |at: TextRange| at.start().into()..at.end().into();
33 let src_id = path.to_str().unwrap_or("<unknown>");
34 for report in lint_result.reports.iter() {
35 let offset = report
36 .diagnostics
37 .iter()
38 .map(|d| d.at.start().into())
39 .min()
40 .unwrap_or(0usize);
41 report
42 .diagnostics
43 .iter()
44 .fold(
45 CliReport::build(CliReportKind::Warning, src_id, offset)
46 .with_config(
47 CliConfig::default()
48 .with_cross_gap(true)
49 .with_multiline_arrows(false)
50 .with_label_attach(LabelAttach::Middle)
51 .with_char_set(CharSet::Unicode),
52 )
53 .with_message(report.note)
54 .with_code(report.code),
55 |cli_report, diagnostic| {
56 cli_report.with_label(
57 Label::new((src_id, range(diagnostic.at)))
58 .with_message(&colorize(&diagnostic.message))
59 .with_color(Color::Magenta),
60 )
61 },
62 )
63 .finish()
64 .write((src_id, Source::from(src)), &mut *self)?;
65 }
66 Ok(())
67 }
68}
69
70// everything within backticks is colorized, backticks are removed
71fn colorize(message: &str) -> String {
72 message.split('`')
73 .enumerate()
74 .map(|(idx, part)| {
75 if idx % 2 == 1 {
76 part.fg(Color::Cyan).to_string()
77 } else {
78 part.to_string()
79 }
80 })
81 .collect::<Vec<_>>()
82 .join("")
83}