From 1a97cce01f8e49b33bf28cbcdfeb3c8aefd809a5 Mon Sep 17 00:00:00 2001 From: Akshay Date: Fri, 29 Oct 2021 18:20:54 +0530 Subject: 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 --- bin/src/config.rs | 9 ++++++--- bin/src/err.rs | 22 +++++++++++----------- bin/src/lint.rs | 22 ++++++++++++---------- bin/src/main.rs | 14 +++----------- bin/src/traits.rs | 18 ++++++++++++++++-- lib/src/lib.rs | 45 +++++++++++++++++++++++++++++++++++++++++---- 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>( fn vfs(files: Vec) -> Result { 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) } 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 @@ -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}")] 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 @@ -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, } -pub fn lint(vfs_entry: VfsEntry) -> Result { +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 { _ => None, }) .flatten() + .chain(error_reports) .collect(); - Ok(LintResult { - file_id: vfs_entry.file_id, - reports, - }) + + LintResult { file_id, reports } } 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> { 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) => { 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::{ 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( .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( filename = path.to_str().unwrap_or(""), 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>, } @@ -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, } }) 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; 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, } @@ -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 { 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. -- cgit v1.2.3