<commit_before>// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp.InvokeDelegateWithConditionalAccess
{
    internal static class Constants
    {
        public const string Kind = nameof(Kind);
        public const string VariableAndIfStatementForm = nameof(VariableAndIfStatementForm);
        public const string SingleIfStatementForm = nameof(SingleIfStatementForm);
    }

    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    internal class InvokeDelegateWithConditionalAccessAnalyzer : DiagnosticAnalyzer, IBuiltInAnalyzer
    {
        private static readonly DiagnosticDescriptor s_descriptor = 
            CreateDescriptor(DiagnosticSeverity.Hidden);

        private static readonly DiagnosticDescriptor s_unnecessaryDescriptor = 
            CreateDescriptor(DiagnosticSeverity.Hidden, DiagnosticCustomTags.Unnecessary);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
            => ImmutableArray.Create(s_descriptor, s_unnecessaryDescriptor);

        public bool OpenFileOnly(Workspace workspace) => false;

        private static DiagnosticDescriptor CreateDescriptor(DiagnosticSeverity severity, params string[] customTags)
            => new DiagnosticDescriptor(
                IDEDiagnosticIds.InvokeDelegateWithConditionalAccessId,
                CSharpFeaturesResources.Delegate_invocation_can_be_simplified,
                CSharpFeaturesResources.Delegate_invocation_can_be_simplified,
                DiagnosticCategory.Style,
                severity,
                isEnabledByDefault: true,
                customTags: customTags);

        public override void Initialize(AnalysisContext context)
        {
            context.RegisterSyntaxNodeAction(SyntaxNodeAction, SyntaxKind.IfStatement);
        }

        private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext)
        {
            var options = syntaxContext.Options.GetOptionSet();
            var styleOption = options.GetOption(CSharpCodeStyleOptions.PreferConditionalDelegateCall);
            if (!styleOption.Value)
            {
                // Bail immediately if the user has disabled this feature.
                return;
            }

            var severity = styleOption.Notification.Value;

            // look for the form "if (a != null)" or "if (null != a)"
            var ifStatement = (IfStatementSyntax)syntaxContext.Node;

            // ?. is only available in C# 6.0 and above.  Don't offer this refactoring
            // in projects targetting a lesser version.
            if (((CSharpParseOptions)ifStatement.SyntaxTree.Options).LanguageVersion < LanguageVersion.CSharp6)
            {
                return;
            }

            if (!ifStatement.Condition.IsKind(SyntaxKind.NotEqualsExpression))
            {
                return;
            }

            if (ifStatement.Else != null)
            {
                return;
            }

            // Check for both:  "if (...) { a(); }" and "if (...) a();"
            var innerStatement = ifStatement.Statement;
            if (innerStatement.IsKind(SyntaxKind.Block))
            {
                var block = (BlockSyntax)innerStatement;
                if (block.Statements.Count != 1)
                {
                    return;
                }

                innerStatement = block.Statements[0];
            }

            if (!innerStatement.IsKind(SyntaxKind.ExpressionStatement))
            {
                return;
            }

            var expressionStatement = (ExpressionStatementSyntax)innerStatement;

            // Check that it's of the form: "if (a != null) { a(); }
            var invocationExpression = ((ExpressionStatementSyntax)innerStatement).Expression as InvocationExpressionSyntax;
            if (invocationExpression == null)
            {
                return;
            }

            var condition = (BinaryExpressionSyntax)ifStatement.Condition;
            if (TryCheckVariableAndIfStatementForm(
                    syntaxContext, ifStatement, condition, 
                    expressionStatement, invocationExpression,
                    severity))
            {
                return;
            }

            TryCheckSingleIfStatementForm(
                syntaxContext, ifStatement, condition, 
                expressionStatement, invocationExpression,
                severity);
        }

        private bool TryCheckSingleIfStatementForm(
            SyntaxNodeAnalysisContext syntaxContext,
            IfStatementSyntax ifStatement,
            BinaryExpressionSyntax condition,
            ExpressionStatementSyntax expressionStatement,
            InvocationExpressionSyntax invocationExpression,
            DiagnosticSeverity severity)
        {
            var cancellationToken = syntaxContext.CancellationToken;

            // Look for the form:  "if (someExpr != null) someExpr()"
            if (condition.Left.IsKind(SyntaxKind.NullLiteralExpression) ||
                condition.Right.IsKind(SyntaxKind.NullLiteralExpression))
            {
                var expr = condition.Left.IsKind(SyntaxKind.NullLiteralExpression)
                    ? condition.Right
                    : condition.Left;

                cancellationToken.ThrowIfCancellationRequested();
                if (SyntaxFactory.AreEquivalent(expr, invocationExpression.Expression, topLevel: false))
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    // Looks good!
                    var tree = syntaxContext.SemanticModel.SyntaxTree;
                    var additionalLocations = new List<Location>
                    {
                        Location.Create(tree, ifStatement.Span),
                        Location.Create(tree, expressionStatement.Span)
                    };

                    ReportDiagnostics(
                        syntaxContext, ifStatement, ifStatement, 
                        expressionStatement, severity, additionalLocations,
                        Constants.SingleIfStatementForm);

                    return true;
                }
            }

            return false;
        }

        private static void ReportDiagnostics(
            SyntaxNodeAnalysisContext syntaxContext,
            StatementSyntax firstStatement,
            IfStatementSyntax ifStatement,
            ExpressionStatementSyntax expressionStatement, 
            DiagnosticSeverity severity, 
            List<Location> additionalLocations, 
            string kind)
        {
            var tree = syntaxContext.Node.SyntaxTree;

            var properties = ImmutableDictionary<string, string>.Empty.Add(
                Constants.Kind, kind);

            var previousToken = expressionStatement.GetFirstToken().GetPreviousToken();
            var nextToken = expressionStatement.GetLastToken().GetNextToken();

            // Fade out the code up to the expression statement.
            syntaxContext.ReportDiagnostic(Diagnostic.Create(s_unnecessaryDescriptor,
                Location.Create(tree, TextSpan.FromBounds(firstStatement.SpanStart, previousToken.Span.End)),
                additionalLocations, properties));

            // Put a diagnostic with the appropriate severity on the expression-statement itself.
            syntaxContext.ReportDiagnostic(Diagnostic.Create(CreateDescriptor(severity),
                expressionStatement.GetLocation(),
                additionalLocations, properties));

            // If the if-statement extends past the expression statement, then fade out the rest.
            if (nextToken.Span.Start < ifStatement.Span.End)
            {
                syntaxContext.ReportDiagnostic(Diagnostic.Create(s_unnecessaryDescriptor,
                    Location.Create(tree, TextSpan.FromBounds(nextToken.Span.Start, ifStatement.Span.End)),
                    additionalLocations, properties));
            }
        }

        private bool TryCheckVariableAndIfStatementForm(
            SyntaxNodeAnalysisContext syntaxContext,
            IfStatementSyntax ifStatement,
            BinaryExpressionSyntax condition,
            ExpressionStatementSyntax expressionStatement,<commit_msg>Use new base-class to simplify code.<commit_after>// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp.InvokeDelegateWithConditionalAccess
{
    internal static class Constants
    {
        public const string Kind = nameof(Kind);
        public const string VariableAndIfStatementForm = nameof(VariableAndIfStatementForm);
        public const string SingleIfStatementForm = nameof(SingleIfStatementForm);
    }

    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    internal class InvokeDelegateWithConditionalAccessAnalyzer : AbstractCodeStyleDiagnosticAnalyzer, IBuiltInAnalyzer
    {
        public InvokeDelegateWithConditionalAccessAnalyzer()
            : base(IDEDiagnosticIds.InvokeDelegateWithConditionalAccessId, 
                   new LocalizableResourceString(nameof(CSharpFeaturesResources.Delegate_invocation_can_be_simplified), CSharpFeaturesResources.ResourceManager, typeof(CSharpFeaturesResources)))
        {
        }

        public bool OpenFileOnly(Workspace workspace) => false;

        public override void Initialize(AnalysisContext context)
        {
            context.RegisterSyntaxNodeAction(SyntaxNodeAction, SyntaxKind.IfStatement);
        }

        private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext)
        {
            var options = syntaxContext.Options.GetOptionSet();
            var styleOption = options.GetOption(CSharpCodeStyleOptions.PreferConditionalDelegateCall);
            if (!styleOption.Value)
            {
                // Bail immediately if the user has disabled this feature.
                return;
            }

            var severity = styleOption.Notification.Value;

            // look for the form "if (a != null)" or "if (null != a)"
            var ifStatement = (IfStatementSyntax)syntaxContext.Node;

            // ?. is only available in C# 6.0 and above.  Don't offer this refactoring
            // in projects targetting a lesser version.
            if (((CSharpParseOptions)ifStatement.SyntaxTree.Options).LanguageVersion < LanguageVersion.CSharp6)
            {
                return;
            }

            if (!ifStatement.Condition.IsKind(SyntaxKind.NotEqualsExpression))
            {
                return;
            }

            if (ifStatement.Else != null)
            {
                return;
            }

            // Check for both:  "if (...) { a(); }" and "if (...) a();"
            var innerStatement = ifStatement.Statement;
            if (innerStatement.IsKind(SyntaxKind.Block))
            {
                var block = (BlockSyntax)innerStatement;
                if (block.Statements.Count != 1)
                {
                    return;
                }

                innerStatement = block.Statements[0];
            }

            if (!innerStatement.IsKind(SyntaxKind.ExpressionStatement))
            {
                return;
            }

            var expressionStatement = (ExpressionStatementSyntax)innerStatement;

            // Check that it's of the form: "if (a != null) { a(); }
            var invocationExpression = ((ExpressionStatementSyntax)innerStatement).Expression as InvocationExpressionSyntax;
            if (invocationExpression == null)
            {
                return;
            }

            var condition = (BinaryExpressionSyntax)ifStatement.Condition;
            if (TryCheckVariableAndIfStatementForm(
                    syntaxContext, ifStatement, condition, 
                    expressionStatement, invocationExpression,
                    severity))
            {
                return;
            }

            TryCheckSingleIfStatementForm(
                syntaxContext, ifStatement, condition, 
                expressionStatement, invocationExpression,
                severity);
        }

        private bool TryCheckSingleIfStatementForm(
            SyntaxNodeAnalysisContext syntaxContext,
            IfStatementSyntax ifStatement,
            BinaryExpressionSyntax condition,
            ExpressionStatementSyntax expressionStatement,
            InvocationExpressionSyntax invocationExpression,
            DiagnosticSeverity severity)
        {
            var cancellationToken = syntaxContext.CancellationToken;

            // Look for the form:  "if (someExpr != null) someExpr()"
            if (condition.Left.IsKind(SyntaxKind.NullLiteralExpression) ||
                condition.Right.IsKind(SyntaxKind.NullLiteralExpression))
            {
                var expr = condition.Left.IsKind(SyntaxKind.NullLiteralExpression)
                    ? condition.Right
                    : condition.Left;

                cancellationToken.ThrowIfCancellationRequested();
                if (SyntaxFactory.AreEquivalent(expr, invocationExpression.Expression, topLevel: false))
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    // Looks good!
                    var tree = syntaxContext.SemanticModel.SyntaxTree;
                    var additionalLocations = new List<Location>
                    {
                        Location.Create(tree, ifStatement.Span),
                        Location.Create(tree, expressionStatement.Span)
                    };

                    ReportDiagnostics(
                        syntaxContext, ifStatement, ifStatement, 
                        expressionStatement, severity, additionalLocations,
                        Constants.SingleIfStatementForm);

                    return true;
                }
            }

            return false;
        }

        private void ReportDiagnostics(
            SyntaxNodeAnalysisContext syntaxContext,
            StatementSyntax firstStatement,
            IfStatementSyntax ifStatement,
            ExpressionStatementSyntax expressionStatement, 
            DiagnosticSeverity severity, 
            List<Location> additionalLocations, 
            string kind)
        {
            var tree = syntaxContext.Node.SyntaxTree;

            var properties = ImmutableDictionary<string, string>.Empty.Add(
                Constants.Kind, kind);

            var previousToken = expressionStatement.GetFirstToken().GetPreviousToken();
            var nextToken = expressionStatement.GetLastToken().GetNextToken();

            // Fade out the code up to the expression statement.
            syntaxContext.ReportDiagnostic(Diagnostic.Create(this.UnnecessaryWithSuggestionDescriptor,
                Location.Create(tree, TextSpan.FromBounds(firstStatement.SpanStart, previousToken.Span.End)),
                additionalLocations, properties));

            // Put a diagnostic with the appropriate severity on the expression-statement itself.
            syntaxContext.ReportDiagnostic(Diagnostic.Create(CreateDescriptor(this.DescriptorId, severity),
                expressionStatement.GetLocation(),
                additionalLocations, properties));

            // If the if-statement extends past the expression statement, then fade out the rest.
            if (nextToken.Span.Start < ifStatement.Span.End)
            {
                syntaxContext.ReportDiagnostic(Diagnostic.Create(this.UnnecessaryWithSuggestionDescriptor,
                    Location.Create(tree, TextSpan.FromBounds(nextToken.Span.Start, ifStatement.Span.End)),
                    additionalLocations, properties));
            }
        }

        private bool TryCheckVariableAndIfStatementForm(
            SyntaxNodeAnalysisContext syntaxContext,
            IfStatementSyntax ifStatement,
            BinaryExpressionSyntax condition,
            ExpressionStatementSyntax expressionStatement,
{'fraction_non_alphanumeric': 0.06668264334072255, 'fraction_numerical': 0.0008387753879336169, 'mean_word_length': 2.033254588406324, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2022032', 'n_tokens_mistral': 3766, 'n_tokens_neox': 3520, 'n_words': 791}