|
// 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.
#nullable disable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod;
using static SyntaxFactory;
internal sealed partial class CSharpMethodExtractor
{
private sealed class PostProcessor
{
private readonly SemanticModel _semanticModel;
private readonly int _contextPosition;
public PostProcessor(SemanticModel semanticModel, int contextPosition)
{
Contract.ThrowIfNull(semanticModel);
_semanticModel = semanticModel;
_contextPosition = contextPosition;
}
public static ImmutableArray<StatementSyntax> RemoveRedundantBlock(ImmutableArray<StatementSyntax> statements)
{
// it must have only one statement
if (statements.Count() != 1)
{
return statements;
}
// that statement must be a block
if (statements.Single() is not BlockSyntax block)
{
return statements;
}
// we have a block, remove them
return RemoveRedundantBlock(block);
}
private static ImmutableArray<StatementSyntax> RemoveRedundantBlock(BlockSyntax block)
{
// if block doesn't have any statement
if (block.Statements.Count == 0)
{
// either remove the block if it doesn't have any trivia, or return as it is if
// there are trivia attached to block
return (block.OpenBraceToken.GetAllTrivia().IsEmpty() && block.CloseBraceToken.GetAllTrivia().IsEmpty())
? []
: [block];
}
// okay transfer asset attached to block to statements
var firstStatement = block.Statements.First();
var firstToken = firstStatement.GetFirstToken(includeZeroWidth: true);
var firstTokenWithAsset = block.OpenBraceToken.CopyAnnotationsTo(firstToken).WithPrependedLeadingTrivia(block.OpenBraceToken.GetAllTrivia());
var lastStatement = block.Statements.Last();
var lastToken = lastStatement.GetLastToken(includeZeroWidth: true);
var lastTokenWithAsset = block.CloseBraceToken.CopyAnnotationsTo(lastToken).WithAppendedTrailingTrivia(block.CloseBraceToken.GetAllTrivia());
// create new block with new tokens
block = block.ReplaceTokens([firstToken, lastToken], (o, c) => (o == firstToken) ? firstTokenWithAsset : lastTokenWithAsset);
// return only statements without the wrapping block
return ImmutableArray.CreateRange(block.Statements);
}
public ImmutableArray<StatementSyntax> MergeDeclarationStatements(ImmutableArray<StatementSyntax> statements)
{
if (statements.FirstOrDefault() == null)
{
return statements;
}
return MergeDeclarationStatementsWorker(statements);
}
private ImmutableArray<StatementSyntax> MergeDeclarationStatementsWorker(ImmutableArray<StatementSyntax> statements)
{
using var _ = ArrayBuilder<StatementSyntax>.GetInstance(out var result);
var map = new Dictionary<ITypeSymbol, List<LocalDeclarationStatementSyntax>>();
foreach (var statement in statements)
{
if (!IsDeclarationMergable(statement))
{
result.AddRange(GetMergedDeclarationStatements(map));
result.Add(statement);
continue;
}
AppendDeclarationStatementToMap(statement as LocalDeclarationStatementSyntax, map);
}
// merge leftover
if (map.Count > 0)
result.AddRange(GetMergedDeclarationStatements(map));
return result.ToImmutableAndClear();
}
private void AppendDeclarationStatementToMap(
LocalDeclarationStatementSyntax statement,
Dictionary<ITypeSymbol, List<LocalDeclarationStatementSyntax>> map)
{
Contract.ThrowIfNull(statement);
var type = _semanticModel.GetSpeculativeTypeInfo(_contextPosition, statement.Declaration.Type, SpeculativeBindingOption.BindAsTypeOrNamespace).Type;
Contract.ThrowIfNull(type);
map.GetOrAdd(type, _ => []).Add(statement);
}
private static IEnumerable<LocalDeclarationStatementSyntax> GetMergedDeclarationStatements(
Dictionary<ITypeSymbol, List<LocalDeclarationStatementSyntax>> map)
{
foreach (var keyValuePair in map)
{
Contract.ThrowIfFalse(keyValuePair.Value.Count > 0);
// merge all variable decl for current type
var variables = new List<VariableDeclaratorSyntax>();
foreach (var statement in keyValuePair.Value)
{
foreach (var variable in statement.Declaration.Variables)
{
variables.Add(variable);
}
}
// and create one decl statement
// use type name from the first decl statement
yield return
LocalDeclarationStatement(
VariableDeclaration(keyValuePair.Value.First().Declaration.Type, [.. variables]));
}
map.Clear();
}
private bool IsDeclarationMergable(StatementSyntax statement)
{
Contract.ThrowIfNull(statement);
// to be mergable, statement must be
// 1. decl statement without any extra info
// 2. no initialization on any of its decls
// 3. no trivia except whitespace
// 4. type must be known
if (statement is not LocalDeclarationStatementSyntax declarationStatement)
{
return false;
}
if (declarationStatement.Modifiers.Count > 0 ||
declarationStatement.IsConst ||
declarationStatement.IsMissing)
{
return false;
}
if (ContainsAnyInitialization(declarationStatement))
{
return false;
}
if (!ContainsOnlyWhitespaceTrivia(declarationStatement))
{
return false;
}
var semanticInfo = _semanticModel.GetSpeculativeTypeInfo(_contextPosition, declarationStatement.Declaration.Type, SpeculativeBindingOption.BindAsTypeOrNamespace).Type;
if (semanticInfo == null ||
semanticInfo.TypeKind is TypeKind.Error or TypeKind.Unknown)
{
return false;
}
return true;
}
private static bool ContainsAnyInitialization(LocalDeclarationStatementSyntax statement)
{
foreach (var variable in statement.Declaration.Variables)
{
if (variable.Initializer != null)
{
return true;
}
}
return false;
}
private static bool ContainsOnlyWhitespaceTrivia(StatementSyntax statement)
{
foreach (var token in statement.DescendantTokens())
{
foreach (var trivia in token.LeadingTrivia.Concat(token.TrailingTrivia))
{
if (trivia.Kind() is not SyntaxKind.WhitespaceTrivia and
not SyntaxKind.EndOfLineTrivia)
{
return false;
}
}
}
return true;
}
public static ImmutableArray<StatementSyntax> RemoveInitializedDeclarationAndReturnPattern(ImmutableArray<StatementSyntax> statements)
{
// if we have inline temp variable as service, we could just use that service here.
// since it is not a service right now, do very simple clean up
if (statements.ElementAtOrDefault(2) != null)
{
return statements;
}
if (statements.ElementAtOrDefault(0) is not LocalDeclarationStatementSyntax declaration || statements.ElementAtOrDefault(1) is not ReturnStatementSyntax returnStatement)
{
return statements;
}
if (declaration.Declaration == null ||
declaration.Declaration.Variables.Count != 1 ||
declaration.Declaration.Variables[0].Initializer == null ||
declaration.Declaration.Variables[0].Initializer.Value == null ||
declaration.Declaration.Variables[0].Initializer.Value is StackAllocArrayCreationExpressionSyntax ||
returnStatement.Expression == null)
{
return statements;
}
if (!ContainsOnlyWhitespaceTrivia(declaration) ||
!ContainsOnlyWhitespaceTrivia(returnStatement))
{
return statements;
}
var variableName = declaration.Declaration.Variables[0].Identifier.ToString();
if (returnStatement.Expression.ToString() != variableName)
{
return statements;
}
return [ReturnStatement(declaration.Declaration.Variables[0].Initializer.Value)];
}
public static ImmutableArray<StatementSyntax> RemoveDeclarationAssignmentPattern(ImmutableArray<StatementSyntax> statements)
{
if (statements.ElementAtOrDefault(0) is not LocalDeclarationStatementSyntax declaration || statements.ElementAtOrDefault(1) is not ExpressionStatementSyntax assignment)
{
return statements;
}
if (ContainsAnyInitialization(declaration) ||
declaration.Declaration == null ||
declaration.Declaration.Variables.Count != 1 ||
assignment.Expression == null ||
assignment.Expression.Kind() != SyntaxKind.SimpleAssignmentExpression)
{
return statements;
}
if (!ContainsOnlyWhitespaceTrivia(declaration) ||
!ContainsOnlyWhitespaceTrivia(assignment))
{
return statements;
}
var variableName = declaration.Declaration.Variables[0].Identifier.ToString();
var assignmentExpression = assignment.Expression as AssignmentExpressionSyntax;
if (assignmentExpression.Left == null ||
assignmentExpression.Right == null ||
assignmentExpression.Left.ToString() != variableName)
{
return statements;
}
var variable = declaration.Declaration.Variables[0].WithInitializer(EqualsValueClause(assignmentExpression.Right));
return
[
declaration.WithDeclaration(
declaration.Declaration.WithVariables([variable])),
.. statements.Skip(2),
];
}
}
}
|