File: SyntaxRewriter\RemoveBodySyntaxRewriter.cs
Web Access
Project: ..\..\..\src\Compatibility\ApiDiff\Microsoft.DotNet.ApiDiff\Microsoft.DotNet.ApiDiff.csproj (Microsoft.DotNet.ApiDiff)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.DotNet.ApiDiff.SyntaxRewriter;
 
/// <summary>
/// Replaces the bodies of nodes that have one (methods, properties, events, etc.) with a semicolon.
/// </summary>
internal class RemoveBodyCSharpSyntaxRewriter : CSharpSyntaxRewriter
{
    public static readonly RemoveBodyCSharpSyntaxRewriter Singleton = new();
 
    private readonly SyntaxToken _semiColonToken = SyntaxFactory.Token(SyntaxKind.SemicolonToken);
    private readonly SyntaxToken _noneToken = SyntaxFactory.Token(SyntaxKind.None);
    private readonly SyntaxTriviaList _endLineTrivia = SyntaxFactory.TriviaList((Environment.NewLine == "\r\n") ? SyntaxFactory.CarriageReturnLineFeed : SyntaxFactory.LineFeed);
 
    public override SyntaxNode? VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
    {
        var result = node
                .WithBody(null) // remove the default empty body wrapped by brackets
                .WithoutLeadingTrivia()
                .WithoutTrailingTrivia() // Remove the single space that follows this new declaration
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
                .WithLeadingTrivia(node.GetLeadingTrivia())
                .WithTrailingTrivia(node.GetTrailingTrivia());
        return base.VisitConstructorDeclaration(result);
    }
 
    // These bad boys look like: 'public static explicit operator int(MyClass value)'
    public override SyntaxNode? VisitConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax node)
    {
        var result = node
                .WithBody(null) // remove the default empty body wrapped by brackets
                .WithoutLeadingTrivia()
                .WithoutTrailingTrivia() // Remove the single space that follows this new declaration
                .WithSemicolonToken(_semiColonToken)
                .WithLeadingTrivia(node.GetLeadingTrivia())
                .WithTrailingTrivia(node.GetTrailingTrivia());
        return base.VisitConversionOperatorDeclaration(result);
    }
 
    public override SyntaxNode? VisitDestructorDeclaration(DestructorDeclarationSyntax node)
    {
        var result = node
                .WithBody(null) // remove the default empty body wrapped by brackets
                .WithoutLeadingTrivia()
                .WithoutTrailingTrivia() // Remove the single space that follows this new declaration
                .WithSemicolonToken(_semiColonToken)
                .WithLeadingTrivia(node.GetLeadingTrivia())
                .WithTrailingTrivia(node.GetTrailingTrivia());
        return base.VisitDestructorDeclaration(result);
    }
 
    public override SyntaxNode? VisitEventDeclaration(EventDeclarationSyntax node)
    {
        var result = node
            .WithIdentifier(node.Identifier.WithoutTrivia())
            .WithAccessorList(GetEmptiedAccessors(node.AccessorList));
        return base.VisitEventDeclaration(result);
    }
 
    public override SyntaxNode? VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        var result = node
                .WithBody(null) // remove the default empty body wrapped by brackets
                .WithoutLeadingTrivia()
                .WithoutTrailingTrivia() // Remove the single space that follows this new declaration
                .WithSemicolonToken(_semiColonToken)
                .WithLeadingTrivia(node.GetLeadingTrivia())
                .WithTrailingTrivia(node.GetTrailingTrivia());
        return base.VisitMethodDeclaration(result);
    }
 
    public override SyntaxNode? VisitOperatorDeclaration(OperatorDeclarationSyntax node)
    {
        if (node.OperatorToken.IsKind(SyntaxKind.GreaterThanToken))
        {
            // Takes care of the missing space before the greater than
            node = node.WithOperatorToken(SyntaxFactory.Token(SyntaxFactory.TriviaList(SyntaxFactory.Space), node.OperatorToken.Kind(), SyntaxTriviaList.Empty));
        }
        var result = node
                .WithBody(null) // remove the default empty body wrapped by brackets
                .WithoutLeadingTrivia()
                .WithoutTrailingTrivia() // Remove the single space that follows this new declaration
                .WithSemicolonToken(_semiColonToken)
                .WithLeadingTrivia(node.GetLeadingTrivia())
                .WithTrailingTrivia(node.GetTrailingTrivia());
        return base.VisitOperatorDeclaration(result);
    }
 
    public override SyntaxNode? VisitPropertyDeclaration(PropertyDeclarationSyntax node)
    {
        var result = node.WithAccessorList(GetEmptiedAccessors(node.AccessorList));
        return base.VisitPropertyDeclaration(result);
    }
 
    public override SyntaxNode? VisitRecordDeclaration(RecordDeclarationSyntax node)
    {
        if (node.Members.Any())
        {
            // Fix the spaces and lack of newline
            var replacedOpenBrace = SyntaxFactory.Token(_endLineTrivia.AddRange(node.GetLeadingTrivia()), SyntaxKind.OpenBraceToken, node.OpenBraceToken.TrailingTrivia);
            node = node.WithOpenBraceToken(replacedOpenBrace);
        }
        else
        {
            // Replace braces with semicolon
            node = node.WithOpenBraceToken(_noneToken)
                       .WithCloseBraceToken(_noneToken)
                       .WithSemicolonToken(_semiColonToken);
        }
 
        if (node.ParameterList != null && node.ParameterList.Parameters.Any())
        {
            // Fix the extra space
            node = node.WithParameterList(node.ParameterList.WithoutTrailingTrivia());
        }
        else
        {
            // Remove the parentheses if there are no arguments
            node = node.WithParameterList(null);
        }
 
        return base.VisitRecordDeclaration(node);
    }
 
    private AccessorListSyntax? GetEmptiedAccessors(AccessorListSyntax? accessorList)
    {
        if (accessorList == null)
        {
            return null;
        }
 
        List<AccessorDeclarationSyntax> newAccessors = new();
 
 
        for (int i = 0; i < accessorList.Accessors.Count; i++)
        {
            AccessorDeclarationSyntax accessorDeclaration = accessorList.Accessors[i]
                               .WithBody(null) // remove the default empty body wrapped by brackets
                               .WithoutTrivia() // Important
                               .WithLeadingTrivia(SyntaxFactory.Space) // Add a space before the accessor
                               .WithSemicolonToken(_semiColonToken); // Append a semicolon at the end
 
            if (i == accessorList.Accessors.Count - 1) // Second to last
            {
                // Add a space after the semicolon only on the last accessor
                accessorDeclaration = accessorDeclaration.WithTrailingTrivia(SyntaxFactory.Space);
            }
 
            newAccessors.Add(accessorDeclaration);
        }
 
        return SyntaxFactory.AccessorList(SyntaxFactory.List(newAccessors)).WithLeadingTrivia(SyntaxFactory.Space);
    }
 
}