aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-10-29 13:50:54 +0100
committerAkshay <[email protected]>2021-10-29 14:14:14 +0100
commit1a97cce01f8e49b33bf28cbcdfeb3c8aefd809a5 (patch)
tree62613f193d84ea8b0c0aa94d09758fee19a6042d
parentd510714ed5a1eae0f6e5e435e4cff4875b06751d (diff)
report syntax errors as statix errors
- statix now reports errors also, not just warnings - all diagnostics are available on stderr stream - non-utf8 files are skipped, does not eject early
-rw-r--r--bin/src/config.rs9
-rw-r--r--bin/src/err.rs22
-rw-r--r--bin/src/lint.rs22
-rw-r--r--bin/src/main.rs14
-rw-r--r--bin/src/traits.rs18
-rw-r--r--lib/src/lib.rs45
6 files changed, 89 insertions, 41 deletions
diff --git a/bin/src/config.rs b/bin/src/config.rs
index cbb1f66..ef3a29d 100644
--- a/bin/src/config.rs
+++ b/bin/src/config.rs
@@ -190,9 +190,12 @@ fn walk_with_ignores<P: AsRef<Path>>(
190fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> { 190fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> {
191 let mut vfs = ReadOnlyVfs::default(); 191 let mut vfs = ReadOnlyVfs::default();
192 for file in files.iter() { 192 for file in files.iter() {
193 let _id = vfs.alloc_file_id(&file); 193 if let Ok(data) = fs::read_to_string(&file) {
194 let data = fs::read_to_string(&file).map_err(ConfigErr::InvalidPath)?; 194 let _id = vfs.alloc_file_id(&file);
195 vfs.set_file_contents(&file, data.as_bytes()); 195 vfs.set_file_contents(&file, data.as_bytes());
196 } else {
197 println!("{} contains non-utf8 content", file.display());
198 };
196 } 199 }
197 Ok(vfs) 200 Ok(vfs)
198} 201}
diff --git a/bin/src/err.rs b/bin/src/err.rs
index 53c3222..4c16d69 100644
--- a/bin/src/err.rs
+++ b/bin/src/err.rs
@@ -1,7 +1,7 @@
1use std::{io, path::PathBuf}; 1use std::io;
2 2
3use globset::ErrorKind; 3use globset::ErrorKind;
4use rnix::parser::ParseError; 4// use rnix::parser::ParseError;
5use thiserror::Error; 5use thiserror::Error;
6 6
7#[derive(Error, Debug)] 7#[derive(Error, Debug)]
@@ -14,16 +14,16 @@ pub enum ConfigErr {
14 InvalidPosition(String), 14 InvalidPosition(String),
15} 15}
16 16
17#[derive(Error, Debug)] 17// #[derive(Error, Debug)]
18pub enum LintErr { 18// pub enum LintErr {
19 #[error("[{0}] syntax error: {1}")] 19// #[error("[{0}] syntax error: {1}")]
20 Parse(PathBuf, ParseError), 20// Parse(PathBuf, ParseError),
21} 21// }
22 22
23#[derive(Error, Debug)] 23#[derive(Error, Debug)]
24pub enum FixErr { 24pub enum FixErr {
25 #[error("[{0}] syntax error: {1}")] 25 // #[error("[{0}] syntax error: {1}")]
26 Parse(PathBuf, ParseError), 26 // Parse(PathBuf, ParseError),
27 #[error("path error: {0}")] 27 #[error("path error: {0}")]
28 InvalidPath(#[from] io::Error), 28 InvalidPath(#[from] io::Error),
29} 29}
@@ -42,8 +42,8 @@ pub enum SingleFixErr {
42 42
43#[derive(Error, Debug)] 43#[derive(Error, Debug)]
44pub enum StatixErr { 44pub enum StatixErr {
45 #[error("linter error: {0}")] 45 // #[error("linter error: {0}")]
46 Lint(#[from] LintErr), 46 // Lint(#[from] LintErr),
47 #[error("fixer error: {0}")] 47 #[error("fixer error: {0}")]
48 Fix(#[from] FixErr), 48 Fix(#[from] FixErr),
49 #[error("single fix error: {0}")] 49 #[error("single fix error: {0}")]
diff --git a/bin/src/lint.rs b/bin/src/lint.rs
index 65ae824..e889d31 100644
--- a/bin/src/lint.rs
+++ b/bin/src/lint.rs
@@ -1,5 +1,3 @@
1use crate::err::LintErr;
2
3use lib::{Report, LINTS}; 1use lib::{Report, LINTS};
4use rnix::WalkEvent; 2use rnix::WalkEvent;
5use vfs::{FileId, VfsEntry}; 3use vfs::{FileId, VfsEntry};
@@ -10,11 +8,16 @@ pub struct LintResult {
10 pub reports: Vec<Report>, 8 pub reports: Vec<Report>,
11} 9}
12 10
13pub fn lint(vfs_entry: VfsEntry) -> Result<LintResult, LintErr> { 11pub fn lint(vfs_entry: VfsEntry) -> LintResult {
12 let file_id = vfs_entry.file_id;
14 let source = vfs_entry.contents; 13 let source = vfs_entry.contents;
15 let parsed = rnix::parse(source) 14 let parsed = rnix::parse(source);
16 .as_result() 15
17 .map_err(|e| LintErr::Parse(vfs_entry.file_path.to_path_buf(), e))?; 16 let error_reports = parsed
17 .errors()
18 .into_iter()
19 .map(|e| Report::from_parse_err(e));
20
18 let reports = parsed 21 let reports = parsed
19 .node() 22 .node()
20 .preorder_with_tokens() 23 .preorder_with_tokens()
@@ -28,9 +31,8 @@ pub fn lint(vfs_entry: VfsEntry) -> Result<LintResult, LintErr> {
28 _ => None, 31 _ => None,
29 }) 32 })
30 .flatten() 33 .flatten()
34 .chain(error_reports)
31 .collect(); 35 .collect();
32 Ok(LintResult { 36
33 file_id: vfs_entry.file_id, 37 LintResult { file_id, reports }
34 reports,
35 })
36} 38}
diff --git a/bin/src/main.rs b/bin/src/main.rs
index c5d0626..90b79ce 100644
--- a/bin/src/main.rs
+++ b/bin/src/main.rs
@@ -20,17 +20,9 @@ fn _main() -> Result<(), StatixErr> {
20 match opts.cmd { 20 match opts.cmd {
21 SubCommand::Check(check_config) => { 21 SubCommand::Check(check_config) => {
22 let vfs = check_config.vfs()?; 22 let vfs = check_config.vfs()?;
23 let (lints, errors): (Vec<_>, Vec<_>) = 23 let mut stderr = io::stderr();
24 vfs.iter().map(lint::lint).partition(Result::is_ok); 24 vfs.iter().map(lint::lint).for_each(|r| {
25 let lint_results = lints.into_iter().map(Result::unwrap); 25 stderr.write(&r, &vfs, check_config.format).unwrap();
26 let errors = errors.into_iter().map(Result::unwrap_err);
27
28 let mut stdout = io::stdout();
29 lint_results.for_each(|r| {
30 stdout.write(&r, &vfs, check_config.format).unwrap();
31 });
32 errors.for_each(|e| {
33 eprintln!("{}", e);
34 }); 26 });
35 } 27 }
36 SubCommand::Fix(fix_config) => { 28 SubCommand::Fix(fix_config) => {
diff --git a/bin/src/traits.rs b/bin/src/traits.rs
index 465abe4..a8ec70f 100644
--- a/bin/src/traits.rs
+++ b/bin/src/traits.rs
@@ -9,6 +9,7 @@ use ariadne::{
9 CharSet, Color, Config as CliConfig, Fmt, Label, LabelAttach, Report as CliReport, 9 CharSet, Color, Config as CliConfig, Fmt, Label, LabelAttach, Report as CliReport,
10 ReportKind as CliReportKind, Source, 10 ReportKind as CliReportKind, Source,
11}; 11};
12use lib::Severity;
12use rnix::{TextRange, TextSize}; 13use rnix::{TextRange, TextSize};
13use vfs::ReadOnlyVfs; 14use vfs::ReadOnlyVfs;
14 15
@@ -57,11 +58,16 @@ fn write_stderr<T: Write>(
57 .map(|d| d.at.start().into()) 58 .map(|d| d.at.start().into())
58 .min() 59 .min()
59 .unwrap_or(0usize); 60 .unwrap_or(0usize);
61 let report_kind = match report.severity {
62 Severity::Warn => CliReportKind::Warning,
63 Severity::Error => CliReportKind::Error,
64 Severity::Hint => CliReportKind::Advice,
65 };
60 report 66 report
61 .diagnostics 67 .diagnostics
62 .iter() 68 .iter()
63 .fold( 69 .fold(
64 CliReport::build(CliReportKind::Warning, src_id, offset) 70 CliReport::build(report_kind, src_id, offset)
65 .with_config( 71 .with_config(
66 CliConfig::default() 72 CliConfig::default()
67 .with_cross_gap(true) 73 .with_cross_gap(true)
@@ -103,7 +109,11 @@ fn write_errfmt<T: Write>(
103 filename = path.to_str().unwrap_or("<unknown>"), 109 filename = path.to_str().unwrap_or("<unknown>"),
104 linenumber = line, 110 linenumber = line,
105 columnnumber = col, 111 columnnumber = col,
106 errortype = "W", 112 errortype = match report.severity {
113 Severity::Warn => "W",
114 Severity::Error => "E",
115 Severity::Hint => "I", /* "info" message */
116 },
107 errornumber = report.code, 117 errornumber = report.code,
108 errormessage = diagnostic.message 118 errormessage = diagnostic.message
109 )?; 119 )?;
@@ -118,6 +128,7 @@ mod json {
118 128
119 use std::io::{self, Write}; 129 use std::io::{self, Write};
120 130
131 use lib::Severity;
121 use rnix::TextRange; 132 use rnix::TextRange;
122 use serde::Serialize; 133 use serde::Serialize;
123 use serde_json; 134 use serde_json;
@@ -134,6 +145,7 @@ mod json {
134 struct JsonReport<'μ> { 145 struct JsonReport<'μ> {
135 note: &'static str, 146 note: &'static str,
136 code: u32, 147 code: u32,
148 severity: &'μ Severity,
137 diagnostics: Vec<JsonDiagnostic<'μ>>, 149 diagnostics: Vec<JsonDiagnostic<'μ>>,
138 } 150 }
139 151
@@ -192,6 +204,7 @@ mod json {
192 .map(|r| { 204 .map(|r| {
193 let note = r.note; 205 let note = r.note;
194 let code = r.code; 206 let code = r.code;
207 let severity = &r.severity;
195 let diagnostics = r 208 let diagnostics = r
196 .diagnostics 209 .diagnostics
197 .iter() 210 .iter()
@@ -207,6 +220,7 @@ mod json {
207 JsonReport { 220 JsonReport {
208 note, 221 note,
209 code, 222 code,
223 severity,
210 diagnostics, 224 diagnostics,
211 } 225 }
212 }) 226 })
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index ec96f50..196cbf8 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -4,7 +4,7 @@ mod make;
4 4
5pub use lints::LINTS; 5pub use lints::LINTS;
6 6
7use rnix::{SyntaxElement, SyntaxKind, TextRange}; 7use rnix::{parser::ParseError, SyntaxElement, SyntaxKind, TextRange};
8use std::{convert::Into, default::Default}; 8use std::{convert::Into, default::Default};
9 9
10#[cfg(feature = "json-out")] 10#[cfg(feature = "json-out")]
@@ -13,10 +13,18 @@ use serde::{
13 Serialize, 13 Serialize,
14}; 14};
15 15
16#[derive(Debug)]
16#[cfg_attr(feature = "json-out", derive(Serialize))] 17#[cfg_attr(feature = "json-out", derive(Serialize))]
17pub struct Position { 18pub enum Severity {
18 line: usize, 19 Warn,
19 column: usize, 20 Error,
21 Hint,
22}
23
24impl Default for Severity {
25 fn default() -> Self {
26 Self::Warn
27 }
20} 28}
21 29
22/// Report generated by a lint 30/// Report generated by a lint
@@ -27,6 +35,8 @@ pub struct Report {
27 pub note: &'static str, 35 pub note: &'static str,
28 /// An error code to uniquely identify this lint 36 /// An error code to uniquely identify this lint
29 pub code: u32, 37 pub code: u32,
38 /// Report severity level
39 pub severity: Severity,
30 /// Collection of diagnostics raised by this lint 40 /// Collection of diagnostics raised by this lint
31 pub diagnostics: Vec<Diagnostic>, 41 pub diagnostics: Vec<Diagnostic>,
32} 42}
@@ -56,6 +66,11 @@ impl Report {
56 .push(Diagnostic::suggest(at, message, suggestion)); 66 .push(Diagnostic::suggest(at, message, suggestion));
57 self 67 self
58 } 68 }
69 /// Set severity level
70 pub fn severity(mut self, severity: Severity) -> Self {
71 self.severity = severity;
72 self
73 }
59 /// A range that encompasses all the suggestions provided in this report 74 /// A range that encompasses all the suggestions provided in this report
60 pub fn total_suggestion_range(&self) -> Option<TextRange> { 75 pub fn total_suggestion_range(&self) -> Option<TextRange> {
61 self.diagnostics 76 self.diagnostics
@@ -80,6 +95,28 @@ impl Report {
80 d.apply(src); 95 d.apply(src);
81 } 96 }
82 } 97 }
98 /// Create a report out of a parse error
99 pub fn from_parse_err(err: ParseError) -> Self {
100 let at = match err {
101 ParseError::Unexpected(at) => at,
102 ParseError::UnexpectedExtra(at) => at,
103 ParseError::UnexpectedWanted(_, at, _) => at,
104 ParseError::UnexpectedDoubleBind(at) => at,
105 ParseError::UnexpectedEOF | ParseError::UnexpectedEOFWanted(_) => {
106 TextRange::empty(0u32.into())
107 }
108 _ => panic!("report a bug, pepper forgot to handle a parse error"),
109 };
110 let mut message = err.to_string();
111 message
112 .as_mut_str()
113 .get_mut(0..1)
114 .unwrap()
115 .make_ascii_uppercase();
116 Self::new("Syntax error", 0)
117 .diagnostic(at, message)
118 .severity(Severity::Error)
119 }
83} 120}
84 121
85/// Mapping from a bytespan to an error message. 122/// Mapping from a bytespan to an error message.