diff --git a/TextFilter.Test/TextFilterTests.cs b/TextFilter.Test/TextFilterTests.cs index 3250a64..1f27239 100644 --- a/TextFilter.Test/TextFilterTests.cs +++ b/TextFilter.Test/TextFilterTests.cs @@ -1064,6 +1064,40 @@ public void GlobClassRangeSpanningAPathSeparatorMatchesIt(string pattern, string } } + [TestMethod] + [DataRow("[a]]", "[a][]]")] + [DataRow("[]]]", "[]][]]")] + [DataRow("*[[][0-9]]", "*[[][0-9][]]")] + public void ExpandGlobClassesWritesALiteralBracketAfterAClassAsItsOwnClass(string token, string expected) => + Assert.AreEqual(expected, TextFilter.ExpandGlobClasses(token)); + + [TestMethod] + [DataRow("*[[][0-9]]", "items[3]", "items[3")] + [DataRow("[a]]", "a]", "a")] + [DataRow("[ab]]", "b]", "b")] + [DataRow("[!a]]", "b]", "b")] + [DataRow("*[[][0-9a-f]]", "h[c]", "h[c")] + [DataRow("[a]]]", "a]]", "a]")] + [DataRow("*[[]DEBUG]*", "x[DEBUG]y", "xDEBUGy")] + public void GlobClassFollowedByALiteralBracketMatchesTheBracket(string pattern, string matching, string notMatching) + { + // DotNet.Glob read a "]" straight after a class as one more member of that class, so the pattern + // needed one character fewer than it was written with (#172). + foreach (TextFilterCaseSensitivity caseSensitivity in new[] { TextFilterCaseSensitivity.CaseSensitive, TextFilterCaseSensitivity.CaseInsensitive }) + { + Assert.IsTrue(TextFilter.IsMatch(matching, pattern, TextFilterType.Glob, TextFilterMatchOptions.ByWholeString, caseSensitivity), $"{pattern} should match {matching} ({caseSensitivity})"); + Assert.IsFalse(TextFilter.IsMatch(notMatching, pattern, TextFilterType.Glob, TextFilterMatchOptions.ByWholeString, caseSensitivity), $"{pattern} should not match {notMatching} ({caseSensitivity})"); + } + } + + [TestMethod] + public void FilterWithAClassFollowedByALiteralBracketKeepsAndExcludesTheRightItems() + { + List items = ["items[3]", "items[3", "notes.txt"]; + CollectionAssert.AreEqual(new List { "items[3]" }, TextFilter.Filter(items, "*[[][0-9]]").ToList()); + CollectionAssert.AreEqual(new List { "items[3", "notes.txt" }, TextFilter.Filter(items, "-*[[][0-9]]").ToList()); + } + [TestMethod] [DataRow("-readme[a-")] [DataRow("!readme[a-")] diff --git a/TextFilter/TextFilter.cs b/TextFilter/TextFilter.cs index 927c00c..f959040 100644 --- a/TextFilter/TextFilter.cs +++ b/TextFilter/TextFilter.cs @@ -535,6 +535,15 @@ internal static IReadOnlyList ExpandGlobStarSegments(string maskedToken) result.Append(token, index, open - index).Append(rewritten); index = close + 1; + + // DotNet.Glob reads a "]" straight after a class as one more member of it, so "[a]]" would + // match "a" and not "a]". Each such "]" is written as the one-member class "[]]", which it + // reads as the literal the pattern means. + while (index < token.Length && token[index] == ']') + { + result.Append("[]]"); + index++; + } } return result.Append(token, index, token.Length - index).ToString();