From 47c756c953b253c28b72c8703fb75605e9e63827 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 16:24:42 +0000 Subject: [PATCH] Fold an inline (?i) culture-invariantly too [patch] CultureInvariant was only set for CaseInsensitive, so a CaseSensitive pattern that asked for its own fold with (?i) still folded by the current culture. Compiled under tr-TR, "(?i)img" stopped matching "IMG_1234.JPG", and the regex cache kept that compile for every later caller. Set CultureInvariant on every compiled filter. Fixes #120 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01P1dENrzXtWG4vP7thSjyof --- TextFilter.Test/TextFilterTests.cs | 29 +++++++++++++++++++++++++++++ TextFilter/TextFilter.cs | 6 ++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/TextFilter.Test/TextFilterTests.cs b/TextFilter.Test/TextFilterTests.cs index 66011f5..e61d8aa 100644 --- a/TextFilter.Test/TextFilterTests.cs +++ b/TextFilter.Test/TextFilterTests.cs @@ -682,6 +682,35 @@ public void ARegexCompiledUnderOneCultureAnswersTheSameUnderAnother() } } + [TestMethod] + public void AnInlineIgnoreCaseFoldsTheSameWhateverCultureCompiledIt() + { + // Under CaseSensitive the pattern can still ask for a fold itself with (?i), and that fold is + // culture-dependent unless CultureInvariant is set. Compiled under tr-TR first, "(?i)img" would + // then miss "IMG_1234.JPG" for every later caller, because the cache keeps the first compile. + CultureInfo original = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = new CultureInfo("tr-TR"); + if (CultureInfo.CurrentCulture.TextInfo.ToUpper("i") == "I") + { + Assert.Inconclusive("This runtime does not apply Turkish case mapping, so the regression cannot be provoked here."); + } + + bool underTurkish = TextFilter.IsMatch("IMG_1234.JPG", "(?i)img", TextFilterType.Regex, TextFilterMatchOptions.ByWholeString); + + CultureInfo.CurrentCulture = new CultureInfo("en-US"); + bool underEnglish = TextFilter.IsMatch("IMG_1234.JPG", "(?i)img", TextFilterType.Regex, TextFilterMatchOptions.ByWholeString); + + Assert.IsTrue(underTurkish, "An inline (?i) should fold i and I whatever locale compiled the pattern."); + Assert.IsTrue(underEnglish, "The first caller's culture should not decide the answer for later callers."); + } + finally + { + CultureInfo.CurrentCulture = original; + } + } + [TestMethod] public void TheTwoSensitivitiesDoNotCollideInTheRegexCache() { diff --git a/TextFilter/TextFilter.cs b/TextFilter/TextFilter.cs index 65c8a36..60af356 100644 --- a/TextFilter/TextFilter.cs +++ b/TextFilter/TextFilter.cs @@ -458,10 +458,12 @@ public static bool DoesMatchRegex(string text, string filter, TextFilterMatchOpt // matches "IMG_1234.JPG" -- and the cache below is keyed by pattern and sensitivity only, // so whichever culture happened to compile the pattern first decides the answer for every // later caller on any thread. A filter pattern is machine text, not prose, and the glob - // path already folds invariantly, so the two paths now agree. + // path already folds invariantly, so the two paths now agree. It applies under CaseSensitive + // too, because the pattern itself can ask for a fold with an inline (?i), and that fold is + // just as culture-dependent and just as cached as the one the option asks for. RegexOptions regexOptions = caseSensitivity is TextFilterCaseSensitivity.CaseInsensitive ? RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant - : RegexOptions.Compiled; + : RegexOptions.Compiled | RegexOptions.CultureInvariant; try {