在Roslyn代码修复中条件添加using语句及程序集引用检测问题
检测程序集引用并调整Roslyn代码修复逻辑
我刚好做过类似的Roslyn分析器场景,这个检测程序集引用的问题其实可以通过利用Compilation对象检查目标类型是否存在来解决,比直接遍历程序集引用更可靠,因为它能直接反映类型是否可被当前项目访问。
核心思路
Roslyn的Compilation对象包含了当前项目所有可访问的类型和程序集信息。我们可以通过查找Trilogy.Miscellaneous.StringExtensions这个类型是否存在,来间接判断项目是否引用了Trilogy.Common程序集:
- 如果类型存在:说明项目已引用,继续使用
StringExtensions.SafeJoin的修复逻辑,同时自动添加using。 - 如果类型不存在:说明未引用,要么不提供该修复,要么切换到
string.Join的备选方案。
具体实现步骤
在你的代码修复提供者(CodeFixProvider)的RegisterCodeFixesAsync方法中,加入以下逻辑:
- 获取当前文档的
SemanticModel和Compilation对象:
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); var compilation = semanticModel.Compilation;
- 检查目标类型是否存在:
// 注意这里要用完全限定的元数据名称(命名空间+类型名) var stringExtensionsType = compilation.GetTypeByMetadataName("Trilogy.Miscellaneous.StringExtensions"); var hasTrilogyReference = stringExtensionsType != null;
- 根据检测结果分支处理修复逻辑:
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var formatInvocation = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<InvocationExpressionSyntax>().First(); if (hasTrilogyReference) { // 原有修复:替换为SafeJoin并添加using context.RegisterCodeFix( CodeAction.Create( title: "Replace string.Format with StringExtensions.SafeJoin", createChangedDocument: c => ReplaceWithSafeJoinAsync(context.Document, formatInvocation, c), equivalenceKey: "ReplaceWithSafeJoin"), diagnostic); } else { // 备选方案1:不提供修复(直接跳过,不注册CodeAction) // return; // 备选方案2:替换为string.Join context.RegisterCodeFix( CodeAction.Create( title: "Replace string.Format with string.Join", createChangedDocument: c => ReplaceWithStringJoinAsync(context.Document, formatInvocation, c), equivalenceKey: "ReplaceWithStringJoin"), diagnostic); }
关键细节说明
- 为什么用GetTypeByMetadataName而不是检查程序集引用?
直接检查程序集引用可能会有误差(比如程序集被引用但类型被移除、或者有别名导致无法访问),而检查类型是否能被编译系统找到,是最直接判断该类型是否可用的方式。 - 确保元数据名称正确:要使用类型的完全限定名,包括命名空间,比如
Trilogy.Miscellaneous.StringExtensions,不能写错。 - using指令的添加逻辑:只有在
hasTrilogyReference为true时,才执行添加using Trilogy.Miscellaneous的代码,避免无效的using导入。
补充:替换为string.Join的代码生成示例
如果选择备选方案2,生成string.Join的代码逻辑可以参考这样:
private async Task<Document> ReplaceWithStringJoinAsync(Document document, InvocationExpressionSyntax formatInvocation, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // 解析原string.Format的参数:格式字符串、A、B var formatArgs = formatInvocation.ArgumentList.Arguments; var separatorLiteral = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(" ")); var joinArgs = SyntaxFactory.ArgumentList( SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(separatorLiteral), formatArgs[1], formatArgs[2] })); // 构建string.Join的调用 var joinInvocation = SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName("string"), SyntaxFactory.IdentifierName("Join")), joinArgs); // 替换原节点 var newRoot = root.ReplaceNode(formatInvocation, joinInvocation); return document.WithSyntaxRoot(newRoot); }
这样就能根据项目是否引用Trilogy.Common,动态提供合适的代码修复选项了。
内容的提问来源于stack exchange,提问作者Cameron Peters
相关产品推荐
相关产品推荐

