Skip to content

Commit f6cd654

Browse files
fix(cli): Don't recognize known file extensions as typos
Fixes #1253.
1 parent 022bdbe commit f6cd654

File tree

6 files changed

+69
-0
lines changed

6 files changed

+69
-0
lines changed

crates/typos-cli/src/default_types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ pub(crate) const DEFAULT_TYPES: &[(&str, &[&str])] = &[
315315
("ts", &["*.ts", "*.tsx", "*.cts", "*.mts"]),
316316
("twig", &["*.twig"]),
317317
("txt", &["*.txt"]),
318+
("typ", &["*.typ"]),
318319
("typoscript", &["*.typoscript"]),
319320
("usd", &["*.usd", "*.usda", "*.usdc"]),
320321
("v", &["*.v", "*.vsh"]),

crates/typos-cli/src/file.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ impl FileChecker for Typos {
2727
) -> Result<(), std::io::Error> {
2828
if policy.check_filenames {
2929
if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
30+
let file_name = policy.strip_file_extension(file_name);
3031
for typo in check_str(file_name, policy) {
3132
let msg = report::Typo {
3233
context: Some(report::PathContext { path }.into()),
@@ -111,6 +112,7 @@ impl FileChecker for FixTypos {
111112
// Ensure the above write can happen before renaming the file.
112113
if policy.check_filenames {
113114
if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
115+
let file_name = policy.strip_file_extension(file_name);
114116
let mut fixes = Vec::new();
115117
for typo in check_str(file_name, policy) {
116118
if is_fixable(&typo) {
@@ -264,6 +266,7 @@ impl FileChecker for HighlightIdentifiers {
264266
let mut ignores: Option<Ignores> = None;
265267
if policy.check_filenames {
266268
if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
269+
let file_name = policy.strip_file_extension(file_name);
267270
let mut styled = String::new();
268271
let mut prev_end = 0;
269272
for (word, highlight) in policy
@@ -366,6 +369,7 @@ impl FileChecker for Identifiers {
366369
let mut ignores: Option<Ignores> = None;
367370
if policy.check_filenames {
368371
if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
372+
let file_name = policy.strip_file_extension(file_name);
369373
for word in policy.tokenizer.parse_str(file_name) {
370374
if ignores
371375
.get_or_insert_with(|| Ignores::new(file_name.as_bytes(), policy.ignore))
@@ -546,6 +550,7 @@ impl FileChecker for Words {
546550
let mut ignores: Option<Ignores> = None;
547551
if policy.check_filenames {
548552
if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
553+
let file_name = policy.strip_file_extension(file_name);
549554
for word in policy
550555
.tokenizer
551556
.parse_str(file_name)

crates/typos-cli/src/policy.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,36 @@ impl<'s> ConfigEngine<'s> {
9494
debug_assert!(path.is_absolute(), "{} is not absolute", path.display());
9595
let dir = self.get_dir(path).expect("`walk()` should be called first");
9696
let (file_type, file_config) = dir.get_file_config(path);
97+
98+
let mut extension_regex = None;
99+
100+
if file_config.check_filenames {
101+
if let Some(file_type) = file_type {
102+
// File extensions can be in our typo dictionary.
103+
// E.g. .typ is the file extension for Typst but we correct typ to (typo, type).
104+
// Here we build a regex later used to ensure that we don't recognize file extensions
105+
// we know (via the glob list of type definitions) in filenames as typos.
106+
let mut extension_regex_str = String::new();
107+
for pattern in &dir.type_matcher.definitions()[file_type] {
108+
if let Some(pattern) = pattern.strip_prefix("*.") {
109+
if !extension_regex_str.is_empty() {
110+
extension_regex_str.push('|');
111+
}
112+
let glob = globset::Glob::new(pattern).unwrap();
113+
if glob.regex().ends_with('$') {
114+
// Policy::strip_file_extension assumes the regex is $-anchored to avoid allocations.
115+
extension_regex_str
116+
.push_str(glob.regex().strip_prefix("(?-u)^").unwrap());
117+
}
118+
}
119+
}
120+
extension_regex = Some(regex::Regex::new(&extension_regex_str).unwrap());
121+
}
122+
}
123+
97124
Policy {
98125
check_filenames: file_config.check_filenames,
126+
extension_regex,
99127
check_files: file_config.check_files,
100128
file_type,
101129
binary: file_config.binary,
@@ -351,6 +379,7 @@ struct FileConfig {
351379
#[derive(derive_setters::Setters)]
352380
pub struct Policy<'t, 'd, 'i> {
353381
pub check_filenames: bool,
382+
extension_regex: Option<regex::Regex>,
354383
pub check_files: bool,
355384
pub file_type: Option<&'d str>,
356385
pub binary: bool,
@@ -363,6 +392,15 @@ impl Policy<'_, '_, '_> {
363392
pub fn new() -> Self {
364393
Default::default()
365394
}
395+
396+
pub fn strip_file_extension<'a>(&self, filename: &'a str) -> &'a str {
397+
if let Some(extension_regex) = &self.extension_regex {
398+
if let Some(m) = extension_regex.find(filename) {
399+
return &filename[..filename.len() - m.len()];
400+
}
401+
}
402+
filename
403+
}
366404
}
367405

368406
static DEFAULT_TOKENIZER: typos::tokens::Tokenizer = typos::tokens::Tokenizer::new();
@@ -373,6 +411,7 @@ impl Default for Policy<'_, '_, '_> {
373411
fn default() -> Self {
374412
Self {
375413
check_filenames: true,
414+
extension_regex: None,
376415
check_files: true,
377416
file_type: None,
378417
binary: false,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[default.extend-words]
2+
boom = "booom"
3+
foo = "fooo"
4+
5+
[type.some-type]
6+
extend-glob = ["*.boom"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bada bing bada boom
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
bin.name = "typos"
2+
args = ""
3+
stdin = ""
4+
# The file extension equals a known typo but it's not a typo because it matches a type glob pattern.
5+
# (The known file extension is stripped before checking the filename for typos.)
6+
stdout = """
7+
error: `foo` should be `fooo`
8+
--> ./foo.boom:1
9+
error: `boom` should be `booom`
10+
--> ./foo.boom:1:16
11+
|
12+
1 | bada bing bada boom
13+
| ^^^^
14+
|
15+
"""
16+
stderr = ""
17+
status.code = 2

0 commit comments

Comments
 (0)