镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions TextFilter.Test/TextFilterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1317,4 +1317,57 @@
CollectionAssert.AreEqual(scores.OrderByDescending(s => s).ToList(), scores);
Assert.IsNotEmpty(result);
}

// Assemblies compiled against 1.6 or earlier bind to these exact signatures, so each must still
// exist in the binary even though source callers now resolve to the case-sensitivity overloads.
[TestMethod]
[DataRow(nameof(TextFilter.Filter), new[] { typeof(IEnumerable<string>), typeof(string), typeof(TextFilterType), typeof(TextFilterMatchOptions) })]
[DataRow(nameof(TextFilter.IsMatch), new[] { typeof(string), typeof(string), typeof(TextFilterType), typeof(TextFilterMatchOptions) })]
[DataRow(nameof(TextFilter.DoesMatchGlob), new[] { typeof(string), typeof(string), typeof(TextFilterMatchOptions) })]
[DataRow(nameof(TextFilter.DoesMatchRegex), new[] { typeof(string), typeof(string), typeof(TextFilterMatchOptions) })]
[DataRow(nameof(TextFilter.AnyTokenMatchesGlobFilter), new[] { typeof(string), typeof(HashSet<string>) })]
[DataRow(nameof(TextFilter.AllTokensMatchGlobFilter), new[] { typeof(string), typeof(HashSet<string>) })]
public void Pre17SignatureStillExists(string methodName, Type[] parameterTypes)
{
Assert.IsNotNull(typeof(TextFilter).GetMethod(methodName, parameterTypes), $"{methodName}({string.Join(", ", parameterTypes.Select(t => t.Name))}) is missing");
}

[TestMethod]
public void Pre17IsMatchWithScoreSignatureStillExists()
{
Type[] parameterTypes = [typeof(string), typeof(string), typeof(int).MakeByRefType(), typeof(TextFilterType), typeof(TextFilterMatchOptions)];
Assert.IsNotNull(typeof(TextFilter).GetMethod(nameof(TextFilter.IsMatch), parameterTypes));
}

[TestMethod]
public void Pre17GenericFilterSignatureStillExists()
{
Assert.IsTrue(typeof(TextFilter).GetMethods().Any(m =>
m.Name == nameof(TextFilter.Filter)
&& m.IsGenericMethodDefinition
&& m.GetParameters().Length == 5
&& m.GetParameters()[4].ParameterType == typeof(TextFilterMatchOptions)));

Check warning on line 1349 in TextFilter.Test/TextFilterTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'Assert.IsTrue'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_TextFilter&issues=AaGO83xuQrt3N4dL2WIR&open=AaGO83xuQrt3N4dL2WIR&pullRequest=166
}

[TestMethod]
public void Pre17SignaturesMatchCaseSensitively()
{
object?[] args = ["Hello World", "hello", TextFilterType.Glob, TextFilterMatchOptions.ByWordAny];
Type[] parameterTypes = [typeof(string), typeof(string), typeof(TextFilterType), typeof(TextFilterMatchOptions)];
System.Reflection.MethodInfo? isMatch = typeof(TextFilter).GetMethod(nameof(TextFilter.IsMatch), parameterTypes);
Assert.IsNotNull(isMatch);
Assert.IsFalse((bool)isMatch.Invoke(null, args)!);
Assert.IsTrue((bool)isMatch.Invoke(null, ["Hello World", "Hello", TextFilterType.Glob, TextFilterMatchOptions.ByWordAny])!);
}

[TestMethod]
public void SourceCallsWithAllPre17ArgumentsStillCompileAndMatchCaseSensitively()
{
Assert.IsFalse(TextFilter.IsMatch("Hello World", "hello", TextFilterType.Glob, TextFilterMatchOptions.ByWordAny));
Assert.IsTrue(TextFilter.IsMatch("Hello World", "hello", TextFilterType.Glob, TextFilterMatchOptions.ByWordAny, TextFilterCaseSensitivity.CaseInsensitive));
Assert.IsTrue(TextFilter.AnyTokenMatchesGlobFilter("Hel*", ["Hello"]));
Assert.IsTrue(TextFilter.AllTokensMatchGlobFilter("hel*", ["Hello"], TextFilterCaseSensitivity.CaseInsensitive));
Assert.HasCount(1, TextFilter.Filter(["Hello", "hello"], "Hello", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString).ToList());
Assert.HasCount(1, TextFilter.Filter([(1, "Hello"), (2, "hello")], t => t.Item2, "Hello", TextFilterType.Glob, TextFilterMatchOptions.ByWholeString).ToList());
}
}
53 changes: 53 additions & 0 deletions TextFilter/TextFilter.BinaryCompatibility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) 2023-2026 ktsu-dev contributors

namespace ktsu.TextFilter;

using System.ComponentModel;

// 1.7 added a trailing optional caseSensitivity parameter to these methods. Optional parameters are
// filled in at the call site, so that changed their binary signatures, and an assembly compiled
// against 1.6 or earlier failed with MissingMethodException the first time it filtered anything.
// These overloads restore the old signatures and forward with the old case-sensitive behaviour.
// Their parameters are not optional, so new code still binds to the case-sensitivity overloads.
public static partial class TextFilter
{
/// <inheritdoc cref="Filter(IEnumerable{string}, string, TextFilterType, TextFilterMatchOptions, TextFilterCaseSensitivity)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static IEnumerable<string> Filter(IEnumerable<string> strings, string filter, TextFilterType filterType, TextFilterMatchOptions textFilterMatchOptions) =>
Filter(strings, filter, filterType, textFilterMatchOptions, TextFilterCaseSensitivity.CaseSensitive);

/// <inheritdoc cref="Filter{TItem}(IEnumerable{TItem}, Func{TItem, string}, string, TextFilterType, TextFilterMatchOptions, TextFilterCaseSensitivity)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static IEnumerable<TItem> Filter<TItem>(IEnumerable<TItem> items, Func<TItem, string> keySelector, string filter, TextFilterType filterType, TextFilterMatchOptions textFilterMatchOptions) =>
Filter(items, keySelector, filter, filterType, textFilterMatchOptions, TextFilterCaseSensitivity.CaseSensitive);

/// <inheritdoc cref="IsMatch(string, string, out int, TextFilterType, TextFilterMatchOptions, TextFilterCaseSensitivity)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static bool IsMatch(string text, string filter, out int score, TextFilterType filterType, TextFilterMatchOptions textFilterMatchOptions) =>
IsMatch(text, filter, out score, filterType, textFilterMatchOptions, TextFilterCaseSensitivity.CaseSensitive);

/// <inheritdoc cref="IsMatch(string, string, TextFilterType, TextFilterMatchOptions, TextFilterCaseSensitivity)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static bool IsMatch(string text, string filter, TextFilterType filterType, TextFilterMatchOptions textFilterMatchOptions) =>
IsMatch(text, filter, filterType, textFilterMatchOptions, TextFilterCaseSensitivity.CaseSensitive);

/// <inheritdoc cref="DoesMatchGlob(string, string, TextFilterMatchOptions, TextFilterCaseSensitivity)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static bool DoesMatchGlob(string text, string filter, TextFilterMatchOptions textFilterMatchOptions) =>
DoesMatchGlob(text, filter, textFilterMatchOptions, TextFilterCaseSensitivity.CaseSensitive);

/// <inheritdoc cref="DoesMatchRegex(string, string, TextFilterMatchOptions, TextFilterCaseSensitivity)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static bool DoesMatchRegex(string text, string filter, TextFilterMatchOptions textFilterMatchOptions) =>
DoesMatchRegex(text, filter, textFilterMatchOptions, TextFilterCaseSensitivity.CaseSensitive);

/// <inheritdoc cref="AnyTokenMatchesGlobFilter(string, HashSet{string}, TextFilterCaseSensitivity)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static bool AnyTokenMatchesGlobFilter(string filterToken, HashSet<string> textTokens) =>
AnyTokenMatchesGlobFilter(filterToken, textTokens, TextFilterCaseSensitivity.CaseSensitive);

/// <inheritdoc cref="AllTokensMatchGlobFilter(string, HashSet{string}, TextFilterCaseSensitivity)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static bool AllTokensMatchGlobFilter(string filterToken, HashSet<string> textTokens) =>
AllTokensMatchGlobFilter(filterToken, textTokens, TextFilterCaseSensitivity.CaseSensitive);
}
Loading