/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Workspaces/CoreTest/FindSymbols/RegexPatternMatcherTests.cs
362 строки
10 KB
Cyrus Najmabadi
Tighten regex search pipeline: parse once, bail early, encapsulate matchers
12 мар 2026, 18:15
12 мар 2026, 18:15
a920453
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.PatternMatching; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests.FindSymbols; public class RegexPatternMatcherTests { private static PatternMatch? GetMatch(string pattern, string candidate, bool includeMatchedSpans = false) { using var matcher = PatternMatcher.CreateNameMatcher(pattern, isRegex: true, includeMatchedSpans); if (matcher is null) return null; using var matches = TemporaryArray<PatternMatch>.Empty; if (matcher.AddMatches(candidate, ref matches.AsRef())) return matches[0]; return null; } #region Positive — matching [Fact] public void SimpleLiteral_ExactMatch() { var match = GetMatch("ReadLine", "ReadLine"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void SimpleLiteral_CaseInsensitiveMatch() { var match = GetMatch("readline", "ReadLine"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); Assert.False(match.Value.IsCaseSensitive); } [Fact] public void Alternation_FirstBranch() { var match = GetMatch("(Read|Write)Line", "ReadLine"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void Alternation_SecondBranch() { var match = GetMatch("(Read|Write)Line", "WriteLine"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void SubstringMatch() { var match = GetMatch("Read", "StreamReader"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.NonLowercaseSubstring, match.Value.Kind); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void DotStar_SubstringMatch() { var match = GetMatch("Goo.*Bar", "GooSomethingBar"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void DotStar_PartialMatch() { var match = GetMatch("Goo.*Bar", "MyGooSomethingBarEnd"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.NonLowercaseSubstring, match.Value.Kind); } [Fact] public void AnchoredExact() { var match = GetMatch("^ReadLine$", "ReadLine"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); } [Fact] public void CharacterClass() { var match = GetMatch("[A-Z]ead", "Read"); Assert.NotNull(match); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void MatchedSpans_Reported() { var match = GetMatch("Line", "ReadLine", includeMatchedSpans: true); Assert.NotNull(match); Assert.Single(match.Value.MatchedSpans); Assert.Equal(4, match.Value.MatchedSpans[0].Start); Assert.Equal(4, match.Value.MatchedSpans[0].Length); } [Fact] public void MatchedSpans_RegexSubstring() { var match = GetMatch("Goo.*Bar", "MyGooSomethingBarEnd", includeMatchedSpans: true); Assert.NotNull(match); Assert.Single(match.Value.MatchedSpans); // "GooSomethingBar" starts at index 2, length 15 Assert.Equal(2, match.Value.MatchedSpans[0].Start); Assert.Equal(15, match.Value.MatchedSpans[0].Length); } [Fact] public void MatchedSpans_ExactMatch_CoversFullString() { var match = GetMatch("GooBar", "GooBar", includeMatchedSpans: true); Assert.NotNull(match); Assert.Single(match.Value.MatchedSpans); Assert.Equal(0, match.Value.MatchedSpans[0].Start); Assert.Equal(6, match.Value.MatchedSpans[0].Length); } [Fact] public void MatchedSpans_NotReported_WhenNotRequested() { var match = GetMatch("Line", "ReadLine", includeMatchedSpans: false); Assert.NotNull(match); Assert.True(match.Value.MatchedSpans.IsDefaultOrEmpty); } [Fact] public void Quantifiers_OneOrMore() { var match = GetMatch("Go+Bar", "GooBar"); Assert.NotNull(match); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void Quantifiers_ZeroOrMore() { var match = GetMatch("Go*Bar", "GBar"); Assert.NotNull(match); } [Fact] public void EscapedDot_LiteralMatch() { var match = GetMatch(@"Goo\.Bar", "Goo.Bar"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); } [Fact] public void CaseInsensitive_SubstringMatch() { var match = GetMatch("read", "StreamReader"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.NonLowercaseSubstring, match.Value.Kind); Assert.False(match.Value.IsCaseSensitive); } [Fact] public void CaseSensitive_MatchHigherPriority() { var match = GetMatch("Read", "ReadLine"); Assert.NotNull(match); Assert.True(match.Value.IsCaseSensitive); } #endregion #region Negative — no match [Fact] public void NoMatch_DifferentText() { var match = GetMatch("ReadLine", "WriteBuffer"); Assert.Null(match); } [Fact] public void NoMatch_AnchoredPrefix_NotFull() { var match = GetMatch("^ReadLine$", "MyReadLine"); Assert.Null(match); } [Fact] public void NoMatch_AlternationMiss() { var match = GetMatch("(Read|Write)Line", "StreamBuffer"); Assert.Null(match); } [Fact] public void NoMatch_EmptyCandidate() { var match = GetMatch("Goo", ""); Assert.Null(match); } [Fact] public void NoMatch_CaseMattersForAnchored() { var match = GetMatch("^readLine$", "ReadLine"); Assert.NotNull(match); Assert.False(match.Value.IsCaseSensitive); } #endregion #region Case sensitivity categorization [Fact] public void CaseSensitivity_ExactCaseSensitive() { var match = GetMatch("GooBar", "GooBar"); Assert.NotNull(match); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void CaseSensitivity_ExactCaseInsensitive() { var match = GetMatch("goobar", "GooBar"); Assert.NotNull(match); Assert.False(match.Value.IsCaseSensitive); } [Fact] public void CaseSensitivity_SubstringCaseSensitive() { var match = GetMatch("Goo", "GooBar"); Assert.NotNull(match); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void CaseSensitivity_SubstringCaseInsensitive() { var match = GetMatch("goo", "GooBar"); Assert.NotNull(match); Assert.False(match.Value.IsCaseSensitive); } [Fact] public void CaseSensitivity_Alternation_MixedCase() { var match = GetMatch("Read|write", "ReadLine"); Assert.NotNull(match); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void CaseSensitivity_Alternation_CaseInsensitiveOnly() { var match = GetMatch("read|write", "ReadLine"); Assert.NotNull(match); Assert.False(match.Value.IsCaseSensitive); } #endregion #region Edge cases [Fact] public void SingleCharPattern() { var match = GetMatch("R", "ReadLine"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.NonLowercaseSubstring, match.Value.Kind); } [Fact] public void WildcardOnly_MatchesAnything() { var match = GetMatch(".*", "Anything"); Assert.NotNull(match); } [Fact] public void NullCandidate_NoMatch() { var match = GetMatch("Goo", null!); Assert.Null(match); } [Fact] public void WhitespaceCandidate_NoMatch() { var match = GetMatch("Goo", " "); Assert.Null(match); } [Fact] public void InvalidRegex_ReturnsNull() { var match = GetMatch("(unclosed", "Anything"); Assert.Null(match); } [Fact] public void InvalidRegex_BadEscape_ReturnsNull() { var match = GetMatch(@"\", "Anything"); Assert.Null(match); } #endregion #region Whitespace handling [Fact] public void WhitespaceInPattern_IsStripped() { var match = GetMatch("( Read | Write ) Line", "ReadLine"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void WhitespaceInPattern_IsStripped_SecondBranch() { var match = GetMatch("( Read | Write ) Line", "WriteLine"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); Assert.True(match.Value.IsCaseSensitive); } [Fact] public void WhitespaceInPattern_SimpleLiteral() { var match = GetMatch("Goo Bar", "GooBar"); Assert.NotNull(match); Assert.Equal(PatternMatchKind.Exact, match.Value.Kind); } [Fact] public void WhitespaceInPattern_DoesNotMatchSpacedCandidate() { var match = GetMatch("Goo Bar", "Goo Bar"); Assert.Null(match); } #endregion }