-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
Move UseExpressionBodyForLambda to Analyzers layer
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// 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.Collections.Immutable; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Threading; | ||
using Microsoft.CodeAnalysis.CodeStyle; | ||
using Microsoft.CodeAnalysis.CSharp.CodeStyle; | ||
using Microsoft.CodeAnalysis.CSharp.Extensions; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Text; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer | ||
{ | ||
private static readonly DiagnosticDescriptor s_useExpressionBodyForLambda = CreateDescriptorWithId(UseExpressionBodyForLambdaHelpers.UseExpressionBodyTitle, UseExpressionBodyForLambdaHelpers.UseExpressionBodyTitle); | ||
private static readonly DiagnosticDescriptor s_useBlockBodyForLambda = CreateDescriptorWithId(UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle, UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle); | ||
|
||
public UseExpressionBodyForLambdaDiagnosticAnalyzer() : base( | ||
ImmutableDictionary<DiagnosticDescriptor, Options.ILanguageSpecificOption>.Empty | ||
.Add(s_useExpressionBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas) | ||
.Add(s_useBlockBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas), | ||
LanguageNames.CSharp) | ||
{ | ||
} | ||
|
||
public override DiagnosticAnalyzerCategory GetAnalyzerCategory() | ||
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis; | ||
|
||
protected override void InitializeWorker(AnalysisContext context) | ||
=> context.RegisterSyntaxNodeAction(AnalyzeIfEnabled, | ||
SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression); | ||
|
||
private static void AnalyzeIfEnabled(SyntaxNodeAnalysisContext context) | ||
{ | ||
var analyzerOptions = context.Options; | ||
var syntaxTree = context.SemanticModel.SyntaxTree; | ||
var optionValue = UseExpressionBodyForLambdaHelpers.GetCodeStyleOption(analyzerOptions.GetAnalyzerOptions(syntaxTree)); | ||
var severity = UseExpressionBodyForLambdaHelpers.GetOptionSeverity(optionValue); | ||
switch (severity) | ||
{ | ||
case ReportDiagnostic.Error: | ||
case ReportDiagnostic.Warn: | ||
case ReportDiagnostic.Info: | ||
break; | ||
default: | ||
// don't analyze if it's any other value. | ||
return; | ||
} | ||
|
||
AnalyzeSyntax(context, optionValue); | ||
} | ||
|
||
private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOption2<ExpressionBodyPreference> option) | ||
{ | ||
var declaration = (LambdaExpressionSyntax)context.Node; | ||
var diagnostic = AnalyzeSyntax(context.SemanticModel, option, declaration, context.CancellationToken); | ||
if (diagnostic != null) | ||
{ | ||
context.ReportDiagnostic(diagnostic); | ||
} | ||
} | ||
|
||
private static Diagnostic? AnalyzeSyntax( | ||
SemanticModel semanticModel, CodeStyleOption2<ExpressionBodyPreference> option, | ||
LambdaExpressionSyntax declaration, CancellationToken cancellationToken) | ||
{ | ||
if (UseExpressionBodyForLambdaHelpers.CanOfferUseExpressionBody(option.Value, declaration, declaration.GetLanguageVersion())) | ||
{ | ||
var location = GetDiagnosticLocation(declaration); | ||
|
||
var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); | ||
var properties = ImmutableDictionary<string, string?>.Empty; | ||
return DiagnosticHelper.Create( | ||
s_useExpressionBodyForLambda, | ||
location, option.Notification.Severity, additionalLocations, properties); | ||
} | ||
|
||
if (UseExpressionBodyForLambdaHelpers.CanOfferUseBlockBody(semanticModel, option.Value, declaration, cancellationToken)) | ||
{ | ||
// They have an expression body. Create a diagnostic to convert it to a block | ||
// if they don't want expression bodies for this member. | ||
var location = GetDiagnosticLocation(declaration); | ||
|
||
var properties = ImmutableDictionary<string, string?>.Empty; | ||
var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); | ||
return DiagnosticHelper.Create( | ||
s_useBlockBodyForLambda, | ||
location, option.Notification.Severity, additionalLocations, properties); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private static Location GetDiagnosticLocation(LambdaExpressionSyntax declaration) | ||
=> Location.Create(declaration.SyntaxTree, | ||
TextSpan.FromBounds(declaration.SpanStart, declaration.ArrowToken.Span.End)); | ||
|
||
private static DiagnosticDescriptor CreateDescriptorWithId( | ||
LocalizableString title, LocalizableString message) | ||
{ | ||
return CreateDescriptorWithId(IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId, EnforceOnBuildValues.UseExpressionBodyForLambdaExpressions, title, message); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// 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.Diagnostics.CodeAnalysis; | ||
using System.Threading; | ||
using Microsoft.CodeAnalysis.CodeStyle; | ||
using Microsoft.CodeAnalysis.CSharp.Extensions; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda | ||
{ | ||
internal static class UseExpressionBodyForLambdaHelpers | ||
{ | ||
internal static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_expression_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); | ||
internal static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_block_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); | ||
|
||
internal static bool CanOfferUseBlockBody( | ||
SemanticModel semanticModel, ExpressionBodyPreference preference, | ||
LambdaExpressionSyntax declaration, CancellationToken cancellationToken) | ||
{ | ||
var userPrefersBlockBodies = preference == ExpressionBodyPreference.Never; | ||
if (!userPrefersBlockBodies) | ||
{ | ||
// If the user doesn't even want block bodies, then certainly do not offer. | ||
return false; | ||
} | ||
|
||
var expressionBodyOpt = GetBodyAsExpression(declaration); | ||
if (expressionBodyOpt == null) | ||
{ | ||
// they already have a block body. | ||
return false; | ||
} | ||
|
||
// We need to know what sort of lambda this is (void returning or not) in order to be | ||
// able to create the right sort of block body (i.e. with a return-statement or | ||
// expr-statement). So, if we can't figure out what lambda type this is, we should not | ||
// proceed. | ||
if (semanticModel.GetTypeInfo(declaration, cancellationToken).ConvertedType is not INamedTypeSymbol lambdaType || lambdaType.DelegateInvokeMethod == null) | ||
{ | ||
return false; | ||
} | ||
|
||
var canOffer = expressionBodyOpt.TryConvertToStatement( | ||
semicolonTokenOpt: null, createReturnStatementForExpression: false, out _); | ||
if (!canOffer) | ||
{ | ||
// Couldn't even convert the expression into statement form. | ||
return false; | ||
} | ||
|
||
var languageVersion = declaration.SyntaxTree.Options.LanguageVersion(); | ||
if (expressionBodyOpt.IsKind(SyntaxKind.ThrowExpression) && | ||
languageVersion < LanguageVersion.CSharp7) | ||
{ | ||
// Can't convert this prior to C# 7 because ```a => throw ...``` isn't allowed. | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
internal static bool CanOfferUseExpressionBody( | ||
ExpressionBodyPreference preference, LambdaExpressionSyntax declaration, LanguageVersion languageVersion) | ||
{ | ||
var userPrefersExpressionBodies = preference != ExpressionBodyPreference.Never; | ||
if (!userPrefersExpressionBodies) | ||
{ | ||
// If the user doesn't even want expression bodies, then certainly do not offer. | ||
return false; | ||
} | ||
|
||
var expressionBody = GetBodyAsExpression(declaration); | ||
if (expressionBody != null) | ||
{ | ||
// they already have an expression body. so nothing to do here. | ||
return false; | ||
} | ||
|
||
// They don't have an expression body. See if we could convert the block they | ||
// have into one. | ||
return TryConvertToExpressionBody(declaration, languageVersion, preference, out _); | ||
} | ||
|
||
internal static ExpressionSyntax? GetBodyAsExpression(LambdaExpressionSyntax declaration) | ||
=> declaration.Body as ExpressionSyntax; | ||
|
||
internal static CodeStyleOption2<ExpressionBodyPreference> GetCodeStyleOption(AnalyzerOptionsProvider provider) | ||
=> ((CSharpAnalyzerOptionsProvider)provider).PreferExpressionBodiedLambdas; | ||
|
||
/// <summary> | ||
/// Helper to get the true ReportDiagnostic severity for a given option. Importantly, this | ||
/// handle ReportDiagnostic.Default and will map that back to the appropriate value in that | ||
/// case. | ||
/// </summary> | ||
internal static ReportDiagnostic GetOptionSeverity(CodeStyleOption2<ExpressionBodyPreference> optionValue) | ||
{ | ||
var severity = optionValue.Notification.Severity; | ||
return severity == ReportDiagnostic.Default | ||
? severity.WithDefaultSeverity(DiagnosticSeverity.Hidden) | ||
: severity; | ||
} | ||
|
||
internal static bool TryConvertToExpressionBody( | ||
LambdaExpressionSyntax declaration, | ||
LanguageVersion languageVersion, | ||
ExpressionBodyPreference conversionPreference, | ||
[NotNullWhen(true)] out ExpressionSyntax? expression) | ||
{ | ||
var body = declaration.Body as BlockSyntax; | ||
|
||
return body.TryConvertToExpressionBody(languageVersion, conversionPreference, out expression, out _); | ||
} | ||
} | ||
} |
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.