如何使用 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 {
}
}
但是我想保留换行符!
通常删除基类型是很痛苦的,因为我必须同时覆盖 VisitSimpleBaseType
和 VisitBaseList
。而且我不知道如何保留换行符。简而言之 - 混乱且不准确。
在保留换行符的同时彻底删除基类型的正确方法是什么?
编辑 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));
}
}
我使用以下 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 {
}
}
但是我想保留换行符!
通常删除基类型是很痛苦的,因为我必须同时覆盖 VisitSimpleBaseType
和 VisitBaseList
。而且我不知道如何保留换行符。简而言之 - 混乱且不准确。
在保留换行符的同时彻底删除基类型的正确方法是什么?
编辑 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));
}
}