File: src\Analyzers\Core\Analyzers\RemoveUnnecessaryParentheses\AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Precedence;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.RemoveUnnecessaryParentheses;
 
internal abstract class AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer<
    TLanguageKindEnum,
    TParenthesizedExpressionSyntax>
    : AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer
    where TLanguageKindEnum : struct
    where TParenthesizedExpressionSyntax : SyntaxNode
{
    protected AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer()
        : base(IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId,
              EnforceOnBuildValues.RemoveUnnecessaryParentheses,
              options: ParenthesesDiagnosticAnalyzersHelper.Options,
              fadingOption: null,
              new LocalizableResourceString(nameof(AnalyzersResources.Remove_unnecessary_parentheses), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)),
              new LocalizableResourceString(nameof(AnalyzersResources.Parentheses_can_be_removed), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)))
    {
    }
 
    protected abstract TLanguageKindEnum GetSyntaxKind();
    protected abstract ISyntaxFacts GetSyntaxFacts();
 
    public sealed override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
 
    protected sealed override void InitializeWorker(AnalysisContext context)
        => context.RegisterSyntaxNodeAction(AnalyzeSyntax, GetSyntaxKind());
 
    protected abstract bool CanRemoveParentheses(
        TParenthesizedExpressionSyntax parenthesizedExpression, SemanticModel semanticModel, CancellationToken cancellationToken,
        out PrecedenceKind precedence, out bool clarifiesPrecedence);
 
    private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
    {
        var cancellationToken = context.CancellationToken;
        var parenthesizedExpression = (TParenthesizedExpressionSyntax)context.Node;
 
        if (!CanRemoveParentheses(parenthesizedExpression, context.SemanticModel, cancellationToken,
                out var precedence, out var clarifiesPrecedence))
        {
            return;
        }
 
        // Do not remove parentheses from these expressions when there are different kinds
        // between the parent and child of the parenthesized expr..  This is because removing
        // these parens can significantly decrease readability and can confuse many people
        // (including several people quizzed on Roslyn).  For example, most people see
        // "1 + 2 << 3" as "1 + (2 << 3)", when it's actually "(1 + 2) << 3".  To avoid 
        // making code bases more confusing, we just do not touch parens for these constructs 
        // unless both the child and parent have the same kinds.
        switch (precedence)
        {
            case PrecedenceKind.Shift:
            case PrecedenceKind.Bitwise:
            case PrecedenceKind.Coalesce:
                var syntaxFacts = GetSyntaxFacts();
                var child = syntaxFacts.GetExpressionOfParenthesizedExpression(parenthesizedExpression);
 
                var parentKind = parenthesizedExpression.Parent?.RawKind;
                var childKind = child.RawKind;
                if (parentKind != childKind)
                {
                    return;
                }
 
                // Ok to remove if it was the exact same kind.  i.e. ```(a | b) | c```
                // not ok to remove if kinds changed.  i.e. ```(a + b) << c```
                break;
        }
 
        var options = context.GetAnalyzerOptions();
        var preference = ParenthesesDiagnosticAnalyzersHelper.GetLanguageOption(options, precedence);
 
        if (ShouldSkipAnalysis(context, preference.Notification))
        {
            // User doesn't care about these parens.  So nothing for us to do.
            return;
        }
 
        if (preference.Value == ParenthesesPreference.AlwaysForClarity &&
            clarifiesPrecedence)
        {
            // User wants these parens if they clarify precedence, and these parens
            // clarify precedence.  So keep these around.
            return;
        }
 
        // either they don't want unnecessary parentheses, or they want them only for
        // clarification purposes and this does not make things clear.
        Debug.Assert(preference.Value == ParenthesesPreference.NeverIfUnnecessary ||
                     !clarifiesPrecedence);
 
        var additionalLocations = ImmutableArray.Create(
            parenthesizedExpression.GetLocation());
        var additionalUnnecessaryLocations = ImmutableArray.Create(
            parenthesizedExpression.GetFirstToken().GetLocation(),
            parenthesizedExpression.GetLastToken().GetLocation());
 
        context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags(
            Descriptor,
            AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer<TLanguageKindEnum, TParenthesizedExpressionSyntax>.GetDiagnosticSquiggleLocation(parenthesizedExpression, cancellationToken),
            preference.Notification,
            context.Options,
            additionalLocations,
            additionalUnnecessaryLocations));
    }
 
    /// <summary>
    /// Gets the span of text to squiggle underline.
    /// If the expression is contained within a single line, the entire expression span is returned.
    /// Otherwise it will return the span from the expression start to the end of the same line.
    /// </summary>
    private static Location GetDiagnosticSquiggleLocation(TParenthesizedExpressionSyntax parenthesizedExpression, CancellationToken cancellationToken)
    {
        var parenthesizedExpressionLocation = parenthesizedExpression.GetLocation();
 
        var lines = parenthesizedExpression.SyntaxTree.GetText(cancellationToken).Lines;
        var expressionFirstLine = lines.GetLineFromPosition(parenthesizedExpressionLocation.SourceSpan.Start);
 
        var textSpanEndPosition = Math.Min(parenthesizedExpressionLocation.SourceSpan.End, expressionFirstLine.Span.End);
        return Location.Create(parenthesizedExpression.SyntaxTree, TextSpan.FromBounds(parenthesizedExpressionLocation.SourceSpan.Start, textSpanEndPosition));
    }
}