From 0ac07540b6e6d2ffdcc401c05f719ff5680d4a47 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 03:26:03 +0000 Subject: [PATCH] Stop regex ByWordAll matching blank text [patch] ExtractTextTokens returns no words for "" or " ", and Enumerable.All over an empty set is true, so the regex ByWordAll path reported blank text as matching every pattern while the glob path said no. The regex path now returns false when the text has no words. Fixes ktsu-dev/TextFilter#107 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QPAgvLThqmty1U7AaebQMQ --- TextFilter.Test/TextFilterTests.cs | 26 ++++++++++++++++++++++++++ TextFilter/TextFilter.cs | 8 ++++++++ 2 files changed, 34 insertions(+) diff --git a/TextFilter.Test/TextFilterTests.cs b/TextFilter.Test/TextFilterTests.cs index 5225dc1..e4a9f4a 100644 --- a/TextFilter.Test/TextFilterTests.cs +++ b/TextFilter.Test/TextFilterTests.cs @@ -771,4 +771,30 @@ public void ACachedPatternStillMatchesAfterTheCacheHasReset() "A pattern evicted by the bound should simply be recompiled, not answer differently."); Assert.IsFalse(TextFilter.IsMatch("hello world", "^goodbye", TextFilterType.Regex, TextFilterMatchOptions.ByWholeString)); } + + [TestMethod] + [DataRow("", TextFilterType.Regex)] + [DataRow(" ", TextFilterType.Regex)] + [DataRow("", TextFilterType.Glob)] + [DataRow(" ", TextFilterType.Glob)] + public void ByWordAllDoesNotMatchTextWithNoWords(string text, TextFilterType filterType) + { + // Blank text splits into no words, and All over nothing is vacuously true, so the regex path + // used to match it against every pattern while the glob path did not. Both now agree. + Assert.IsFalse(TextFilter.IsMatch(text, "zzz", filterType, TextFilterMatchOptions.ByWordAll)); + CollectionAssert.AreEqual( + new List(), + TextFilter.Filter([text], "zzz", filterType, TextFilterMatchOptions.ByWordAll).ToList()); + } + + [TestMethod] + public void RegexByWordAllFilterDropsBlankEntries() + { + CollectionAssert.AreEqual( + new List(), + TextFilter.Filter(["", "hello"], "zzz", TextFilterType.Regex, TextFilterMatchOptions.ByWordAll).ToList()); + CollectionAssert.AreEqual( + new List { "hello" }, + TextFilter.Filter(["", "hello"], "hel", TextFilterType.Regex, TextFilterMatchOptions.ByWordAll).ToList()); + } } diff --git a/TextFilter/TextFilter.cs b/TextFilter/TextFilter.cs index e8d698c..b56026c 100644 --- a/TextFilter/TextFilter.cs +++ b/TextFilter/TextFilter.cs @@ -457,6 +457,14 @@ public static bool DoesMatchRegex(string text, string filter, TextFilterMatchOpt AddBounded(RegexCache, cacheKey, regex); } + // Text with no words -- "" or " " split by word -- has nothing for the pattern to match. + // Without this, Enumerable.All over the empty set is vacuously true, so under ByWordAll blank + // text matched every pattern while the glob path reported no match for the same input. + if (textTokens.Count == 0) + { + return false; + } + Func, Func, bool> matchFunc = textFilterMatchOptions is TextFilterMatchOptions.ByWordAny ? Enumerable.Any : Enumerable.All;