use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum SkipReason {
NotAFile,
UnsupportedLanguage,
IgnoredInternal,
IgnoredByGitignore,
ExcludedByGlob,
}
impl SkipReason {
pub fn sort_key(&self) -> u8 {
match self {
SkipReason::IgnoredInternal => 0, SkipReason::IgnoredByGitignore => 1, SkipReason::ExcludedByGlob => 2, SkipReason::UnsupportedLanguage => 3, SkipReason::NotAFile => 4, }
}
pub fn description(&self) -> &'static str {
match self {
SkipReason::NotAFile => "not a regular file",
SkipReason::UnsupportedLanguage => "language not supported",
SkipReason::IgnoredInternal => "internal ignore rule",
SkipReason::IgnoredByGitignore => "matched by gitignore",
SkipReason::ExcludedByGlob => "excluded by pattern",
}
}
}
impl fmt::Display for SkipReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl PartialOrd for SkipReason {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SkipReason {
fn cmp(&self, other: &Self) -> Ordering {
self.sort_key().cmp(&other.sort_key())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum DiagnosticStage {
Read,
Parse,
IndexSymbols,
IndexReferences,
IndexCalls,
Other,
}
impl DiagnosticStage {
pub fn sort_key(&self) -> u8 {
match self {
DiagnosticStage::Read => 0,
DiagnosticStage::Parse => 1,
DiagnosticStage::IndexSymbols => 2,
DiagnosticStage::IndexReferences => 3,
DiagnosticStage::IndexCalls => 4,
DiagnosticStage::Other => 5,
}
}
pub fn description(&self) -> &'static str {
match self {
DiagnosticStage::Read => "reading file",
DiagnosticStage::Parse => "parsing source",
DiagnosticStage::IndexSymbols => "indexing symbols",
DiagnosticStage::IndexReferences => "indexing references",
DiagnosticStage::IndexCalls => "indexing calls",
DiagnosticStage::Other => "processing",
}
}
}
impl fmt::Display for DiagnosticStage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl PartialOrd for DiagnosticStage {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DiagnosticStage {
fn cmp(&self, other: &Self) -> Ordering {
self.sort_key().cmp(&other.sort_key())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum WatchDiagnostic {
Skipped {
path: String,
reason: SkipReason,
},
Error {
path: String,
stage: DiagnosticStage,
message: String,
},
}
impl WatchDiagnostic {
pub fn path(&self) -> &str {
match self {
WatchDiagnostic::Skipped { path, .. } => path,
WatchDiagnostic::Error { path, .. } => path,
}
}
pub fn sort_key(&self) -> (&str, u8, u8) {
match self {
WatchDiagnostic::Error { path, stage, .. } => (path, 0, stage.sort_key()),
WatchDiagnostic::Skipped { path, reason } => (path, 1, reason.sort_key()),
}
}
pub fn skipped(path: String, reason: SkipReason) -> Self {
WatchDiagnostic::Skipped { path, reason }
}
pub fn error(path: String, stage: DiagnosticStage, message: String) -> Self {
WatchDiagnostic::Error {
path,
stage,
message,
}
}
pub fn format_stderr(&self) -> String {
match self {
WatchDiagnostic::Skipped { path, reason } => {
format!("SKIP {}: {}", path, reason)
}
WatchDiagnostic::Error {
path,
stage,
message,
} => {
format!("ERROR {}: {}: {}", path, stage, message)
}
}
}
}
impl fmt::Display for WatchDiagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.format_stderr())
}
}
impl PartialOrd for WatchDiagnostic {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for WatchDiagnostic {
fn cmp(&self, other: &Self) -> Ordering {
self.sort_key().cmp(&other.sort_key())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_skip_reason_sort_key() {
assert!(SkipReason::IgnoredInternal.sort_key() < SkipReason::IgnoredByGitignore.sort_key());
assert!(SkipReason::IgnoredByGitignore.sort_key() < SkipReason::ExcludedByGlob.sort_key());
}
#[test]
fn test_skip_reason_ord() {
assert!(SkipReason::IgnoredInternal < SkipReason::IgnoredByGitignore);
assert!(SkipReason::IgnoredByGitignore < SkipReason::ExcludedByGlob);
}
#[test]
fn test_diagnostic_stage_sort_key() {
assert_eq!(DiagnosticStage::Read.sort_key(), 0);
assert_eq!(DiagnosticStage::Parse.sort_key(), 1);
assert_eq!(DiagnosticStage::IndexSymbols.sort_key(), 2);
}
#[test]
fn test_diagnostic_stage_ord() {
assert!(DiagnosticStage::Read < DiagnosticStage::Parse);
assert!(DiagnosticStage::Parse < DiagnosticStage::IndexSymbols);
}
#[test]
fn test_watch_diagnostic_skipped() {
let diag =
WatchDiagnostic::skipped("src/test.rs".to_string(), SkipReason::UnsupportedLanguage);
assert_eq!(diag.path(), "src/test.rs");
assert!(matches!(diag, WatchDiagnostic::Skipped { .. }));
}
#[test]
fn test_watch_diagnostic_error() {
let diag = WatchDiagnostic::error(
"src/bad.rs".to_string(),
DiagnosticStage::Parse,
"unexpected token".to_string(),
);
assert_eq!(diag.path(), "src/bad.rs");
assert!(matches!(diag, WatchDiagnostic::Error { .. }));
}
#[test]
fn test_watch_diagnostic_sort_key() {
let error = WatchDiagnostic::error(
"src/a.rs".to_string(),
DiagnosticStage::Parse,
"error".to_string(),
);
let skipped =
WatchDiagnostic::skipped("src/a.rs".to_string(), SkipReason::UnsupportedLanguage);
let error_key = error.sort_key();
let skipped_key = skipped.sort_key();
assert_eq!(error_key.0, skipped_key.0); assert!(error_key.1 < skipped_key.1); }
#[test]
fn test_watch_diagnostic_ord() {
let d1 = WatchDiagnostic::skipped("src/b.rs".to_string(), SkipReason::ExcludedByGlob);
let d2 = WatchDiagnostic::skipped("src/a.rs".to_string(), SkipReason::ExcludedByGlob);
assert!(d2 < d1); }
#[test]
fn test_format_stderr_skipped() {
let diag =
WatchDiagnostic::skipped("target/lib.rs".to_string(), SkipReason::IgnoredInternal);
let formatted = diag.format_stderr();
assert_eq!(formatted, "SKIP target/lib.rs: internal ignore rule");
}
#[test]
fn test_format_stderr_error() {
let diag = WatchDiagnostic::error(
"src/bad.rs".to_string(),
DiagnosticStage::Parse,
"unexpected end of file".to_string(),
);
let formatted = diag.format_stderr();
assert_eq!(
formatted,
"ERROR src/bad.rs: parsing source: unexpected end of file"
);
}
#[test]
fn test_skip_reason_display() {
assert_eq!(
SkipReason::UnsupportedLanguage.to_string(),
"language not supported"
);
assert_eq!(
SkipReason::IgnoredInternal.to_string(),
"internal ignore rule"
);
}
#[test]
fn test_diagnostic_stage_display() {
assert_eq!(DiagnosticStage::Read.to_string(), "reading file");
assert_eq!(DiagnosticStage::Parse.to_string(), "parsing source");
}
#[test]
fn test_watch_diagnostic_sorting_vec() {
let mut diagnostics = vec![
WatchDiagnostic::skipped("src/c.rs".to_string(), SkipReason::ExcludedByGlob),
WatchDiagnostic::error(
"src/a.rs".to_string(),
DiagnosticStage::Read,
"error".to_string(),
),
WatchDiagnostic::skipped("src/b.rs".to_string(), SkipReason::IgnoredInternal),
];
diagnostics.sort();
assert_eq!(diagnostics[0].path(), "src/a.rs"); assert_eq!(diagnostics[1].path(), "src/b.rs");
assert_eq!(diagnostics[2].path(), "src/c.rs");
}
}