diff --git a/crates/typos/src/checks.rs b/crates/typos/src/checks.rs index 9a2404d..20864a7 100644 --- a/crates/typos/src/checks.rs +++ b/crates/typos/src/checks.rs @@ -225,39 +225,38 @@ impl Checks { } let mut typos_found = false; - for ident in path - .file_name() - .and_then(|s| s.to_str()) - .iter() - .flat_map(|part| parser.parse(part)) - { - match dictionary.correct_ident(ident) { - Some(Status::Valid) => {} - Some(corrections) => { - let byte_offset = ident.offset(); - let msg = report::PathTypo { - path, - byte_offset, - typo: ident.token(), - corrections, - }; - typos_found |= reporter.report(msg.into()); - } - None => { - for word in ident.split() { - match dictionary.correct_word(word) { - Some(Status::Valid) => {} - Some(corrections) => { - let byte_offset = word.offset(); - let msg = report::PathTypo { - path, - byte_offset, - typo: word.token(), - corrections, - }; - typos_found |= reporter.report(msg.into()); + if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) { + for ident in parser.parse(file_name) { + match dictionary.correct_ident(ident) { + Some(Status::Valid) => {} + Some(corrections) => { + let byte_offset = ident.offset(); + let msg = report::Typo { + context: report::PathContext { path }.into(), + buffer: std::borrow::Cow::Borrowed(file_name.as_bytes()), + byte_offset, + typo: ident.token(), + corrections, + }; + typos_found |= reporter.report(msg.into()); + } + None => { + for word in ident.split() { + match dictionary.correct_word(word) { + Some(Status::Valid) => {} + Some(corrections) => { + let byte_offset = word.offset(); + let msg = report::Typo { + context: report::PathContext { path }.into(), + buffer: std::borrow::Cow::Borrowed(file_name.as_bytes()), + byte_offset, + typo: word.token(), + corrections, + }; + typos_found |= reporter.report(msg.into()); + } + None => {} } - None => {} } } } @@ -296,10 +295,9 @@ impl Checks { Some(Status::Valid) => {} Some(corrections) => { let byte_offset = ident.offset(); - let msg = report::FileTypo { - path, - line, - line_num, + let msg = report::Typo { + context: report::FileContext { path, line_num }.into(), + buffer: std::borrow::Cow::Borrowed(line), byte_offset, typo: ident.token(), corrections, @@ -312,10 +310,9 @@ impl Checks { Some(Status::Valid) => {} Some(corrections) => { let byte_offset = word.offset(); - let msg = report::FileTypo { - path, - line, - line_num, + let msg = report::Typo { + context: report::FileContext { path, line_num }.into(), + buffer: std::borrow::Cow::Borrowed(line), byte_offset, typo: word.token(), corrections, diff --git a/crates/typos/src/report.rs b/crates/typos/src/report.rs index 3626ebd..9131fc7 100644 --- a/crates/typos/src/report.rs +++ b/crates/typos/src/report.rs @@ -1,5 +1,6 @@ #![allow(clippy::needless_update)] +use std::borrow::Cow; use std::io::{self, Write}; #[derive(Clone, Debug, serde::Serialize, derive_more::From)] @@ -8,8 +9,7 @@ use std::io::{self, Write}; #[non_exhaustive] pub enum Message<'m> { BinaryFile(BinaryFile<'m>), - FileTypo(FileTypo<'m>), - PathTypo(PathTypo<'m>), + Typo(Typo<'m>), File(File<'m>), Parse(Parse<'m>), PathError(PathError<'m>), @@ -20,8 +20,7 @@ impl<'m> Message<'m> { pub fn is_correction(&self) -> bool { match self { Message::BinaryFile(_) => false, - Message::FileTypo(c) => c.corrections.is_correction(), - Message::PathTypo(c) => c.corrections.is_correction(), + Message::Typo(c) => c.corrections.is_correction(), Message::File(_) => false, Message::Parse(_) => false, Message::PathError(_) => false, @@ -32,8 +31,7 @@ impl<'m> Message<'m> { pub fn is_error(&self) -> bool { match self { Message::BinaryFile(_) => false, - Message::FileTypo(_) => false, - Message::PathTypo(_) => false, + Message::Typo(_) => false, Message::File(_) => false, Message::Parse(_) => false, Message::PathError(_) => true, @@ -51,22 +49,20 @@ pub struct BinaryFile<'m> { #[derive(Clone, Debug, serde::Serialize, derive_setters::Setters)] #[non_exhaustive] -pub struct FileTypo<'m> { - pub path: &'m std::path::Path, +pub struct Typo<'m> { + pub context: Context<'m>, #[serde(skip)] - pub line: &'m [u8], - pub line_num: usize, + pub buffer: Cow<'m, [u8]>, pub byte_offset: usize, pub typo: &'m str, pub corrections: crate::Status<'m>, } -impl<'m> Default for FileTypo<'m> { +impl<'m> Default for Typo<'m> { fn default() -> Self { Self { - path: std::path::Path::new("-"), - line: b"", - line_num: 0, + context: Context::None, + buffer: Cow::Borrowed(&[]), byte_offset: 0, typo: "", corrections: crate::Status::Invalid, @@ -74,22 +70,56 @@ impl<'m> Default for FileTypo<'m> { } } -#[derive(Clone, Debug, serde::Serialize, derive_setters::Setters)] +#[derive(Clone, Debug, serde::Serialize, derive_more::From)] #[non_exhaustive] -pub struct PathTypo<'m> { - pub path: &'m std::path::Path, - pub byte_offset: usize, - pub typo: &'m str, - pub corrections: crate::Status<'m>, +pub enum Context<'m> { + File(FileContext<'m>), + Path(PathContext<'m>), + None, } -impl<'m> Default for PathTypo<'m> { +impl<'m> Default for Context<'m> { + fn default() -> Self { + Context::None + } +} + +impl<'m> std::fmt::Display for Context<'m> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + match self { + Context::File(c) => write!(f, "{}:{}", c.path.display(), c.line_num), + Context::Path(c) => write!(f, "{}", c.path.display()), + Context::None => Ok(()), + } + } +} + +#[derive(Clone, Debug, serde::Serialize, derive_setters::Setters)] +#[non_exhaustive] +pub struct FileContext<'m> { + pub path: &'m std::path::Path, + pub line_num: usize, +} + +impl<'m> Default for FileContext<'m> { + fn default() -> Self { + Self { + path: std::path::Path::new("-"), + line_num: 0, + } + } +} + +#[derive(Clone, Debug, serde::Serialize, derive_setters::Setters)] +#[non_exhaustive] +pub struct PathContext<'m> { + pub path: &'m std::path::Path, +} + +impl<'m> Default for PathContext<'m> { fn default() -> Self { Self { path: std::path::Path::new("-"), - byte_offset: 0, - typo: "", - corrections: crate::Status::Invalid, } } } @@ -195,42 +225,7 @@ impl Report for PrintBrief { Message::BinaryFile(msg) => { log::info!("{}", msg); } - Message::FileTypo(msg) => match &msg.corrections { - crate::Status::Valid => {} - crate::Status::Invalid => { - println!( - "{}:{}:{}: {} is disallowed", - msg.path.display(), - msg.line_num, - msg.byte_offset, - msg.typo, - ); - } - crate::Status::Corrections(corrections) => { - println!( - "{}:{}:{}: {} -> {}", - msg.path.display(), - msg.line_num, - msg.byte_offset, - msg.typo, - itertools::join(corrections.iter(), ", ") - ); - } - }, - Message::PathTypo(msg) => match &msg.corrections { - crate::Status::Valid => {} - crate::Status::Invalid => { - println!("{}: {} is disallowed", msg.path.display(), msg.typo,); - } - crate::Status::Corrections(corrections) => { - println!( - "{}: {} -> {}", - msg.path.display(), - msg.typo, - itertools::join(corrections.iter(), ", ") - ); - } - }, + Message::Typo(msg) => print_brief_correction(msg), Message::File(msg) => { println!("{}", msg.path.display()); } @@ -257,25 +252,7 @@ impl Report for PrintLong { Message::BinaryFile(msg) => { log::info!("{}", msg); } - Message::FileTypo(msg) => print_long_correction(msg), - Message::PathTypo(msg) => match &msg.corrections { - crate::Status::Valid => {} - crate::Status::Invalid => { - println!( - "{}: error: `{}` is disallowed", - msg.path.display(), - msg.typo, - ); - } - crate::Status::Corrections(corrections) => { - println!( - "{}: error: `{}` should be {}", - msg.path.display(), - msg.typo, - itertools::join(corrections.iter().map(|c| format!("`{}`", c)), ", ") - ); - } - }, + Message::Typo(msg) => print_long_correction(msg), Message::File(msg) => { println!("{}", msg.path.display()); } @@ -293,45 +270,66 @@ impl Report for PrintLong { } } -fn print_long_correction(msg: &FileTypo) { - let line_num = msg.line_num.to_string(); - let line_indent: String = itertools::repeat_n(" ", line_num.len()).collect(); - - let hl_indent: String = itertools::repeat_n(" ", msg.byte_offset).collect(); - let hl: String = itertools::repeat_n("^", msg.typo.len()).collect(); - - let line = String::from_utf8_lossy(msg.line); - let line = line.replace("\t", " "); +fn print_brief_correction(msg: &Typo) { + match &msg.corrections { + crate::Status::Valid => {} + crate::Status::Invalid => { + println!( + "{}:{}: {} is disallowed", + msg.context, msg.byte_offset, msg.typo, + ); + } + crate::Status::Corrections(corrections) => { + println!( + "{}:{}: {} -> {}", + msg.context, + msg.byte_offset, + msg.typo, + itertools::join(corrections.iter(), ", ") + ); + } + } +} +fn print_long_correction(msg: &Typo) { let stdout = io::stdout(); let mut handle = stdout.lock(); match &msg.corrections { crate::Status::Valid => {} crate::Status::Invalid => { - writeln!(handle, "error: `{}` is disallowed", msg.typo,).unwrap(); + writeln!( + handle, + "{}:{}: {} is disallowed", + msg.context, msg.byte_offset, msg.typo, + ) + .unwrap(); } crate::Status::Corrections(corrections) => { writeln!( handle, "error: `{}` should be {}", msg.typo, - itertools::join(corrections.iter().map(|c| format!("`{}`", c)), ", ") + itertools::join(corrections.iter(), ", ") ) .unwrap(); } } - writeln!( - handle, - " --> {}:{}:{}", - msg.path.display(), - msg.line_num, - msg.byte_offset - ) - .unwrap(); - writeln!(handle, "{} |", line_indent).unwrap(); - writeln!(handle, "{} | {}", msg.line_num, line.trim_end()).unwrap(); - writeln!(handle, "{} | {}{}", line_indent, hl_indent, hl).unwrap(); - writeln!(handle, "{} |", line_indent).unwrap(); + writeln!(handle, " --> {}:{}", msg.context, msg.byte_offset).unwrap(); + + if let Context::File(context) = &msg.context { + let line_num = context.line_num.to_string(); + let line_indent: String = itertools::repeat_n(" ", line_num.len()).collect(); + + let hl_indent: String = itertools::repeat_n(" ", msg.byte_offset).collect(); + let hl: String = itertools::repeat_n("^", msg.typo.len()).collect(); + + let line = String::from_utf8_lossy(msg.buffer.as_ref()); + let line = line.replace("\t", " "); + writeln!(handle, "{} |", line_indent).unwrap(); + writeln!(handle, "{} | {}", line_num, line.trim_end()).unwrap(); + writeln!(handle, "{} | {}{}", line_indent, hl_indent, hl).unwrap(); + writeln!(handle, "{} |", line_indent).unwrap(); + } } #[derive(Copy, Clone, Debug)] diff --git a/src/replace.rs b/src/replace.rs index 0e91c07..22a6376 100644 --- a/src/replace.rs +++ b/src/replace.rs @@ -56,37 +56,41 @@ impl<'r> Replace<'r> { impl<'r> typos::report::Report for Replace<'r> { fn report(&self, msg: typos::report::Message<'_>) -> bool { - match msg { - typos::report::Message::FileTypo(msg) => match msg.corrections { - typos::Status::Corrections(corrections) if corrections.len() == 1 => { - let path = msg.path.to_owned(); - let line_num = msg.line_num; - let correction = - Correction::new(msg.byte_offset, msg.typo, corrections[0].as_ref()); - let mut deferred = self.deferred.lock().unwrap(); - let content = deferred - .content - .entry(path) - .or_insert_with(BTreeMap::new) - .entry(line_num) - .or_insert_with(Vec::new); - content.push(correction); - false - } - _ => self.reporter.report(typos::report::Message::FileTypo(msg)), - }, - typos::report::Message::PathTypo(msg) => match msg.corrections { - typos::Status::Corrections(corrections) if corrections.len() == 1 => { - let path = msg.path.to_owned(); - let correction = - Correction::new(msg.byte_offset, msg.typo, corrections[0].as_ref()); - let mut deferred = self.deferred.lock().unwrap(); - let content = deferred.paths.entry(path).or_insert_with(Vec::new); - content.push(correction); - false - } - _ => self.reporter.report(typos::report::Message::PathTypo(msg)), - }, + let typo = match &msg { + typos::report::Message::Typo(typo) => typo, + _ => return self.reporter.report(msg), + }; + + let corrections = match &typo.corrections { + typos::Status::Corrections(corrections) if corrections.len() == 1 => corrections, + _ => return self.reporter.report(msg), + }; + + match &typo.context { + typos::report::Context::File(file) => { + let path = file.path.to_owned(); + let line_num = file.line_num; + let correction = + Correction::new(typo.byte_offset, typo.typo, corrections[0].as_ref()); + let mut deferred = self.deferred.lock().unwrap(); + let content = deferred + .content + .entry(path) + .or_insert_with(BTreeMap::new) + .entry(line_num) + .or_insert_with(Vec::new); + content.push(correction); + false + } + typos::report::Context::Path(path) => { + let path = path.path.to_owned(); + let correction = + Correction::new(typo.byte_offset, typo.typo, corrections[0].as_ref()); + let mut deferred = self.deferred.lock().unwrap(); + let content = deferred.paths.entry(path).or_insert_with(Vec::new); + content.push(correction); + false + } _ => self.reporter.report(msg), } } @@ -204,10 +208,14 @@ mod test { let primary = typos::report::PrintSilent; let replace = Replace::new(&primary); replace.report( - typos::report::FileTypo::default() - .path(input_file.path()) - .line(b"1 foo 2\n3 4 5") - .line_num(1) + typos::report::Typo::default() + .context( + typos::report::FileContext::default() + .path(input_file.path()) + .line_num(1) + .into(), + ) + .buffer(std::borrow::Cow::Borrowed(b"1 foo 2\n3 4 5")) .byte_offset(2) .typo("foo") .corrections(typos::Status::Corrections(vec![ @@ -229,8 +237,13 @@ mod test { let primary = typos::report::PrintSilent; let replace = Replace::new(&primary); replace.report( - typos::report::PathTypo::default() - .path(input_file.path()) + typos::report::Typo::default() + .context( + typos::report::PathContext::default() + .path(input_file.path()) + .into(), + ) + .buffer(std::borrow::Cow::Borrowed(b"foo.txt")) .byte_offset(0) .typo("foo") .corrections(typos::Status::Corrections(vec![