diff --git a/Cargo.toml b/Cargo.toml index 42affeb..4a5cf0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ rayon = "1.10.0" regex = "1.10.5" serde = { version = "1.0.204", features = ["derive"] } serde_regex = "1.1.0" +tempfile = "3.10.1" thiserror = "1.0.61" toml = "0.8.14" tree-sitter = "0.22.6" diff --git a/src/cli.rs b/src/cli.rs index 07c83ec..3daa8c3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -41,6 +41,10 @@ pub(crate) struct Args { #[arg(long, group = "mode", help_heading = "Mode")] files: bool, + /// Write fixes out + #[arg(long, short, group = "mode", help_heading = "Mode")] + write_changes: bool, + /// Write the current configuration to file with `-` for stdout #[arg(long, value_name = "OUTPUT", group = "mode", help_heading = "Mode")] dump_config: Option, @@ -104,6 +108,10 @@ impl Args { linter .iter() .map(|typo| { + if self.write_changes { + let _ = typo.fix().apply(file.path()); + } + let typo: miette::Report = typo.into(); let _ = writeln!(stderr, "{typo:?}"); }) diff --git a/src/lang.rs b/src/lang.rs index 212c50d..5548e1c 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -234,8 +234,8 @@ impl Parsed for ParsedQuery { /// A string that can be checked with its offset within its source #[derive(PartialEq, Eq, Debug)] pub struct LintableString { - pub(crate) offset: usize, - pub(crate) value: String, + offset: usize, + value: String, } impl LintableString { diff --git a/src/lint.rs b/src/lint.rs index bd03afc..cbb4312 100644 --- a/src/lint.rs +++ b/src/lint.rs @@ -1,3 +1,4 @@ +use std::io::Write; use std::path::Path; use miette::{SourceCode, SourceSpan}; @@ -15,6 +16,39 @@ pub trait Rule { fn check(&self, bytes: &[u8]) -> Vec>; } +/// The kind of action to perform to fix the lint suggestion +pub enum Fix { + /// Unclear how to fix the typo, nothing is done + Unknown, + + /// Removes some characters + Remove { span: SourceSpan }, +} + +impl Fix { + /// Applies the action on the given file and location + pub fn apply(&self, path: impl AsRef) -> anyhow::Result<()> { + match self { + Self::Unknown => Ok(()), + Self::Remove { span } => { + let path = path.as_ref(); + let mut content = std::fs::read(path)?; + content.drain(span.offset()..(span.offset() + span.len())); + + let mut file = if let Some(parent) = path.parent() { + tempfile::NamedTempFile::new_in(parent)? + } else { + tempfile::NamedTempFile::new()? + }; + file.write_all(&content)?; + file.persist(path)?; + + Ok(()) + } + } + } +} + /// Type that represents a typo found pub trait Typo: miette::Diagnostic + std::error::Error + Sync + Send { /// Span that identify where the typo is located @@ -22,6 +56,11 @@ pub trait Typo: miette::Diagnostic + std::error::Error + Sync + Send { /// Specify within which source the typo has been found fn with_source(&mut self, src: SharedSource, offset: usize); + + /// Returns the action to perform to fix the typo + fn fix(&self) -> Fix { + Fix::Unknown + } } /// Detects typos in a file @@ -192,9 +231,11 @@ impl miette::Diagnostic for Box { #[cfg(test)] mod tests { + use std::{fs::File, io::Write}; + use crate::lint::Language; - use super::Linter; + use super::{Fix, Linter}; #[test] fn from_path_unknown_extension() { @@ -354,4 +395,18 @@ Hello mate `this should not trigger the rule : foobar` abc let typos = linter.iter().count(); assert_eq!(typos, 0); } + + #[test] + fn write_changes() { + let fix = Fix::Remove { + span: (1, 2).into(), + }; + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("file.txt"); + let mut file = File::create(&file_path).unwrap(); + file.write_all(b"123456").unwrap(); + drop(file); + fix.apply(&file_path).unwrap(); + assert_eq!(b"1456", std::fs::read(file_path).unwrap().as_slice()); + } } diff --git a/src/lint/punctuation.rs b/src/lint/punctuation.rs index 1f07a27..1249307 100644 --- a/src/lint/punctuation.rs +++ b/src/lint/punctuation.rs @@ -14,7 +14,7 @@ use winnow::error::InputError; use winnow::token::{none_of, one_of, take}; use winnow::{Located, PResult, Parser}; -use super::SharedSource; +use super::{Fix, SharedSource}; use super::{Rule, Typo}; /// A space *before* a punctuation mark has been detected. @@ -61,6 +61,10 @@ impl Typo for TypoSpaceBeforePunctuationMarks { self.src = Some(src); self.span = (self.span.offset() + offset, self.span.len()).into(); } + + fn fix(&self) -> Fix { + Fix::Remove { span: self.span } + } } /// A rule that detects typographical mistakes related to punctuation.