File: src\Analyzers\CSharp\CodeFixes\UseExpressionBodyForLambda\UseExpressionBodyForLambdaCodeActionHelpers.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// 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 System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.UseExpressionBodyForLambda;
 
using static CSharpSyntaxTokens;
using static SyntaxFactory;
 
internal static class UseExpressionBodyForLambdaCodeActionHelpers
{
    internal static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, CancellationToken cancellationToken)
        => UpdateWorker(semanticModel, originalDeclaration, currentDeclaration, cancellationToken).WithAdditionalAnnotations(Formatter.Annotation);
 
    private static LambdaExpressionSyntax UpdateWorker(
        SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, CancellationToken cancellationToken)
    {
        var expressionBody = UseExpressionBodyForLambdaHelpers.GetBodyAsExpression(currentDeclaration);
        return expressionBody == null
            ? WithExpressionBody(currentDeclaration, originalDeclaration.GetLanguageVersion(), cancellationToken)
            : WithBlockBody(semanticModel, originalDeclaration, currentDeclaration, expressionBody);
    }
 
    private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax declaration, LanguageVersion languageVersion, CancellationToken cancellationToken)
    {
        if (!UseExpressionBodyForLambdaHelpers.TryConvertToExpressionBody(
                declaration, languageVersion, ExpressionBodyPreference.WhenPossible, cancellationToken, out var expressionBody))
        {
            return declaration;
        }
 
        var updatedDecl = declaration.WithBody(expressionBody);
 
        // If there will only be whitespace between the arrow and the body, then replace that
        // with a single space so that the lambda doesn't have superfluous newlines in it.
        if (declaration.ArrowToken.TrailingTrivia.All(t => t.IsWhitespaceOrEndOfLine()) &&
            expressionBody.GetLeadingTrivia().All(t => t.IsWhitespaceOrEndOfLine()))
        {
            updatedDecl = updatedDecl.WithArrowToken(updatedDecl.ArrowToken.WithTrailingTrivia(ElasticSpace));
        }
 
        return updatedDecl;
    }
 
    private static LambdaExpressionSyntax WithBlockBody(
        SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, ExpressionSyntax expressionBody)
    {
        var createReturnStatementForExpression = CreateReturnStatementForExpression(
            semanticModel, originalDeclaration);
 
        if (!expressionBody.TryConvertToStatement(
                semicolonTokenOpt: null,
                createReturnStatementForExpression,
                out var statement))
        {
            return currentDeclaration;
        }
 
        // If the user is converting to a block, it's likely they intend to add multiple
        // statements to it.  So make a multi-line block so that things are formatted properly
        // for them to do so.
        return currentDeclaration.WithBody(Block(
            OpenBraceToken.WithAppendedTrailingTrivia(ElasticCarriageReturnLineFeed),
            [statement],
            CloseBraceToken));
    }
 
    private static bool CreateReturnStatementForExpression(
        SemanticModel semanticModel, LambdaExpressionSyntax declaration)
    {
        var lambdaType = (INamedTypeSymbol)semanticModel.GetTypeInfo(declaration).ConvertedType!;
        if (lambdaType.DelegateInvokeMethod!.ReturnsVoid)
        {
            return false;
        }
 
        // 'async Task' is effectively a void-returning lambda.  we do not want to create 
        // 'return statements' when converting.
        if (declaration.AsyncKeyword != default)
        {
            var returnType = lambdaType.DelegateInvokeMethod.ReturnType;
            if (returnType.IsErrorType())
            {
                // "async Goo" where 'Goo' failed to bind.  If 'Goo' is 'Task' then it's
                // reasonable to assume this is just a missing 'using' and that this is a true
                // "async Task" lambda.  If the name isn't 'Task', then this looks like a
                // real return type, and we should use return statements.
                return returnType.Name != nameof(Task);
            }
 
            var taskType = semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName!);
            if (returnType.Equals(taskType))
            {
                // 'async Task'.  definitely do not create a 'return' statement;
                return false;
            }
        }
 
        return true;
    }
}