Skip to content

Commit

Permalink
Merge pull request #62675 from Youssef1313/use-expression-body-lambda
Browse files Browse the repository at this point in the history
Move UseExpressionBodyForLambda to Analyzers layer
  • Loading branch information
mavasani authored Jul 21, 2022
2 parents deabaa7 + d162892 commit 8a7b8df
Show file tree
Hide file tree
Showing 45 changed files with 637 additions and 961 deletions.
2 changes: 2 additions & 0 deletions src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
<Compile Include="$(MSBuildThisFileDirectory)UseConditionalExpression\CSharpUseConditionalExpressionForReturnDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseDeconstruction\CSharpUseDeconstructionDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseDefaultLiteral\CSharpUseDefaultLiteralDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseExpressionBodyForLambda\UseExpressionBodyForLambdaDiagnosticAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseExpressionBodyForLambda\UseExpressionBodyForLambdaHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseExpressionBody\Helpers\UseExpressionBodyForAccessorsHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseExpressionBody\Helpers\UseExpressionBodyForConstructorsHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseExpressionBody\Helpers\UseExpressionBodyForConversionOperatorsHelper.cs" />
Expand Down
6 changes: 6 additions & 0 deletions src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,10 @@
<data name="Remove_redundant_nullable_directive" xml:space="preserve">
<value>Remove redundant nullable directive</value>
</data>
<data name="Use_expression_body_for_lambda_expressions" xml:space="preserve">
<value>Use expression body for lambda expressions</value>
</data>
<data name="Use_block_body_for_lambda_expressions" xml:space="preserve">
<value>Use block body for lambda expressions</value>
</data>
</root>
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 _);
}
}
}
10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8a7b8df

Please sign in to comment.