如何使用 Roslyn 从源代码中删除基类型,同时在声明末尾保留换行符?

How to remove base type from the source code using Roslyn while preserving the newline at the end of the declaration?

我使用以下 class 删除基本类型:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;

namespace CSTool.Rewriters
{
    public class BaseTypeNameRemover : CSharpSyntaxRewriter
    {
        private readonly string m_typeName;
        private readonly string m_baseOldTypeName;
        public bool Changed { get; private set; }

        public BaseTypeNameRemover(string typeName, string baseOldTypeName)
        {
            m_typeName = typeName;
            m_baseOldTypeName = baseOldTypeName;
        }

        public override SyntaxNode VisitBaseList(BaseListSyntax node)
        {
            var trailingTrivia = node.GetTrailingTrivia();
            node = (BaseListSyntax)base.VisitBaseList(node);
            if (!node.ChildNodes().Any())
            {
                return null;
            }
            if (trailingTrivia == null || node.HasTrailingTrivia)
            {
                return node;
            }
            return node.WithTrailingTrivia(trailingTrivia);
        }

        public override SyntaxNode VisitSimpleBaseType(SimpleBaseTypeSyntax node)
        {
            if (node.ToString() != m_baseOldTypeName)
            {
                return node;
            }
            
            Changed = true;
            return null;
        }

        public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) =>
            node.Parent is BaseTypeDeclarationSyntax || node.Identifier.Text != m_typeName
            ? node
            : base.VisitClassDeclaration(node);

        public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) =>
            node.Parent is BaseTypeDeclarationSyntax || node.Identifier.Text != m_typeName
            ? node
            : base.VisitInterfaceDeclaration(node);
    }
}

但是,我无法在声明后保留换行符。假设这是输入源代码:

namespace xyz
{
    interface IInterface : SomeClass
    {
    }
}

在运行之后通过我的BaseTypeNameRemover变成:

namespace xyz
{
    interface IInterface    {
    }
}

但是我想保留换行符!

通常删除基类型是很痛苦的,因为我必须同时覆盖 VisitSimpleBaseTypeVisitBaseList。而且我不知道如何保留换行符。简而言之 - 混乱且不准确。

在保留换行符的同时彻底删除基类型的正确方法是什么?

编辑 1

当基类型超过 1 种时,我的代码就会出错。我真的不明白如何用 CSharpSyntaxRewriter

惯用地做

ִ我已经受够了想弄清楚该怎么做。最后退回到暴力文本操作:

if (node.BaseList == null)
{
    continue;
}

var index = node.BaseList.Types.IndexOf(o => o.ToString() == simpleOldBaseTypeName);
if (index < 0)
{
    continue;
}

var text = File.ReadAllText(filePath);
var found = node.BaseList.Types[index];
var start = found.SpanStart;
var end = found.Span.End;
if (node.BaseList.Types.Count == 1 || index > 0)
{
    char stopChar = index == 0 ? ':' : ',';
    while (char.IsWhiteSpace(text[--start]))
    {
    }
    if (text[start] != stopChar)
    {
        throw new ApplicationException($"Failed to parse the base type list in {filePath}");
    }
    while (char.IsWhiteSpace(text[start - 1]))
    {
        --start;
    }
}
else
{
    while (char.IsWhiteSpace(text[end]))
    {
        ++end;
    }
    if (text[end] != ',')
    {
        throw new ApplicationException($"Failed to parse the base type list in {filePath}");
    }
    while (char.IsWhiteSpace(text[++end]))
    {
    }
}

text = text.Remove(start, end - start);
File.WriteAllText(filePath, text);

不理想,但对我来说足够好了。

我今天也遇到了这个问题,解决方案是修改外部ClassDeclarationSyntax的标识符,从基本列表中添加尾随琐事,然后将基本列表设置为null。这是完成这项工作的扩展方法:

    /// <summary>
    /// Removes the specified base type from a Class node.
    /// </summary>
    /// <param name="node">The <see cref="ClassDeclarationSyntax"/> node that will be modified.</param>
    /// <param name="typeName">The name of the type to be removed.</param>
    /// <returns>An updated <see cref="ClassDeclarationSyntax"/> node.</returns>
    public static ClassDeclarationSyntax RemoveBaseType(this ClassDeclarationSyntax node, string typeName)
    {
        var baseType = node.BaseList?.Types.FirstOrDefault(x => string.Equals(x.ToString(), typeName, StringComparison.OrdinalIgnoreCase));
        if (baseType == null)
        {
            // Base type not found
            return node;
        }

        var baseTypes = node.BaseList!.Types.Remove(baseType);
        if (baseTypes.Count == 0)
        {
            // No more base implementations, remove the base list entirely
            // Make sure we update the identifier though to include the baselist trailing trivia (typically '\r\n')
            // so the trailing opening brace gets put onto a new line.
            return node
                .WithBaseList(null)
                .WithIdentifier(node.Identifier.WithTrailingTrivia(node.BaseList.GetTrailingTrivia()));
        }
        else
        {
            // Remove the type but retain all remaining types and trivia
            return node.WithBaseList(node.BaseList!.WithTypes(baseTypes));
        }
    }