Skip to content

Commit 8547c2c

Browse files
committed
Replace expression tree pattern matching with closures
1 parent 97f3d73 commit 8547c2c

2 files changed

Lines changed: 38 additions & 68 deletions

File tree

Lines changed: 21 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,39 @@
1-
#nullable disable
21
namespace Microsoft.ComponentDetection.Common;
32

43
using System;
54
using System.Collections.Generic;
65
using System.Linq;
7-
using System.Linq.Expressions;
8-
using System.Reflection;
96

107
public static class PatternMatchingUtility
118
{
129
public delegate bool FilePatternMatcher(ReadOnlySpan<char> span);
1310

1411
public static FilePatternMatcher GetFilePatternMatcher(IEnumerable<string> patterns)
1512
{
16-
var ordinalComparison = Expression.Constant(StringComparison.Ordinal, typeof(StringComparison));
17-
var asSpan = typeof(MemoryExtensions).GetMethod("AsSpan", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, [typeof(string)], []);
18-
var equals = typeof(MemoryExtensions).GetMethod("Equals", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, [typeof(ReadOnlySpan<char>), typeof(ReadOnlySpan<char>), typeof(StringComparison)], []);
19-
var startsWith = typeof(MemoryExtensions).GetMethod("StartsWith", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, [typeof(ReadOnlySpan<char>), typeof(ReadOnlySpan<char>), typeof(StringComparison)], []);
20-
var endsWith = typeof(MemoryExtensions).GetMethod("EndsWith", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Standard, [typeof(ReadOnlySpan<char>), typeof(ReadOnlySpan<char>), typeof(StringComparison)], []);
21-
22-
var predicates = new List<Expression>();
23-
var left = Expression.Parameter(typeof(ReadOnlySpan<char>), "fileName");
24-
25-
foreach (var pattern in patterns)
13+
var matchers = patterns.Select<string, FilePatternMatcher>(pattern => pattern switch
2614
{
27-
if (pattern.StartsWith('*'))
28-
{
29-
var match = Expression.Constant(pattern[1..], typeof(string));
30-
var right = Expression.Call(null, asSpan, match);
31-
var combine = Expression.Call(null, endsWith, left, right, ordinalComparison);
32-
predicates.Add(combine);
33-
}
34-
else if (pattern.EndsWith('*'))
35-
{
36-
var match = Expression.Constant(pattern[..^1], typeof(string));
37-
var right = Expression.Call(null, asSpan, match);
38-
var combine = Expression.Call(null, startsWith, left, right, ordinalComparison);
39-
predicates.Add(combine);
40-
}
41-
else
15+
_ when pattern.StartsWith('*') && pattern.EndsWith('*') =>
16+
pattern.Length <= 2
17+
? _ => true
18+
: span => span.Contains(pattern.AsSpan(1, pattern.Length - 2), StringComparison.Ordinal),
19+
_ when pattern.StartsWith('*') =>
20+
span => span.EndsWith(pattern.AsSpan(1), StringComparison.Ordinal),
21+
_ when pattern.EndsWith('*') =>
22+
span => span.StartsWith(pattern.AsSpan(0, pattern.Length - 1), StringComparison.Ordinal),
23+
_ => span => span.Equals(pattern.AsSpan(), StringComparison.Ordinal),
24+
}).ToList();
25+
26+
return span =>
27+
{
28+
foreach (var matcher in matchers)
4229
{
43-
var match = Expression.Constant(pattern, typeof(string));
44-
var right = Expression.Call(null, asSpan, match);
45-
var combine = Expression.Call(null, equals, left, right, ordinalComparison);
46-
predicates.Add(combine);
30+
if (matcher(span))
31+
{
32+
return true;
33+
}
4734
}
48-
}
49-
50-
var aggregateExpression = predicates.Aggregate(Expression.OrElse);
51-
52-
var func = Expression.Lambda<FilePatternMatcher>(aggregateExpression, left).Compile();
5335

54-
return func;
36+
return false;
37+
};
5538
}
5639
}

test/Microsoft.ComponentDetection.Common.Tests/PatternMatchingUtilityTests.cs

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,25 @@ namespace Microsoft.ComponentDetection.Common.Tests;
1010
public class PatternMatchingUtilityTests
1111
{
1212
[TestMethod]
13-
public void PatternMatcher_Matches_StartsWith()
13+
[DataRow("test*", "test123", true)]
14+
[DataRow("test*", "123test", false)]
15+
[DataRow("*test", "123test", true)]
16+
[DataRow("*test", "test123", false)]
17+
[DataRow("test", "test", true)]
18+
[DataRow("test", "123test", false)]
19+
[DataRow("*test*", "123test456", true)]
20+
[DataRow("*test*", "test456", true)]
21+
[DataRow("*test*", "123test", true)]
22+
[DataRow("*test*", "test", true)]
23+
[DataRow("*test*", "tes", false)]
24+
[DataRow("*", "anything", true)]
25+
[DataRow("*", "", true)]
26+
[DataRow("**", "anything", true)]
27+
[DataRow("**", "", true)]
28+
public void PatternMatcher_MatchesExpected(string pattern, string input, bool expected)
1429
{
15-
var pattern = "test*";
16-
var input = "test123";
17-
18-
var matcher = PatternMatchingUtility.GetFilePatternMatcher([pattern]);
19-
20-
matcher(input).Should().BeTrue();
21-
matcher("123test").Should().BeFalse();
22-
}
23-
24-
[TestMethod]
25-
public void PatternMatcher_Matches_EndsWith()
26-
{
27-
var pattern = "*test";
28-
var input = "123test";
29-
30-
var matcher = PatternMatchingUtility.GetFilePatternMatcher([pattern]);
31-
32-
matcher(input).Should().BeTrue();
33-
matcher("test123").Should().BeFalse();
34-
}
35-
36-
[TestMethod]
37-
public void PatternMatcher_Matches_Exact()
38-
{
39-
var pattern = "test";
40-
var input = "test";
41-
4230
var matcher = PatternMatchingUtility.GetFilePatternMatcher([pattern]);
4331

44-
matcher(input).Should().BeTrue();
45-
matcher("123test").Should().BeFalse();
32+
matcher(input).Should().Be(expected);
4633
}
4734
}

0 commit comments

Comments
 (0)