File: src\Analyzers\CSharp\Analyzers\UseImplicitlyTypedLambdaExpression\CSharpUseImplicitlyTypedLambdaExpressionDiagnosticAnalyzer.cs
Web Access
Project: src\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.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.UseImplicitlyTypedLambdaExpression;
 
using static SyntaxFactory;
 
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpUseImplicitlyTypedLambdaExpressionDiagnosticAnalyzer()
    : AbstractBuiltInCodeStyleDiagnosticAnalyzer(IDEDiagnosticIds.UseImplicitlyTypedLambdaExpressionDiagnosticId,
        EnforceOnBuildValues.UseImplicitObjectCreation,
        CSharpCodeStyleOptions.PreferImplicitlyTypedLambdaExpression,
        new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_implicitly_typed_lambda), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)),
        new LocalizableResourceString(nameof(CSharpAnalyzersResources.Lambda_expression_can_be_simplified), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
 
    protected override void InitializeWorker(AnalysisContext context)
        => context.RegisterSyntaxNodeAction(AnalyzeIfEnabled,
            SyntaxKind.ParenthesizedLambdaExpression);
 
    private void AnalyzeIfEnabled(SyntaxNodeAnalysisContext context)
    {
        var cancellationToken = context.CancellationToken;
        var analyzerOptions = context.Options;
        var semanticModel = context.SemanticModel;
        var option = analyzerOptions.GetCSharpAnalyzerOptions(semanticModel.SyntaxTree).PreferImplicitlyTypedLambdaExpression;
        if (!option.Value || ShouldSkipAnalysis(context, option.Notification))
            return;
 
        var explicitLambda = (ParenthesizedLambdaExpressionSyntax)context.Node;
        if (!Analyze(semanticModel, explicitLambda, cancellationToken))
            return;
 
        context.ReportDiagnostic(DiagnosticHelper.Create(
            Descriptor,
            explicitLambda.ParameterList.OpenParenToken.GetLocation(),
            option.Notification,
            context.Options,
            [explicitLambda.GetLocation()],
            properties: null));
    }
 
    public static bool Analyze(
        SemanticModel semanticModel,
        ParenthesizedLambdaExpressionSyntax explicitLambda,
        CancellationToken cancellationToken)
    {
        // If the lambda has an explicit return type, then do not offer the feature.  Explicit return types are used to
        // provide full semantic information to the compiler so it does not need to perform speculative lambda binding.
        // Removing may cause code compilation performance to regress.
        if (explicitLambda.ReturnType != null)
            return false;
 
        // Needs to have at least one parameter, all parameters need to have a provided type, and no parameters can have a
        // default value provided.
        if (explicitLambda.ParameterList.Parameters.Count == 0 ||
            explicitLambda.ParameterList.Parameters.Any(p => p.Type is null || p.Default != null))
        {
            return false;
        }
 
        // Prior to C# 14, implicitly typed lambdas can't have modifiers on parameters.
        var languageVersion = semanticModel.Compilation.LanguageVersion();
        if (!languageVersion.IsCSharp14OrAbove() && explicitLambda.ParameterList.Parameters.Any(p => p.Modifiers.Count > 0))
            return false;
 
        var implicitLambda = ConvertToImplicitlyTypedLambda(explicitLambda);
 
        var analyzer = new SpeculationAnalyzer(
            explicitLambda, implicitLambda, semanticModel, cancellationToken);
        if (analyzer.ReplacementChangesSemantics())
            return false;
 
        if (semanticModel.GetSymbolInfo(explicitLambda, cancellationToken).Symbol is not IMethodSymbol explicitLambdaMethod ||
            analyzer.SpeculativeSemanticModel.GetSymbolInfo(analyzer.ReplacedExpression, cancellationToken).Symbol is not IMethodSymbol implicitLambdaMethod)
        {
            return false;
        }
 
        if (!SignatureComparer.Instance.HaveSameSignature(explicitLambdaMethod, implicitLambdaMethod, caseSensitive: true))
            return false;
 
        return true;
    }
 
    public static LambdaExpressionSyntax ConvertToImplicitlyTypedLambda(ParenthesizedLambdaExpressionSyntax explicitLambda)
    {
        var implicitLambda = explicitLambda.ReplaceNodes(
            explicitLambda.ParameterList.Parameters,
            (parameter, _) => RemoveParamsModifier(
                parameter.WithType(null)
                         .WithIdentifier(parameter.Identifier.WithPrependedLeadingTrivia(parameter.Type!.GetLeadingTrivia()))));
 
        // If the lambda only has one parameter, then convert it to the non-parenthesized form.
        if (implicitLambda.ParameterList.Parameters is not ([{ AttributeLists.Count: 0, Modifiers.Count: 0 } parameter]))
            return implicitLambda;
 
        return SimpleLambdaExpression(
            explicitLambda.AttributeLists,
            explicitLambda.Modifiers,
            parameter.WithTriviaFrom(explicitLambda.ParameterList),
            explicitLambda.Block,
            explicitLambda.ExpressionBody);
    }
 
    private static ParameterSyntax RemoveParamsModifier(ParameterSyntax parameter)
    {
        // Implicitly typed lambdas aren't ever allowed to have the 'params' modifier.
        var paramsModifierIndex = parameter.Modifiers.IndexOf(SyntaxKind.ParamsKeyword);
        return paramsModifierIndex >= 0 ? parameter.WithModifiers(parameter.Modifiers.RemoveAt(paramsModifierIndex)) : parameter;
    }
}