Skip to content

Commit

Permalink
feat: add support for extend-ignore-re config field from typos
Browse files Browse the repository at this point in the history
  • Loading branch information
ronnychevalier committed Jul 30, 2024
1 parent bd785f2 commit 6b593a5
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 38 deletions.
16 changes: 14 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ clap = { version = "4.5.9", features = ["derive"] }
ignore = "0.4.22"
miette = { version = "7.2.0", features = ["fancy"] }
rayon = "1.10.0"
regex = "1.10.5"
serde = { version = "1.0.204", features = ["derive"] }
serde_regex = "1.1.0"
thiserror = "1.0.61"
toml = "0.8.14"
tree-sitter = "0.22.6"
Expand Down
11 changes: 6 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,15 @@ impl Args {
let config = self.to_config()?;
let walker = self.to_walk(&config)?;
let process_entry = |file: DirEntry| {
if let Some(file_type_config) = config.type_.config_from_path(file.path()) {
if !file_type_config.check_file() {
return 0;
}
let config = config.config_from_path(file.path());
if !config.check_file() {
return 0;
}
let Ok(Some(linter)) = Linter::from_path(file.path()) else {

let Ok(Some(mut linter)) = Linter::from_path(file.path()) else {
return 0;
};
linter.extend_ignore_re(&config.extend_ignore_re);

let mut stderr = std::io::stderr().lock();
linter
Expand Down
64 changes: 38 additions & 26 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Config parsers to recognize the config fields of [`typos`](https://crates.io/crates/typos-cli).
// It is based on <https://github.com/crate-ci/typos/blob/master/crates/typos-cli/src/config.rs>
// but it has been modified to remove fields that we do not care about for the moment.
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
Expand All @@ -11,8 +12,6 @@ use ignore::WalkBuilder;

use crate::lang::Language;

const NO_CHECK_TYPES: &[&str] = &["cert", "lock"];

pub const SUPPORTED_FILE_NAMES: &[&str] =
&["typos.toml", "_typos.toml", ".typos.toml", "pyproject.toml"];

Expand All @@ -24,8 +23,6 @@ pub struct Config {
pub default: EngineConfig,
#[serde(rename = "type")]
pub type_: TypeEngineConfig,
#[serde(skip)]
pub overrides: EngineConfig,
}

#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -82,15 +79,13 @@ impl Config {
files: Walk::from_defaults(),
default: EngineConfig::from_defaults(),
type_: TypeEngineConfig::from_defaults(),
overrides: EngineConfig::default(),
}
}

pub fn update(&mut self, source: &Self) {
self.files.update(&source.files);
self.default.update(&source.default);
self.type_.update(&source.type_);
self.overrides.update(&source.overrides);
}

pub fn to_walk_builder(&self, path: &Path) -> WalkBuilder {
Expand All @@ -105,6 +100,23 @@ impl Config {

walk
}

pub fn config_from_path(&self, path: impl AsRef<Path>) -> Cow<'_, EngineConfig> {
let path = path.as_ref();
let Some(extension) = path.extension() else {
return Cow::Borrowed(&self.default);
};
let Some(lang) = Language::from_extension(extension) else {
return Cow::Borrowed(&self.default);
};

let mut config = self.default.clone();
if let Some(type_config) = self.type_.patterns.get(lang.name()) {
config.update(type_config);
}

Cow::Owned(config)
}
}

#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -211,18 +223,7 @@ pub struct TypeEngineConfig {

impl TypeEngineConfig {
pub fn from_defaults() -> Self {
let mut patterns = HashMap::new();

for &no_check_type in NO_CHECK_TYPES {
patterns.insert(
no_check_type.to_owned(),
EngineConfig {
check_file: Some(false),
},
);
}

Self { patterns }
Self::default()
}

pub fn update(&mut self, source: &Self) {
Expand All @@ -233,28 +234,38 @@ impl TypeEngineConfig {
.update(engine);
}
}

pub fn config_from_path(&self, path: impl AsRef<Path>) -> Option<&EngineConfig> {
let path = path.as_ref();
let extension = path.extension()?;
let lang = Language::from_extension(extension)?;
self.patterns.get(lang.name())
}
}

#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(default)]
#[serde(rename_all = "kebab-case")]
pub struct EngineConfig {
/// Verifying spelling in files.
pub check_file: Option<bool>,

#[serde(with = "serde_regex")]
pub extend_ignore_re: Vec<regex::Regex>,
}

impl PartialEq for EngineConfig {
fn eq(&self, other: &Self) -> bool {
self.check_file == other.check_file
&& self
.extend_ignore_re
.iter()
.map(|r| r.as_str())
.eq(other.extend_ignore_re.iter().map(|r| r.as_str()))
}
}

impl Eq for EngineConfig {}

impl EngineConfig {
pub fn from_defaults() -> Self {
let empty = Self::default();
Self {
check_file: Some(empty.check_file()),
..Default::default()
}
}

Expand Down Expand Up @@ -315,6 +326,7 @@ check-file = true
"po".into(),
EngineConfig {
check_file: Some(true),
..Default::default()
},
);
let actual = Config::from_toml(input).unwrap();
Expand Down
43 changes: 38 additions & 5 deletions src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct Linter {
parsed: Box<dyn Parsed>,
source: SharedSource,
rules: Vec<Box<dyn Rule>>,
ignore_re: Vec<regex::Regex>,
}

impl Linter {
Expand Down Expand Up @@ -62,9 +63,14 @@ impl Linter {
parsed,
source,
rules,
ignore_re: Vec::new(),
})
}

pub fn extend_ignore_re(&mut self, ignore_re: &[regex::Regex]) {
self.ignore_re.extend_from_slice(ignore_re);
}

/// Returns an iterator over the typos found in the source
///
/// # Example
Expand Down Expand Up @@ -102,6 +108,7 @@ pub struct Iter<'t> {
source: SharedSource,
typos: Vec<Box<dyn Typo>>,
rules: &'t [Box<dyn Rule>],
ignore_re: &'t [regex::Regex],
}

impl<'t> Iter<'t> {
Expand All @@ -111,6 +118,7 @@ impl<'t> Iter<'t> {
source: linter.source.clone(),
typos: vec![],
rules: &linter.rules,
ignore_re: &linter.ignore_re,
}
}
}
Expand Down Expand Up @@ -138,18 +146,26 @@ impl Iterator for Iter<'_> {
}

let offset = node.start_byte();
let typos =
node.lintable_bytes(self.source.inner()).flat_map(|string| {
let typos = node
.lintable_bytes(self.source.inner())
.filter_map(|bytes| {
let string = String::from_utf8_lossy(bytes);
let ignored = self.ignore_re.iter().any(|re| re.is_match(&string));
if ignored {
return None;
}

let source = self.source.clone();
let typos = self.rules.iter().flat_map(|rule| rule.check(string)).map(
let typos = self.rules.iter().flat_map(|rule| rule.check(bytes)).map(
move |mut typo| {
typo.with_source(source.clone(), offset);
typo
},
);

Box::new(typos)
});
Some(Box::new(typos))
})
.flatten();
self.typos.extend(typos);
}
}
Expand Down Expand Up @@ -256,6 +272,23 @@ mod tests {
assert_eq!(typo.span(), (141, 2).into());
}

#[cfg(feature = "lang-rust")]
#[test]
fn typo_rust_string_ignored() {
let rust = r#"
/// Doc comment
fn func() -> anyhow::Result<()> {
anyhow::bail!("failed to do something for the following reason : foobar foo");
}
"#;
let mut linter =
Linter::new(Language::rust().into(), rust.as_bytes().to_vec(), "file.rs").unwrap();
linter.extend_ignore_re(&[regex::Regex::new(r"foobar foo").unwrap()]);

let typos = linter.iter().count();
assert_eq!(typos, 0);
}

#[cfg(feature = "lang-rust")]
#[test]
fn typo_rust_rawstring() {
Expand Down

0 comments on commit 6b593a5

Please sign in to comment.