Refactor: string-or-struct matcher syntax, Case enum, remove case_sensitive_mappings table

Co-authored-by: keith-hall <11882719+keith-hall@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]andkeith-hall authored and Keith Hall committed 2026-03-18 22:16:50 +02:00
1 parent 22c38b6fcb
commit 2a29802dd5
4 files changed
+76 -36

No files matched your search

+58 -25
View File
@@ -47,16 +47,34 @@ impl ToTokens for MappingTarget {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, DeserializeFromStr)] /// Whether a glob pattern should be matched case-sensitively or case-insensitively.
///
/// Mirrors the runtime `Case` type in `src/syntax_mapping.rs`.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum Case {
Sensitive,
Insensitive,
}
impl ToTokens for Case {
fn to_tokens(&self, tokens: &mut TokenStream) {
let t = match self {
Self::Sensitive => quote! { Case::Sensitive },
Self::Insensitive => quote! { Case::Insensitive },
};
tokens.append_all(t);
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
/// A single matcher. /// A single matcher.
/// ///
/// Codegen converts this into a `Lazy<Option<GlobMatcher>>`. /// Codegen converts this into a `Lazy<Option<GlobMatcher>>`.
struct Matcher { struct Matcher {
segments: Vec<MatcherSegment>, segments: Vec<MatcherSegment>,
/// Whether the glob pattern should be matched case-insensitively. /// Whether the glob pattern should be matched case-sensitively.
/// ///
/// Defaults to `true` (case-insensitive) for backwards compatibility. /// Defaults to `Case::Insensitive` for backwards compatibility.
case_insensitive: bool, case: Case,
} }
/// Parse a matcher. /// Parse a matcher.
/// ///
@@ -124,21 +142,53 @@ impl FromStr for Matcher {
Ok(Self { Ok(Self {
segments: non_empty_segments, segments: non_empty_segments,
case_insensitive: true, case: Case::Insensitive,
}) })
} }
} }
/// Helper type for deserializing a `Matcher` from either a plain string or a
/// `{ glob = "...", case_sensitive = true }` struct.
#[derive(Deserialize)]
#[serde(untagged)]
enum RawMatcher {
Simple(String),
Full {
glob: String,
#[serde(default)]
case_sensitive: bool,
},
}
impl<'de> serde::Deserialize<'de> for Matcher {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let raw = RawMatcher::deserialize(deserializer)?;
match raw {
RawMatcher::Simple(s) => Matcher::from_str(&s).map_err(serde::de::Error::custom),
RawMatcher::Full { glob, case_sensitive } => {
let mut matcher =
Matcher::from_str(&glob).map_err(serde::de::Error::custom)?;
matcher.case = if case_sensitive {
Case::Sensitive
} else {
Case::Insensitive
};
Ok(matcher)
}
}
}
}
impl ToTokens for Matcher { impl ToTokens for Matcher {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
let case_insensitive = self.case_insensitive; let case = &self.case;
let t = match self.segments.as_slice() { let t = match self.segments.as_slice() {
[] => unreachable!("0-length matcher should never be created"), [] => unreachable!("0-length matcher should never be created"),
[MatcherSegment::Text(text)] => { [MatcherSegment::Text(text)] => {
quote! { Lazy::new(|| Some(build_matcher_fixed(#text, #case_insensitive))) } quote! { Lazy::new(|| Some(build_matcher_fixed(#text, #case))) }
} }
// parser logic ensures that this case can only happen when there are dynamic segments // parser logic ensures that this case can only happen when there are dynamic segments
segs @ [_, ..] => { segs @ [_, ..] => {
quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ], #case_insensitive)) } quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ], #case)) }
} }
}; };
tokens.append_all(t); tokens.append_all(t);
@@ -189,10 +239,6 @@ impl MatcherSegment {
struct MappingDefModel { struct MappingDefModel {
#[serde(default)] #[serde(default)]
mappings: IndexMap<MappingTarget, Vec<Matcher>>, mappings: IndexMap<MappingTarget, Vec<Matcher>>,
/// Case-sensitive mappings. Unlike `mappings`, these glob patterns are
/// matched case-sensitively.
#[serde(default)]
case_sensitive_mappings: IndexMap<MappingTarget, Vec<Matcher>>,
} }
impl MappingDefModel { impl MappingDefModel {
fn into_mapping_list(self) -> MappingList { fn into_mapping_list(self) -> MappingList {
@@ -205,19 +251,6 @@ impl MappingDefModel {
.map(|matcher| (matcher, target.clone())) .map(|matcher| (matcher, target.clone()))
.collect::<Vec<_>>() .collect::<Vec<_>>()
}) })
.chain(
self.case_sensitive_mappings
.into_iter()
.flat_map(|(target, matchers)| {
matchers
.into_iter()
.map(|mut matcher| {
matcher.case_insensitive = false;
(matcher, target.clone())
})
.collect::<Vec<_>>()
}),
)
.collect(); .collect();
MappingList(list) MappingList(list)
} }
+11 -4
View File
@@ -17,9 +17,16 @@ use ignored_suffixes::IgnoredSuffixes;
mod builtin; mod builtin;
pub mod ignored_suffixes; pub mod ignored_suffixes;
fn make_glob_matcher(from: &str, case_insensitive: bool) -> Result<GlobMatcher> { /// Whether a glob pattern should be matched case-sensitively or case-insensitively.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Case {
Sensitive,
Insensitive,
}
fn make_glob_matcher(from: &str, case: Case) -> Result<GlobMatcher> {
let matcher = GlobBuilder::new(from) let matcher = GlobBuilder::new(from)
.case_insensitive(case_insensitive) .case_insensitive(matches!(case, Case::Insensitive))
.literal_separator(true) .literal_separator(true)
.build()? .build()?
.compile_matcher(); .compile_matcher();
@@ -97,14 +104,14 @@ impl<'a> SyntaxMapping<'a> {
} }
pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> { pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
let matcher = make_glob_matcher(from, true)?; let matcher = make_glob_matcher(from, Case::Insensitive)?;
self.custom_mappings.push((matcher, to)); self.custom_mappings.push((matcher, to));
Ok(()) Ok(())
} }
/// Like [`Self::insert`], but the glob pattern is matched case-sensitively. /// Like [`Self::insert`], but the glob pattern is matched case-sensitively.
pub fn insert_case_sensitive(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> { pub fn insert_case_sensitive(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
let matcher = make_glob_matcher(from, false)?; let matcher = make_glob_matcher(from, Case::Sensitive)?;
self.custom_mappings.push((matcher, to)); self.custom_mappings.push((matcher, to));
Ok(()) Ok(())
} }
+5 -5
View File
@@ -3,7 +3,7 @@ use std::env;
use globset::GlobMatcher; use globset::GlobMatcher;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::syntax_mapping::{make_glob_matcher, MappingTarget}; use crate::syntax_mapping::{make_glob_matcher, Case, MappingTarget};
// Static syntax mappings generated from /src/syntax_mapping/builtins/ by the // Static syntax mappings generated from /src/syntax_mapping/builtins/ by the
// build script (/build/syntax_mapping.rs). // build script (/build/syntax_mapping.rs).
@@ -53,8 +53,8 @@ include!(concat!(
/// A failure to compile is a fatal error. /// A failure to compile is a fatal error.
/// ///
/// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure. /// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure.
fn build_matcher_fixed(from: &str, case_insensitive: bool) -> GlobMatcher { fn build_matcher_fixed(from: &str, case: Case) -> GlobMatcher {
make_glob_matcher(from, case_insensitive) make_glob_matcher(from, case)
.expect("A builtin fixed glob matcher failed to compile") .expect("A builtin fixed glob matcher failed to compile")
} }
@@ -65,7 +65,7 @@ fn build_matcher_fixed(from: &str, case_insensitive: bool) -> GlobMatcher {
/// to compile. /// to compile.
/// ///
/// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure. /// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure.
fn build_matcher_dynamic(segs: &[MatcherSegment], case_insensitive: bool) -> Option<GlobMatcher> { fn build_matcher_dynamic(segs: &[MatcherSegment], case: Case) -> Option<GlobMatcher> {
// join segments // join segments
let mut buf = String::new(); let mut buf = String::new();
for seg in segs { for seg in segs {
@@ -78,7 +78,7 @@ fn build_matcher_dynamic(segs: &[MatcherSegment], case_insensitive: bool) -> Opt
} }
} }
// compile glob matcher // compile glob matcher
let matcher = make_glob_matcher(&buf, case_insensitive).ok()?; let matcher = make_glob_matcher(&buf, case).ok()?;
Some(matcher) Some(matcher)
} }
@@ -1,2 +1,2 @@
[case_sensitive_mappings] [mappings]
"Python" = ["BUILD"] "Python" = [{ glob = "BUILD", case_sensitive = true }]