| File: CodeRefactorings\InlineMethod\CSharpInlineMethodRefactoringProvider.cs | Web Access |
| Project: src\roslyn\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; using System.Composition; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.InlineMethod; namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.InlineMethod; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InlineMethod), Shared] [Export(typeof(CSharpInlineMethodRefactoringProvider))] internal sealed class CSharpInlineMethodRefactoringProvider : AbstractInlineMethodRefactoringProvider<BaseMethodDeclarationSyntax, StatementSyntax, ExpressionSyntax, InvocationExpressionSyntax> { [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public CSharpInlineMethodRefactoringProvider() : base(CSharpSyntaxFacts.Instance, CSharpSemanticFactsService.Instance) { } protected override ExpressionSyntax? GetRawInlineExpression(BaseMethodDeclarationSyntax methodDeclarationSyntax) { var blockSyntaxNode = methodDeclarationSyntax.Body; if (blockSyntaxNode != null) { // 1. If it is an ordinary method with block if (blockSyntaxNode.Statements is [var statementSyntax]) { return statementSyntax switch { // Note: For this case this will return null in Callee() // void Caller() { Callee(); } // void Callee() { return; } // Refactoring won't be provided for this case. ReturnStatementSyntax returnStatementSyntax => returnStatementSyntax.Expression, ExpressionStatementSyntax expressionStatementSyntax => expressionStatementSyntax.Expression, ThrowStatementSyntax throwStatementSyntax => throwStatementSyntax.Expression, _ => null }; } } else { // 2. If it is an Arrow Expression return methodDeclarationSyntax.ExpressionBody?.Expression; } return null; } protected override SyntaxNode GenerateTypeSyntax(ITypeSymbol symbol, bool allowVar) => symbol.GenerateTypeSyntax(allowVar); protected override ExpressionSyntax GenerateLiteralExpression(ITypeSymbol typeSymbol, object? value) => ExpressionGenerator.GenerateExpression(typeSymbol, value, canUseFieldReference: true); protected override bool IsFieldDeclarationSyntax(SyntaxNode node) => node.IsKind(SyntaxKind.FieldDeclaration); protected override bool IsValidExpressionUnderExpressionStatement(ExpressionSyntax expressionNode) { // C# Expression Statements defined in the language reference // expression_statement // : statement_expression ';' // ; // // statement_expression // : invocation_expression // | null_conditional_invocation_expression // | object_creation_expression // | assignment // | post_increment_expression // | post_decrement_expression // | pre_increment_expression // | pre_decrement_expression // | await_expression // ; var isNullConditionalInvocationExpression = IsNullConditionalInvocationExpression(expressionNode); return isNullConditionalInvocationExpression || expressionNode is AssignmentExpressionSyntax || expressionNode.Kind() is SyntaxKind.InvocationExpression or SyntaxKind.ObjectCreationExpression or SyntaxKind.PreIncrementExpression or SyntaxKind.PreDecrementExpression or SyntaxKind.PostIncrementExpression or SyntaxKind.PostDecrementExpression or SyntaxKind.AwaitExpression; } protected override bool CanBeReplacedByThrowExpression(SyntaxNode syntaxNode) { // C# Throw Expression definition in language reference: // 'A throw expression is permitted in only the following syntactic contexts: // As the second or third operand of a ternary conditional operator ?: // As the second operand of a null coalescing operator ?? // As the body of an expression-bodied lambda or method.' return syntaxNode.Parent switch { ConditionalExpressionSyntax conditionalExpressionSyntax => syntaxNode.Equals(conditionalExpressionSyntax.WhenTrue) || syntaxNode.Equals(conditionalExpressionSyntax.WhenFalse), BinaryExpressionSyntax(kind: SyntaxKind.CoalesceExpression) binaryExpressionSyntax => syntaxNode.Equals(binaryExpressionSyntax.Right), LambdaExpressionSyntax lambdaExpressionSyntax => lambdaExpressionSyntax.ExpressionBody != null, var parent => parent.IsKind(SyntaxKind.ArrowExpressionClause), }; } private static bool IsNullConditionalInvocationExpression(ExpressionSyntax expressionSyntax) { // Check if the expression syntax is like an invocation expression nested inside ConditionalAccessExpressionSyntax. // For example: a?.b.c() // // If the expression is ended with an invocation // (if the expressions in the middle are not ConditionalAccessExpressionSyntax), // like a?.b.e.c(), the syntax tree would be // ConditionalAccessExpressionSyntax -> InvocationExpression. // And in case of example like a?.b?.d?.c(); // This is case it would be // ConditionalAccessExpressionSyntax -> ConditionalAccessExpressionSyntax -> ... -> InvocationExpression. return expressionSyntax is ConditionalAccessExpressionSyntax { WhenNotNull: var whenNotNull } && (whenNotNull.IsKind(SyntaxKind.InvocationExpression) || IsNullConditionalInvocationExpression(whenNotNull)); } }