File: CSharpUseTupleSwapDiagnosticAnalyzer.cs
Web Access
Project: ..\..\..\src\CodeStyle\CSharp\Analyzers\Microsoft.CodeAnalysis.CSharp.CodeStyle.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle)
// 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.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.CSharp.UseTupleSwap
{
    /// <summary>
    /// Looks for code of the form:
    /// 
    /// <code>
    ///     var temp = expr_a;
    ///     expr_a = expr_b;
    ///     expr_b = temp;
    /// </code>
    ///
    /// and converts it to:
    /// 
    /// <code>
    ///     (expr_b, expr_a) = (expr_a, expr_b);
    /// </code>
    /// 
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    internal class CSharpUseTupleSwapDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
    {
        public CSharpUseTupleSwapDiagnosticAnalyzer()
            : base(IDEDiagnosticIds.UseTupleSwapDiagnosticId,
                   EnforceOnBuildValues.UseTupleSwap,
                   CSharpCodeStyleOptions.PreferTupleSwap,
                   new LocalizableResourceString(
                       nameof(CSharpAnalyzersResources.Use_tuple_to_swap_values), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
        {
        }
 
        public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
            => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
 
        protected override void InitializeWorker(AnalysisContext context)
        {
            context.RegisterCompilationStartAction(context =>
            {
                // Tuples are only available in C# 7 and above.
                var compilation = context.Compilation;
                if (compilation.LanguageVersion() < LanguageVersion.CSharp7)
                    return;
 
                context.RegisterSyntaxNodeAction(
                    AnalyzeLocalDeclarationStatement,
                    SyntaxKind.LocalDeclarationStatement);
            });
        }
 
        private void AnalyzeLocalDeclarationStatement(SyntaxNodeAnalysisContext syntaxContext)
        {
            var cancellationToken = syntaxContext.CancellationToken;
            var styleOption = syntaxContext.GetCSharpAnalyzerOptions().PreferTupleSwap;
            if (!styleOption.Value)
                return;
 
            var severity = styleOption.Notification.Severity;
 
            // `var expr_temp = expr_a`;
            var localDeclarationStatement = (LocalDeclarationStatementSyntax)syntaxContext.Node;
            if (localDeclarationStatement.UsingKeyword != default ||
                localDeclarationStatement.AwaitKeyword != default)
            {
                return;
            }
 
            if (localDeclarationStatement.Declaration.Variables.Count != 1)
                return;
 
            var variableDeclarator = localDeclarationStatement.Declaration.Variables.First();
            var localDeclarationExprA = variableDeclarator.Initializer?.Value.WalkDownParentheses();
            if (localDeclarationExprA == null)
                return;
 
            // `expr_a = expr_b`;
            var firstAssignmentStatement = localDeclarationStatement.GetNextStatement();
            if (!IsSimpleAssignment(firstAssignmentStatement, out var firstAssignmentExprA, out var firstAssignmentExprB))
                return;
 
            // `expr_b = expr_temp;`
            var secondAssignmentStatement = firstAssignmentStatement.GetNextStatement();
            if (!IsSimpleAssignment(secondAssignmentStatement, out var secondAssignmentExprB, out var secondAssignmentExprTemp))
                return;
 
            if (!localDeclarationExprA.IsEquivalentTo(firstAssignmentExprA, topLevel: false))
                return;
 
            if (!firstAssignmentExprB.IsEquivalentTo(secondAssignmentExprB, topLevel: false))
                return;
 
            if (secondAssignmentExprTemp is not IdentifierNameSyntax { Identifier: var secondAssignmentExprTempIdentifier })
                return;
 
            if (variableDeclarator.Identifier.ValueText != secondAssignmentExprTempIdentifier.ValueText)
                return;
 
            // Can't swap ref-structs.
            var semanticModel = syntaxContext.SemanticModel;
            var local = (ILocalSymbol)semanticModel.GetRequiredDeclaredSymbol(variableDeclarator, cancellationToken);
            if (local.Type.IsRefLikeType || local.Type.RequiresUnsafeModifier())
                return;
 
            var additionalLocations = ImmutableArray.Create(
                localDeclarationStatement.GetLocation(),
                firstAssignmentStatement.GetLocation(),
                secondAssignmentStatement.GetLocation());
 
            // If the diagnostic is not hidden, then just place the user visible part
            // on the local being initialized with the lambda.
            syntaxContext.ReportDiagnostic(DiagnosticHelper.Create(
                Descriptor,
                localDeclarationStatement.GetFirstToken().GetLocation(),
                severity,
                additionalLocations,
                properties: null));
        }
 
        private static bool IsSimpleAssignment(
            [NotNullWhen(true)] StatementSyntax? assignmentStatement,
            [NotNullWhen(true)] out ExpressionSyntax? left,
            [NotNullWhen(true)] out ExpressionSyntax? right)
        {
            left = null;
            right = null;
            if (assignmentStatement == null)
                return false;
 
            if (assignmentStatement is not ExpressionStatementSyntax { Expression: AssignmentExpressionSyntax(SyntaxKind.SimpleAssignmentExpression) assignment })
                return false;
 
            left = assignment.Left.WalkDownParentheses();
            right = assignment.Right.WalkDownParentheses();
 
            return left is not RefExpressionSyntax && right is not RefExpressionSyntax;
        }
    }
}