|
// 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;
}
}
|