From b5792a425dff1fc0c4058f9bf9d7d0979256f381 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 04:27:49 +0000 Subject: [PATCH 1/2] Count only doubled **/ against the zero-segment expansion cap [patch] Since #171 a "**/" joined to a preceding literal is never expanded into a zero-segment form, but it still incremented the expansion counter. Four of them used up the cap, so "a**/b**/c**/d**/**/x.cs" stopped matching "a/b/c/d/x.cs". The counter now advances only when a "**/" actually added the zero-segment alternative. Fixes #185 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW6FzBtSrY5LKUuDYce --- TextFilter.Test/TextFilterTests.cs | 9 +++++++++ TextFilter/TextFilter.cs | 10 +++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/TextFilter.Test/TextFilterTests.cs b/TextFilter.Test/TextFilterTests.cs index 886b9cd..7d900d8 100644 --- a/TextFilter.Test/TextFilterTests.cs +++ b/TextFilter.Test/TextFilterTests.cs @@ -1303,6 +1303,15 @@ public void GlobStarJoinedToLiteralStillMatchesWithSeparator(string text, string Assert.IsTrue(TextFilter.IsMatch(text, filter, TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); } + [TestMethod] + [DataRow("a/b/c/d/x.cs", "a**/b**/c**/d**/**/x.cs")] + [DataRow("a/b/c/d/q/x.cs", "a**/b**/c**/d**/**/x.cs")] + [DataRow("a/b/c/d/e/x.cs", "a**/b**/c**/d**/e**/**/x.cs")] + public void GlobStarJoinedToLiteralDoesNotUseUpTheZeroSegmentCap(string text, string filter) + { + Assert.IsTrue(TextFilter.IsMatch(text, filter, TextFilterType.Glob, TextFilterMatchOptions.ByWholeString)); + } + [TestMethod] public void GlobStarSlashFiltersPathItems() { diff --git a/TextFilter/TextFilter.cs b/TextFilter/TextFilter.cs index 16701d8..bfdf03b 100644 --- a/TextFilter/TextFilter.cs +++ b/TextFilter/TextFilter.cs @@ -471,6 +471,7 @@ internal static IReadOnlyList ExpandGlobStarSegments(string maskedToken) { string literal = maskedToken[start..index]; bool expandBothWays = expanded < MaxExpandedGlobStarSegments; + bool doubled = false; List next = new(expansions.Count * 2); foreach (string prefix in expansions) @@ -483,11 +484,18 @@ internal static IReadOnlyList ExpandGlobStarSegments(string maskedToken) if (expandBothWays && (before.Length == 0 || before[^1] == MaskedPathSeparator)) { next.Add(before); + doubled = true; } } + // Only a "**/" that actually added a zero-segment form counts against the cap, so ones + // joined to a literal don't use it up before a later whole-segment "**/" is reached. expansions = next; - expanded++; + if (doubled) + { + expanded++; + } + start = index + MaskedGlobStarSegment.Length; } From 6f196b4e5f61e7304eb5f7ba34d2594df3a64a33 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 04:25:55 +0000 Subject: [PATCH 2/2] Hold only the visible overloads to the README's documented defaults [patch] The README API test required every overload's same-named parameter to carry the documented default. The binary-compatibility overloads from #166 have no defaults by design, so main has been red since #178. They are marked [EditorBrowsable(Never)] and are not what the README documents, so the default check now skips them. A wrong default on a visible overload still fails. Fixes #183 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW6FzBtSrY5LKUuDYce (cherry picked from commit 9328e00f13b545b66eb8ae37345b4aee5ecafd77) --- TextFilter.Test/ReadmeTests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/TextFilter.Test/ReadmeTests.cs b/TextFilter.Test/ReadmeTests.cs index 7ced909..17fc9c2 100644 --- a/TextFilter.Test/ReadmeTests.cs +++ b/TextFilter.Test/ReadmeTests.cs @@ -4,6 +4,7 @@ namespace TextFilter.Test; using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; @@ -89,7 +90,10 @@ public void EveryMethodInTheReadmeApiReferenceExistsWithItsDocumentedDefaults() foreach (Match row in rows) { string methodName = row.Groups["method"].Value; - MethodInfo[] overloads = [.. methods.Where(m => m.Name == methodName)]; + // The binary-compatibility overloads are hidden from IntelliSense and deliberately carry no + // defaults, since defaults on both sets would make calls ambiguous. The README documents the + // visible overloads, so only those are held to its defaults. + MethodInfo[] overloads = [.. methods.Where(m => m.Name == methodName && !IsHiddenFromCallers(m))]; if (overloads.Length == 0) { problems.Add($"{methodName} does not exist"); @@ -115,6 +119,9 @@ public void EveryMethodInTheReadmeApiReferenceExistsWithItsDocumentedDefaults() Assert.IsEmpty(problems, string.Join("; ", problems)); } + private static bool IsHiddenFromCallers(MethodInfo method) => + method.GetCustomAttribute()?.State == EditorBrowsableState.Never; + [TestMethod] public void ReadmeMatchOptionsSampleGivesTheDocumentedResults() {