File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\Utilities\TypeStyle\CSharpTypeStyleHelper.cs
Web Access
Project: src\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.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.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Simplification;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.CodeAnalysis.CSharp.Utilities;
 
internal readonly struct TypeStyleResult
{
    private readonly CSharpTypeStyleHelper _helper;
    private readonly TypeSyntax _typeName;
    private readonly SemanticModel _semanticModel;
    private readonly CSharpSimplifierOptions _options;
    private readonly CancellationToken _cancellationToken;
 
    /// <summary>
    /// Whether or not converting would transition the code to the style the user prefers. i.e. if the user likes
    /// <c>var</c> for everything, and you have <c>int i = 0</c> then <see cref="IsStylePreferred"/> will be
    /// <see langword="true"/>. However, if the user likes <c>var</c> for everything and you have <c>var i = 0</c>,
    /// then it's still possible to convert that, it would just be <see langword="false"/> for
    /// <see cref="IsStylePreferred"/> because it goes against the user's preferences.
    /// </summary>
    /// <remarks>
    /// <para>In general, most features should only convert the type if <see cref="IsStylePreferred"/> is
    /// <see langword="true"/>. The one exception is the refactoring, which is explicitly there to still let people
    /// convert things quickly, even if it's going against their stated style.</para>
    /// </remarks>
    public readonly bool IsStylePreferred;
    public readonly NotificationOption2 Notification;
 
    public TypeStyleResult(CSharpTypeStyleHelper helper, TypeSyntax typeName, SemanticModel semanticModel, CSharpSimplifierOptions options, bool isStylePreferred, NotificationOption2 notificationOption, CancellationToken cancellationToken) : this()
    {
        _helper = helper;
        _typeName = typeName;
        _semanticModel = semanticModel;
        _options = options;
        _cancellationToken = cancellationToken;
 
        IsStylePreferred = isStylePreferred;
        Notification = notificationOption;
    }
 
    public bool CanConvert()
        => _helper.TryAnalyzeVariableDeclaration(_typeName, _semanticModel, _options, _cancellationToken);
}
 
internal abstract partial class CSharpTypeStyleHelper
{
    protected abstract bool IsStylePreferred(in State state);
 
    public virtual TypeStyleResult AnalyzeTypeName(
        TypeSyntax typeName, SemanticModel semanticModel,
        CSharpSimplifierOptions options, CancellationToken cancellationToken)
    {
        if (typeName?.FirstAncestorOrSelf<SyntaxNode>(a => a.Kind() is SyntaxKind.DeclarationExpression or SyntaxKind.VariableDeclaration or SyntaxKind.ForEachStatement) is not { } declaration)
        {
            return default;
        }
 
        var state = new State(
            declaration, semanticModel, options, cancellationToken);
        var isStylePreferred = this.IsStylePreferred(in state);
        var notificationOption = state.GetDiagnosticSeverityPreference();
 
        return new TypeStyleResult(
            this, typeName, semanticModel, options, isStylePreferred, notificationOption, cancellationToken);
    }
 
    internal abstract bool TryAnalyzeVariableDeclaration(
        TypeSyntax typeName, SemanticModel semanticModel, CSharpSimplifierOptions options, CancellationToken cancellationToken);
 
    protected abstract bool AssignmentSupportsStylePreference(
        SyntaxToken identifier, TypeSyntax typeName, ExpressionSyntax initializer, SemanticModel semanticModel, CSharpSimplifierOptions options, CancellationToken cancellationToken);
 
    internal TypeSyntax? FindAnalyzableType(SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken)
    {
        Debug.Assert(node.Kind() is SyntaxKind.VariableDeclaration or SyntaxKind.ForEachStatement or SyntaxKind.DeclarationExpression);
 
        return node switch
        {
            VariableDeclarationSyntax variableDeclaration => ShouldAnalyzeVariableDeclaration(variableDeclaration, cancellationToken)
                ? variableDeclaration.Type
                : null,
            ForEachStatementSyntax forEachStatement => ShouldAnalyzeForEachStatement(forEachStatement, semanticModel, cancellationToken)
                ? forEachStatement.Type
                : null,
            DeclarationExpressionSyntax declarationExpression => ShouldAnalyzeDeclarationExpression(declarationExpression, semanticModel, cancellationToken)
                ? declarationExpression.Type
                : null,
            _ => null,
        };
    }
 
    public virtual bool ShouldAnalyzeVariableDeclaration(VariableDeclarationSyntax variableDeclaration, CancellationToken cancellationToken)
    {
        // implicit type is applicable only for local variables and
        // such declarations cannot have multiple declarators and
        // must have an initializer.
        var isSupportedParentKind = variableDeclaration.Parent is (kind:
            SyntaxKind.LocalDeclarationStatement or
            SyntaxKind.ForStatement or
            SyntaxKind.UsingStatement);
 
        return isSupportedParentKind &&
            variableDeclaration.Variables is [{ Initializer: not null }];
    }
 
    protected virtual bool ShouldAnalyzeForEachStatement(ForEachStatementSyntax forEachStatement, SemanticModel semanticModel, CancellationToken cancellationToken)
        => true;
 
    protected virtual bool ShouldAnalyzeDeclarationExpression(DeclarationExpressionSyntax declaration, SemanticModel semanticModel, CancellationToken cancellationToken)
    {
        // Ensure that deconstruction assignment or foreach variable statement have a non-null deconstruct method.
        DeconstructionInfo? deconstructionInfoOpt = null;
        switch (declaration.Parent)
        {
            case AssignmentExpressionSyntax assignmentExpression:
                if (assignmentExpression.IsDeconstruction())
                {
                    deconstructionInfoOpt = semanticModel.GetDeconstructionInfo(assignmentExpression);
                }
 
                break;
 
            case ForEachVariableStatementSyntax forEachVariableStatement:
                deconstructionInfoOpt = semanticModel.GetDeconstructionInfo(forEachVariableStatement);
                break;
        }
 
        return !deconstructionInfoOpt.HasValue || !deconstructionInfoOpt.Value.Nested.IsEmpty || deconstructionInfoOpt.Value.Method != null;
    }
}