File: PatternMatching\ContainerPatternMatcher.cs
Web Access
Project: src\src\roslyn\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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 System;
using System.Globalization;
using System.Linq;
using Microsoft.CodeAnalysis.Collections;

namespace Microsoft.CodeAnalysis.PatternMatching;

internal abstract partial class PatternMatcher
{
    /// <summary>
    /// Pattern matcher for matching against the container of a symbol (like <c>System.Collections.Generic</c>).  Understands
    /// how to break on dots and match subportions of that container.  Note: all matching is done in a non-fuzzy way.  Fuzzy
    /// matching is only performed by the <see cref="FuzzyPatternMatcher"/>.
    /// </summary>
    private sealed partial class ContainerPatternMatcher : PatternMatcher
    {
        private readonly PatternSegment[] _patternSegments;
        private readonly char[] _containerSplitCharacters;

        internal ContainerPatternMatcher(
            string[] patternParts,
            char[] containerSplitCharacters,
            bool includeMatchedSpans,
            CultureInfo? culture)
            : base(includeMatchedSpans, culture)
        {
            _containerSplitCharacters = containerSplitCharacters;

            _patternSegments = [.. patternParts.Select(text => new PatternSegment(text.Trim()))];

            _invalidPattern = _patternSegments.Length == 0 || _patternSegments.Any(s => s.IsInvalid);
        }

        public override void Dispose()
        {
            base.Dispose();

            foreach (var segment in _patternSegments)
            {
                segment.Dispose();
            }
        }

        /// <summary>
        /// Container matching is always non-fuzzy.
        /// </summary>
        protected override bool AddMatchesWorker(string container, ref TemporaryArray<PatternMatch> matches)
        {
            using var tempContainerMatches = TemporaryArray<PatternMatch>.Empty;

            var containerParts = container.Split(_containerSplitCharacters, StringSplitOptions.RemoveEmptyEntries);

            if (_patternSegments.Length > containerParts.Length)
            {
                // There weren't enough container parts to match against the pattern parts.
                // So this definitely doesn't match.
                return false;
            }

            // So far so good.  Now break up the container for the candidate and check if all
            // the dotted parts match up correctly.

            for (int i = _patternSegments.Length - 1, j = containerParts.Length - 1;
                    i >= 0;
                    i--, j--)
            {
                var containerName = containerParts[j];
                if (!MatchPatternSegment(containerName, ref _patternSegments[i], ref tempContainerMatches.AsRef()))
                {
                    // This container didn't match the pattern piece.  So there's no match at all.
                    return false;
                }
            }

            // Success, this symbol's full name matched against the dotted name the user was asking
            // about.
            matches.AddRange(tempContainerMatches);
            return true;
        }
    }
}