mirror of
https://github.com/nerdypepper/statix.git
synced 2024-09-11 13:45:32 +03:00
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:
parent
d510714ed5
commit
1a97cce01f
@ -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)
|
||||
}
|
||||
|
@ -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}")]
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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,
|
||||
}
|
||||
})
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user