File: FilterExpressionWrapper.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.Filter.Source\Microsoft.TestPlatform.Filter.Source.csproj (Microsoft.TestPlatform.Filter.Source)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System;
using System.Collections.Generic;
#if IS_VSTEST_REPO
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
#endif

#if !IS_VSTEST_REPO
using Microsoft.CodeAnalysis;
#endif

#if IS_VSTEST_REPO
using Microsoft.VisualStudio.TestPlatform.ObjectModel;

using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
#endif

namespace Microsoft.VisualStudio.TestPlatform.Common.Filtering;

/// <summary>
/// Class holds information related to filtering criteria.
/// </summary>
#if IS_VSTEST_REPO
public class FilterExpressionWrapper
#else
[Embedded]
internal sealed class FilterExpressionWrapper
#endif
{
    /// <summary>
    /// FilterExpression corresponding to filter criteria
    /// </summary>
    private readonly FilterExpression? _filterExpression;

    /// <remarks>
    /// Exposed for testing purpose.
    /// </remarks>
    internal readonly FastFilter? FastFilter;

    /// <summary>
    /// Initializes FilterExpressionWrapper with given filterString and options.
    /// </summary>
#if IS_VSTEST_REPO
    public FilterExpressionWrapper(string filterString, FilterOptions? options)
#else
    public FilterExpressionWrapper(string filterString)
#endif
    {
#if IS_VSTEST_REPO
        ValidateArg.NotNullOrEmpty(filterString, nameof(filterString));
#endif

        FilterString = filterString;

#if IS_VSTEST_REPO
        FilterOptions = options;
#endif

        try
        {
            // We prefer fast filter when it's available.
            _filterExpression = FilterExpression.Parse(filterString, out FastFilter);

            if (UseFastFilter)
            {
                _filterExpression = null;

                // Property value regex is only supported for fast filter,
                // so we ignore it if no fast filter is constructed.

#if IS_VSTEST_REPO
                // TODO: surface an error message to user.
                var regexString = options?.FilterRegEx;
                if (!regexString.IsNullOrEmpty())
                {
                    TPDebug.Assert(options!.FilterRegExReplacement == null || options.FilterRegEx != null);
                    FastFilter.PropertyValueRegex = new Regex(regexString, RegexOptions.Compiled);
                    FastFilter.PropertyValueRegexReplacement = options.FilterRegExReplacement;
                }
#endif
            }

        }
        catch (FormatException ex)
        {
            ParseError = ex.Message;
        }
        catch (ArgumentException ex)
        {
            FastFilter = null;
            ParseError = ex.Message;
        }
    }

#if IS_VSTEST_REPO
    /// <summary>
    /// Initializes FilterExpressionWrapper with given filterString.
    /// </summary>
    public FilterExpressionWrapper(string filterString)
        : this(filterString, null)
    {
    }
#endif

#if IS_VSTEST_REPO
    [MemberNotNullWhen(true, nameof(FastFilter))]
#endif
    private bool UseFastFilter => FastFilter != null;

    /// <summary>
    /// User specified filter criteria.
    /// </summary>
    public string FilterString { get; }

#if IS_VSTEST_REPO
    /// <summary>
    /// User specified additional filter options.
    /// </summary>
    public FilterOptions? FilterOptions { get; }
#endif

    /// <summary>
    /// Parsing error (if any), when parsing 'FilterString' with built-in parser.
    /// </summary>
    public string? ParseError { get; }

    /// <summary>
    /// Validate if underlying filter expression is valid for given set of supported properties.
    /// </summary>
#if IS_VSTEST_REPO
    public string[]? ValidForProperties(IEnumerable<string>? supportedProperties, Func<string, TestProperty?>? propertyProvider)
#else
    public string[]? ValidForProperties(IEnumerable<string>? supportedProperties)
#endif
        => UseFastFilter
            ? FastFilter!.ValidForProperties(supportedProperties)
#if IS_VSTEST_REPO
            : _filterExpression?.ValidForProperties(supportedProperties, propertyProvider);
#else
            : _filterExpression?.ValidForProperties(supportedProperties);
#endif

    /// <summary>
    /// Evaluate filterExpression with given propertyValueProvider.
    /// </summary>
    public bool Evaluate(Func<string, object?> propertyValueProvider)
    {
#if IS_VSTEST_REPO
        ValidateArg.NotNull(propertyValueProvider, nameof(propertyValueProvider));
#endif

        return UseFastFilter
            ? FastFilter!.Evaluate(propertyValueProvider)
            : _filterExpression != null && _filterExpression.Evaluate(propertyValueProvider);
    }
}