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
This commit is contained in:
Akshay 2021-10-29 18:20:54 +05:30
parent d510714ed5
commit 1a97cce01f
6 changed files with 89 additions and 41 deletions

View File

@ -190,9 +190,12 @@ fn walk_with_ignores<P: AsRef<Path>>(
fn vfs(files: Vec<PathBuf>) -> Result<ReadOnlyVfs, ConfigErr> {
let mut vfs = ReadOnlyVfs::default();
for file in 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());
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)
}

View File

@ -1,7 +1,7 @@
use std::{io, path::PathBuf};
use std::io;
use globset::ErrorKind;
use rnix::parser::ParseError;
// use rnix::parser::ParseError;
use thiserror::Error;
#[derive(Error, Debug)]
@ -14,16 +14,16 @@ pub enum ConfigErr {
InvalidPosition(String),
}
#[derive(Error, Debug)]
pub enum LintErr {
#[error("[{0}] syntax error: {1}")]
Parse(PathBuf, ParseError),
}
// #[derive(Error, Debug)]
// pub enum LintErr {
// #[error("[{0}] syntax error: {1}")]
// Parse(PathBuf, ParseError),
// }
#[derive(Error, Debug)]
pub enum FixErr {
#[error("[{0}] syntax error: {1}")]
Parse(PathBuf, ParseError),
// #[error("[{0}] syntax error: {1}")]
// Parse(PathBuf, ParseError),
#[error("path error: {0}")]
InvalidPath(#[from] io::Error),
}
@ -42,8 +42,8 @@ pub enum SingleFixErr {
#[derive(Error, Debug)]
pub enum StatixErr {
#[error("linter error: {0}")]
Lint(#[from] LintErr),
// #[error("linter error: {0}")]
// Lint(#[from] LintErr),
#[error("fixer error: {0}")]
Fix(#[from] FixErr),
#[error("single fix error: {0}")]

View File

@ -1,5 +1,3 @@
use crate::err::LintErr;
use lib::{Report, LINTS};
use rnix::WalkEvent;
use vfs::{FileId, VfsEntry};
@ -10,11 +8,16 @@ pub struct LintResult {
pub reports: Vec<Report>,
}
pub fn lint(vfs_entry: VfsEntry) -> Result<LintResult, LintErr> {
pub fn lint(vfs_entry: VfsEntry) -> LintResult {
let file_id = vfs_entry.file_id;
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 parsed = rnix::parse(source);
let error_reports = parsed
.errors()
.into_iter()
.map(|e| Report::from_parse_err(e));
let reports = parsed
.node()
.preorder_with_tokens()
@ -28,9 +31,8 @@ pub fn lint(vfs_entry: VfsEntry) -> Result<LintResult, LintErr> {
_ => None,
})
.flatten()
.chain(error_reports)
.collect();
Ok(LintResult {
file_id: vfs_entry.file_id,
reports,
})
LintResult { file_id, reports }
}

View File

@ -20,17 +20,9 @@ fn _main() -> Result<(), StatixErr> {
match opts.cmd {
SubCommand::Check(check_config) => {
let vfs = check_config.vfs()?;
let (lints, errors): (Vec<_>, Vec<_>) =
vfs.iter().map(lint::lint).partition(Result::is_ok);
let lint_results = lints.into_iter().map(Result::unwrap);
let errors = errors.into_iter().map(Result::unwrap_err);
let mut stdout = io::stdout();
lint_results.for_each(|r| {
stdout.write(&r, &vfs, check_config.format).unwrap();
});
errors.for_each(|e| {
eprintln!("{}", e);
let mut stderr = io::stderr();
vfs.iter().map(lint::lint).for_each(|r| {
stderr.write(&r, &vfs, check_config.format).unwrap();
});
}
SubCommand::Fix(fix_config) => {

View File

@ -9,6 +9,7 @@ use ariadne::{
CharSet, Color, Config as CliConfig, Fmt, Label, LabelAttach, Report as CliReport,
ReportKind as CliReportKind, Source,
};
use lib::Severity;
use rnix::{TextRange, TextSize};
use vfs::ReadOnlyVfs;
@ -57,11 +58,16 @@ fn write_stderr<T: Write>(
.map(|d| d.at.start().into())
.min()
.unwrap_or(0usize);
let report_kind = match report.severity {
Severity::Warn => CliReportKind::Warning,
Severity::Error => CliReportKind::Error,
Severity::Hint => CliReportKind::Advice,
};
report
.diagnostics
.iter()
.fold(
CliReport::build(CliReportKind::Warning, src_id, offset)
CliReport::build(report_kind, src_id, offset)
.with_config(
CliConfig::default()
.with_cross_gap(true)
@ -103,7 +109,11 @@ fn write_errfmt<T: Write>(
filename = path.to_str().unwrap_or("<unknown>"),
linenumber = line,
columnnumber = col,
errortype = "W",
errortype = match report.severity {
Severity::Warn => "W",
Severity::Error => "E",
Severity::Hint => "I", /* "info" message */
},
errornumber = report.code,
errormessage = diagnostic.message
)?;
@ -118,6 +128,7 @@ mod json {
use std::io::{self, Write};
use lib::Severity;
use rnix::TextRange;
use serde::Serialize;
use serde_json;
@ -134,6 +145,7 @@ mod json {
struct JsonReport<'μ> {
note: &'static str,
code: u32,
severity: &'μ Severity,
diagnostics: Vec<JsonDiagnostic<'μ>>,
}
@ -192,6 +204,7 @@ mod json {
.map(|r| {
let note = r.note;
let code = r.code;
let severity = &r.severity;
let diagnostics = r
.diagnostics
.iter()
@ -207,6 +220,7 @@ mod json {
JsonReport {
note,
code,
severity,
diagnostics,
}
})

View File

@ -4,7 +4,7 @@ mod make;
pub use lints::LINTS;
use rnix::{SyntaxElement, SyntaxKind, TextRange};
use rnix::{parser::ParseError, SyntaxElement, SyntaxKind, TextRange};
use std::{convert::Into, default::Default};
#[cfg(feature = "json-out")]
@ -13,10 +13,18 @@ use serde::{
Serialize,
};
#[derive(Debug)]
#[cfg_attr(feature = "json-out", derive(Serialize))]
pub struct Position {
line: usize,
column: usize,
pub enum Severity {
Warn,
Error,
Hint,
}
impl Default for Severity {
fn default() -> Self {
Self::Warn
}
}
/// Report generated by a lint
@ -27,6 +35,8 @@ pub struct Report {
pub note: &'static str,
/// An error code to uniquely identify this lint
pub code: u32,
/// Report severity level
pub severity: Severity,
/// Collection of diagnostics raised by this lint
pub diagnostics: Vec<Diagnostic>,
}
@ -56,6 +66,11 @@ impl Report {
.push(Diagnostic::suggest(at, message, suggestion));
self
}
/// Set severity level
pub fn severity(mut self, severity: Severity) -> Self {
self.severity = severity;
self
}
/// A range that encompasses all the suggestions provided in this report
pub fn total_suggestion_range(&self) -> Option<TextRange> {
self.diagnostics
@ -80,6 +95,28 @@ impl Report {
d.apply(src);
}
}
/// Create a report out of a parse error
pub fn from_parse_err(err: ParseError) -> Self {
let at = match err {
ParseError::Unexpected(at) => at,
ParseError::UnexpectedExtra(at) => at,
ParseError::UnexpectedWanted(_, at, _) => at,
ParseError::UnexpectedDoubleBind(at) => at,
ParseError::UnexpectedEOF | ParseError::UnexpectedEOFWanted(_) => {
TextRange::empty(0u32.into())
}
_ => panic!("report a bug, pepper forgot to handle a parse error"),
};
let mut message = err.to_string();
message
.as_mut_str()
.get_mut(0..1)
.unwrap()
.make_ascii_uppercase();
Self::new("Syntax error", 0)
.diagnostic(at, message)
.severity(Severity::Error)
}
}
/// Mapping from a bytespan to an error message.