Repository: crate-ci/typos (Rust)

Issue:
Configuring anything about a type looses the type-specific dictionary
typos comes with some language-specific dictionary entries, e.g. for Rust `flate2` and `ser` are not reported as typos.
Unfortunately configuring anything about a type looses these language-specific dictionary entries, e.g. if you configure:

```toml
[type.rust]
check-filename = false
```

then suddenly `flate2` and `ser` in `.rs` files become typos ... which is quite obviously not what you'd expect (or are likely to want).


Discussion:
The problem here is the way we do defaulting.

Currently, we don't ever merge in the defaults when processing but instead fallback to them when doing lookups.  For most types, this just works.  However, we deal with the types lookup at a higher level which causes it to not quite work.



Patch:
diff --git a/crates/typos-cli/src/config.rs b/crates/typos-cli/src/config.rs
index a2a7d1628..8a753b236 100644
--- a/crates/typos-cli/src/config.rs
+++ b/crates/typos-cli/src/config.rs
@@ -1,5 +1,7 @@
 use std::collections::HashMap;
 
+use kstring::KString;
+
 #[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
 #[serde(deny_unknown_fields)]
 #[serde(default)]
@@ -148,15 +150,99 @@ impl Walk {
 #[serde(default)]
 #[serde(transparent)]
 pub struct TypeEngineConfig {
-    pub patterns: std::collections::HashMap<kstring::KString, GlobEngineConfig>,
+    pub patterns: std::collections::HashMap<KString, GlobEngineConfig>,
 }
 
 impl TypeEngineConfig {
     pub fn from_defaults() -> Self {
-        let empty = Self::default();
-        Self {
-            patterns: empty.patterns().collect(),
-        }
+        let patterns = [
+            (
+                KString::from("lock"),
+                GlobEngineConfig {
+                    extend_glob: Vec::new(),
+                    engine: EngineConfig {
+                        check_file: Some(false),
+                        ..Default::default()
+                    },
+                },
+            ),
+            (
+                KString::from("vim"),
+                GlobEngineConfig {
+                    extend_glob: Vec::new(),
+                    engine: EngineConfig {
+                        dict: Some(DictConfig {
+                            extend_identifiers: maplit::hashmap! {
+                                "windo".into() => "windo".into(),
+                            },
+                            ..Default::default()
+                        }),
+                        ..Default::default()
+                    },
+                },
+            ),
+            (
+                KString::from("vimscript"),
+                GlobEngineConfig {
+                    extend_glob: Vec::new(),
+                    engine: EngineConfig {
+                        dict: Some(DictConfig {
+                            extend_identifiers: maplit::hashmap! {
+                                "windo".into() => "windo".into(),
+                            },
+                            ..Default::default()
+                        }),
+                        ..Default::default()
+                    },
+                },
+            ),
+            (
+                KString::from("rust"),
+                GlobEngineConfig {
+                    extend_glob: Vec::new(),
+                    engine: EngineConfig {
+                        dict: Some(DictConfig {
+                            extend_identifiers: maplit::hashmap! {
+                                "flate2".into() => "flate2".into(),
+                            },
+                            extend_words: maplit::hashmap! {
+                                "ser".into() => "ser".into(),
+                            },
+                            ..Default::default()
+                        }),
+                        ..Default::default()
+                    },
+                },
+            ),
+            (
+                KString::from("py"),
+                GlobEngineConfig {
+                    extend_glob: Vec::new(),
+                    engine: EngineConfig {
+                        dict: Some(DictConfig {
+                            extend_identifiers: maplit::hashmap! {
+                                "NDArray".into() => "NDArray".into(),
+                            },
+                            ..Default::default()
+                        }),
+                        ..Default::default()
+                    },
+                },
+            ),
+            (
+                KString::from("cert"),
+                GlobEngineConfig {
+                    extend_glob: Vec::new(),
+                    engine: EngineConfig {
+                        check_file: Some(false),
+                        ..Default::default()
+                    },
+                },
+            ),
+        ]
+        .into_iter()
+        .collect();
+        Self { patterns }
     }
 
     pub fn update(&mut self, source: &Self) {
@@ -169,85 +255,9 @@ impl TypeEngineConfig {
     }
 
     pub fn patterns(&self) -> impl Iterator<Item = (kstring::KString, GlobEngineConfig)> {
-        let mut patterns = self.patterns.clone();
-        patterns
-            .entry("lock".into())
-            .or_insert_with(|| GlobEngineConfig {
-                extend_glob: Vec::new(),
-                engine: EngineConfig {
-                    check_file: Some(false),
-                    ..Default::default()
-                },
-            });
-        patterns
-            .entry("vim".into())
-            .or_insert_with(|| GlobEngineConfig {
-                extend_glob: Vec::new(),
-                engine: EngineConfig {
-                    dict: Some(DictConfig {
-                        extend_identifiers: maplit::hashmap! {
-                            "windo".into() => "windo".into(),
-                        },
-                        ..Default::default()
-                    }),
-                    ..Default::default()
-                },
-            });
-        patterns
-            .entry("vimscript".into())
-            .or_insert_with(|| GlobEngineConfig {
-                extend_glob: Vec::new(),
-                engine: EngineConfig {
-                    dict: Some(DictConfig {
-                        extend_identifiers: maplit::hashmap! {
-                            "windo".into() => "windo".into(),
-                        },
-                        ..Default::default()
-                    }),
-                    ..Default::default()
-                },
-            });
-        patterns
-            .entry("rust".into())
-            .or_insert_with(|| GlobEngineConfig {
-                extend_glob: Vec::new(),
-                engine: EngineConfig {
-                    dict: Some(DictConfig {
-                        extend_identifiers: maplit::hashmap! {
-                            "flate2".into() => "flate2".into(),
-                        },
-                        extend_words: maplit::hashmap! {
-                            "ser".into() => "ser".into(),
-                        },
-                        ..Default::default()
-                    }),
-                    ..Default::default()
-                },
-            });
-        patterns
-            .entry("py".into())
-            .or_insert_with(|| GlobEngineConfig {
-                extend_glob: Vec::new(),
-                engine: EngineConfig {
-                    dict: Some(DictConfig {
-                        extend_identifiers: maplit::hashmap! {
-                            "NDArray".into() => "NDArray".into(),
-                        },
-                        ..Default::default()
-                    }),
-                    ..Default::default()
-                },
-            });
-        patterns
-            .entry("cert".into())
-            .or_insert_with(|| GlobEngineConfig {
-                extend_glob: Vec::new(),
-                engine: EngineConfig {
-                    check_file: Some(false),
-                    ..Default::default()
-                },
-            });
-        patterns.into_iter()
+        let mut engine = Self::from_defaults();
+        engine.update(self);
+        engine.patterns.into_iter()
     }
 }
 


Test patch:
diff --git a/crates/typos-cli/tests/cmd/extend-builtin-dict.in/_typos.toml b/crates/typos-cli/tests/cmd/extend-builtin-dict.in/_typos.toml
new file mode 100644
index 000000000..c82050db0
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/extend-builtin-dict.in/_typos.toml
@@ -0,0 +1,10 @@
+[type.rust]
+check-filename = false
+
+[default.extend-words]
+foo = "bar"
+
+[files]
+extend-exclude = [
+  "_typos.toml"
+]
diff --git a/crates/typos-cli/tests/cmd/extend-builtin-dict.in/foo.rs b/crates/typos-cli/tests/cmd/extend-builtin-dict.in/foo.rs
new file mode 100644
index 000000000..bcfd16d25
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/extend-builtin-dict.in/foo.rs
@@ -0,0 +1,1 @@
+flate2
diff --git a/crates/typos-cli/tests/cmd/extend-builtin-dict.toml b/crates/typos-cli/tests/cmd/extend-builtin-dict.toml
new file mode 100644
index 000000000..b2c2f341e
--- /dev/null
+++ b/crates/typos-cli/tests/cmd/extend-builtin-dict.toml
@@ -0,0 +1,8 @@
+bin.name = "typos"
+args = ""
+stdin = '''
+\n\n
+Destory
+'''
+stdout = ""
+stderr = ""