diff --git a/src/Microsoft.ComponentDetection.Common/PatternMatchingUtility.cs b/src/Microsoft.ComponentDetection.Common/PatternMatchingUtility.cs index 884e79375..c39cf7dd7 100644 --- a/src/Microsoft.ComponentDetection.Common/PatternMatchingUtility.cs +++ b/src/Microsoft.ComponentDetection.Common/PatternMatchingUtility.cs @@ -1,11 +1,8 @@ -#nullable disable namespace Microsoft.ComponentDetection.Common; using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; public static class PatternMatchingUtility { @@ -13,44 +10,30 @@ public static class PatternMatchingUtility public static FilePatternMatcher GetFilePatternMatcher(IEnumerable patterns) { - var ordinalComparison = Expression.Constant(StringComparison.Ordinal, typeof(StringComparison)); - var asSpan = typeof(MemoryExtensions).GetMethod("AsSpan", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, [typeof(string)], []); - var equals = typeof(MemoryExtensions).GetMethod("Equals", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, [typeof(ReadOnlySpan), typeof(ReadOnlySpan), typeof(StringComparison)], []); - var startsWith = typeof(MemoryExtensions).GetMethod("StartsWith", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, [typeof(ReadOnlySpan), typeof(ReadOnlySpan), typeof(StringComparison)], []); - var endsWith = typeof(MemoryExtensions).GetMethod("EndsWith", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, [typeof(ReadOnlySpan), typeof(ReadOnlySpan), typeof(StringComparison)], []); - - var predicates = new List(); - var left = Expression.Parameter(typeof(ReadOnlySpan), "fileName"); - - foreach (var pattern in patterns) + var matchers = patterns.Select(pattern => pattern switch { - if (pattern.StartsWith('*')) - { - var match = Expression.Constant(pattern[1..], typeof(string)); - var right = Expression.Call(null, asSpan, match); - var combine = Expression.Call(null, endsWith, left, right, ordinalComparison); - predicates.Add(combine); - } - else if (pattern.EndsWith('*')) - { - var match = Expression.Constant(pattern[..^1], typeof(string)); - var right = Expression.Call(null, asSpan, match); - var combine = Expression.Call(null, startsWith, left, right, ordinalComparison); - predicates.Add(combine); - } - else + _ when pattern.StartsWith('*') && pattern.EndsWith('*') => + pattern.Length <= 2 + ? _ => true + : span => span.Contains(pattern.AsSpan(1, pattern.Length - 2), StringComparison.Ordinal), + _ when pattern.StartsWith('*') => + span => span.EndsWith(pattern.AsSpan(1), StringComparison.Ordinal), + _ when pattern.EndsWith('*') => + span => span.StartsWith(pattern.AsSpan(0, pattern.Length - 1), StringComparison.Ordinal), + _ => span => span.Equals(pattern.AsSpan(), StringComparison.Ordinal), + }).ToList(); + + return span => + { + foreach (var matcher in matchers) { - var match = Expression.Constant(pattern, typeof(string)); - var right = Expression.Call(null, asSpan, match); - var combine = Expression.Call(null, equals, left, right, ordinalComparison); - predicates.Add(combine); + if (matcher(span)) + { + return true; + } } - } - - var aggregateExpression = predicates.Aggregate(Expression.OrElse); - - var func = Expression.Lambda(aggregateExpression, left).Compile(); - return func; + return false; + }; } } diff --git a/test/Microsoft.ComponentDetection.Common.Tests/PatternMatchingUtilityTests.cs b/test/Microsoft.ComponentDetection.Common.Tests/PatternMatchingUtilityTests.cs index a0a019b73..b3bb016f3 100644 --- a/test/Microsoft.ComponentDetection.Common.Tests/PatternMatchingUtilityTests.cs +++ b/test/Microsoft.ComponentDetection.Common.Tests/PatternMatchingUtilityTests.cs @@ -10,38 +10,43 @@ namespace Microsoft.ComponentDetection.Common.Tests; public class PatternMatchingUtilityTests { [TestMethod] - public void PatternMatcher_Matches_StartsWith() + [DataRow("test*", "test123", true)] + [DataRow("test*", "123test", false)] + [DataRow("*test", "123test", true)] + [DataRow("*test", "test123", false)] + [DataRow("test", "test", true)] + [DataRow("test", "123test", false)] + [DataRow("*test*", "123test456", true)] + [DataRow("*test*", "test456", true)] + [DataRow("*test*", "123test", true)] + [DataRow("*test*", "test", true)] + [DataRow("*test*", "tes", false)] + [DataRow("*", "anything", true)] + [DataRow("*", "", true)] + [DataRow("**", "anything", true)] + [DataRow("**", "", true)] + public void PatternMatcher_MatchesExpected(string pattern, string input, bool expected) { - var pattern = "test*"; - var input = "test123"; - var matcher = PatternMatchingUtility.GetFilePatternMatcher([pattern]); - matcher(input).Should().BeTrue(); - matcher("123test").Should().BeFalse(); + matcher(input).Should().Be(expected); } [TestMethod] - public void PatternMatcher_Matches_EndsWith() + public void PatternMatcher_MultiplePatterns_MatchesAny() { - var pattern = "*test"; - var input = "123test"; - - var matcher = PatternMatchingUtility.GetFilePatternMatcher([pattern]); + var matcher = PatternMatchingUtility.GetFilePatternMatcher(["a*", "*b"]); - matcher(input).Should().BeTrue(); - matcher("test123").Should().BeFalse(); + matcher("apple").Should().BeTrue(); + matcher("crab").Should().BeTrue(); + matcher("middle").Should().BeFalse(); } [TestMethod] - public void PatternMatcher_Matches_Exact() + public void PatternMatcher_EmptyPatterns_DoesNotThrow() { - var pattern = "test"; - var input = "test"; - - var matcher = PatternMatchingUtility.GetFilePatternMatcher([pattern]); + var matcher = PatternMatchingUtility.GetFilePatternMatcher([]); - matcher(input).Should().BeTrue(); - matcher("123test").Should().BeFalse(); + matcher("anything").Should().BeFalse(); } }