-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
…ly typed one. (#76770)
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
// 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.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.Operations; | ||
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; | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.