/
WildcardPatternMatcher.cs
111 lines (102 loc) · 4.2 KB
/
WildcardPatternMatcher.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace SonarAnalyzer.Helpers;
internal class WildcardPatternMatcher
{
public static bool IsMatch(string pattern, string input, bool timoutFallback = false) =>
!(string.IsNullOrWhiteSpace(pattern) || string.IsNullOrWhiteSpace(input))
&& WildcardPattern.Create(pattern).Match(input, timoutFallback);
/// <summary>
/// Copied from https://github.com/SonarSource/sonar-plugin-api/blob/a9bd7ff48f0f77811ed909070030678c443c975a/sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java.
/// </summary>
private sealed class WildcardPattern
{
private static readonly ConcurrentDictionary<string, WildcardPattern> Cache = new();
private readonly Regex pattern;
private WildcardPattern(string pattern) =>
this.pattern = new Regex(ToRegex(pattern), RegexOptions.None, RegexConstants.DefaultTimeout);
public bool Match(string value, bool timoutFallback)
{
try
{
return pattern.IsMatch(value.Trim('/'));
}
catch (RegexMatchTimeoutException)
{
return timoutFallback;
}
}
public static WildcardPattern Create(string pattern) =>
Cache.GetOrAdd(pattern + Path.DirectorySeparatorChar, _ => new WildcardPattern(pattern));
private static string ToRegex(string wildcardPattern)
{
var escapedDirectorySeparator = Regex.Escape(Path.DirectorySeparatorChar.ToString());
var sb = new StringBuilder("^", wildcardPattern.Length);
var i = IsSlash(wildcardPattern[0]) ? 1 : 0;
while (i < wildcardPattern.Length)
{
var ch = wildcardPattern[i];
if (ch == '*')
{
if (i + 1 < wildcardPattern.Length && wildcardPattern[i + 1] == '*')
{
// Double asterisk - Zero or more directories
if (i + 2 < wildcardPattern.Length && IsSlash(wildcardPattern[i + 2]))
{
sb.Append($"(.*{escapedDirectorySeparator}|)");
i += 2;
}
else
{
sb.Append(".*");
i += 1;
}
}
else
{
// Single asterisk - Zero or more characters excluding directory separator
sb.Append($"[^{escapedDirectorySeparator}]*?");
}
}
else if (ch == '?')
{
// Any single character excluding directory separator
sb.Append($"[^{escapedDirectorySeparator}]");
}
else if (IsSlash(ch))
{
sb.Append(escapedDirectorySeparator);
}
else
{
sb.Append(Regex.Escape(ch.ToString()));
}
i++;
}
return sb.Append('$').ToString();
}
private static bool IsSlash(char ch) =>
ch == '/' || ch == '\\';
}
}