如何编写脚本从给定的 C# 源文件中删除多余的 using 指令?
How to script removal of redundant using directives from the given C# source file?
似乎 Resharper 的 cleanupcode.exe 工具不知道这样做。
也许其他工具可以,但我没有找到。
尝试自己使用 Roslyn SemanticModel 到目前为止还没有成功,因为我不知道模型需要什么样的处理并且我找不到相关文档(或错过)。
所以,问题是 - 是否有工具可以识别实际使用的命名空间,或者甚至可以立即从给定的源文件中删除多余的 using 指令?也欢迎提出有关如何处理语义模型的想法。
编辑 1
考虑以下 C# 源代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CSTool.Tests.Input
{
public class Sample
{
public void Func()
{
static ICollection<ArrayList> action(string _) => null;
Console.WriteLine(action(Directory.GetFiles(Directory.GetCurrentDirectory()).FirstOrDefault())?.Count);
}
}
}
那么,我怎样才能确认using指令都是必需的呢?我尝试了以下代码:
private static IEnumerable<string> GetNamespaceSymbol(ISymbol symbol)
{
if (symbol != null && symbol.ContainingNamespace != null)
{
yield return symbol.ContainingNamespace.Name;
}
}
public static HashSet<string> Run(CSharpCompilation compilation, SyntaxTree tree)
{
var semanticModel = compilation.GetSemanticModel(tree);
return tree.GetRoot().DescendantNodes().SelectMany(node =>
GetNamespaceSymbol(semanticModel.GetSymbolInfo(node).Symbol)).ToHashSet();
}
但它 returns 以下无意义的结果 - {"", "CSTool", "System", "Tests"}
根本不需要。
我被指出了以下问题 - 。但显然自 2012 年以来发生了很多变化,今天的答案没有任何意义。至少对我来说。
编辑 2
我是这样获取树和编译对象的:
public List<string> Test(string text, Type[] types)
{
var tree = CSharpSyntaxTree.ParseText(text);
var compilation = CSharpCompilation
.Create("test")
.AddReferences(types.Select(t => MetadataReference.CreateFromFile(t.Assembly.Location)))
.AddSyntaxTrees(tree);
return CleanupNamespacesCmd.Run(compilation, tree).OrderBy(ns => ns).ToList();
}
编辑 3
所以,我尝试了新方法。这是代码:
public static void Run(string projectFilePath)
{
var project = new Project(projectFilePath);
var outDir = project.GetPropertyValue("OutDir");
using var a = AssemblyDefinition.ReadAssembly(project.GetPropertyValue("TargetPath"));
var trees = MapTypesCmd
.YieldCSFiles(project)
.Select(filePath => CSharpSyntaxTree.ParseText(File.ReadAllText(filePath), path: filePath));
var compilation = CSharpCompilation
.Create(project.GetPropertyValue("AssemblyName"))
.AddReferences(a.MainModule.AssemblyReferences.Select(CreateMetadataReference))
.AddSyntaxTrees(trees);
foreach (var tree in compilation.SyntaxTrees)
{
var model = compilation.GetSemanticModel(tree);
var diag = model.GetDiagnostics();
}
MetadataReference CreateMetadataReference(AssemblyNameReference asmRef)
{
var asm = a.MainModule.AssemblyResolver.Resolve(asmRef);
var path = asm.MainModule.FileName;
if (!Path.IsPathRooted(path))
{
path = outDir + asm.MainModule.Name;
}
return MetadataReference.CreateFromFile(path);
}
}
代码已经编译,所以我使用Mono.Cecil
读取程序集引用列表,将它们转换为MetadataReference
s并在创建CSharpCompilation
对象时使用。
但是,diag
数组包含伪造的诊断消息,就像这个 - C:\xyz\tip\DB\DL\Db\FilterDB.cs(25,24): error CS0518: Predefined type 'System.String' 未定义或导入
这很假。手表 window 告诉我 compilation.References.First().Display
等于 C:\Program Files\dotnet\shared\Microsoft.NETCore.App.1.12\mscorlib.dll 正如我之前提到的 - 代码编译干净。
我做错了什么?
所以这个特定问题在 Roslyn 中是特殊情况——Roslyn 编译器会针对它知道未使用的语句发出诊断,然后您可以从那里处理这些语句。 GetSemanticModel.GetDiagnostics() 应该让您了解它们。 IDE 使用这些诊断来实现我们的“删除未使用的使用”功能。
为什么要用这种奇怪的方法?因为试图从其他 API 回答这个问题几乎是不可能的。 C# 中存在疯狂的边缘情况,您可以在其中删除 using 并且代码仍会编译, 但是 重载分辨率已更改,因此您的代码会更改含义。我们将它们计为“已使用”,但如果您的代码没有仔细了解重载决策如何考虑名称空间,您就不能这样做。
更一般地说,根据您尝试的目的,我们还有其他可能更有用的工具或 API。
似乎 Resharper 的 cleanupcode.exe 工具不知道这样做。
也许其他工具可以,但我没有找到。
尝试自己使用 Roslyn SemanticModel 到目前为止还没有成功,因为我不知道模型需要什么样的处理并且我找不到相关文档(或错过)。
所以,问题是 - 是否有工具可以识别实际使用的命名空间,或者甚至可以立即从给定的源文件中删除多余的 using 指令?也欢迎提出有关如何处理语义模型的想法。
编辑 1
考虑以下 C# 源代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CSTool.Tests.Input
{
public class Sample
{
public void Func()
{
static ICollection<ArrayList> action(string _) => null;
Console.WriteLine(action(Directory.GetFiles(Directory.GetCurrentDirectory()).FirstOrDefault())?.Count);
}
}
}
那么,我怎样才能确认using指令都是必需的呢?我尝试了以下代码:
private static IEnumerable<string> GetNamespaceSymbol(ISymbol symbol)
{
if (symbol != null && symbol.ContainingNamespace != null)
{
yield return symbol.ContainingNamespace.Name;
}
}
public static HashSet<string> Run(CSharpCompilation compilation, SyntaxTree tree)
{
var semanticModel = compilation.GetSemanticModel(tree);
return tree.GetRoot().DescendantNodes().SelectMany(node =>
GetNamespaceSymbol(semanticModel.GetSymbolInfo(node).Symbol)).ToHashSet();
}
但它 returns 以下无意义的结果 - {"", "CSTool", "System", "Tests"}
根本不需要。
我被指出了以下问题 - 。但显然自 2012 年以来发生了很多变化,今天的答案没有任何意义。至少对我来说。
编辑 2
我是这样获取树和编译对象的:
public List<string> Test(string text, Type[] types)
{
var tree = CSharpSyntaxTree.ParseText(text);
var compilation = CSharpCompilation
.Create("test")
.AddReferences(types.Select(t => MetadataReference.CreateFromFile(t.Assembly.Location)))
.AddSyntaxTrees(tree);
return CleanupNamespacesCmd.Run(compilation, tree).OrderBy(ns => ns).ToList();
}
编辑 3
所以,我尝试了新方法。这是代码:
public static void Run(string projectFilePath)
{
var project = new Project(projectFilePath);
var outDir = project.GetPropertyValue("OutDir");
using var a = AssemblyDefinition.ReadAssembly(project.GetPropertyValue("TargetPath"));
var trees = MapTypesCmd
.YieldCSFiles(project)
.Select(filePath => CSharpSyntaxTree.ParseText(File.ReadAllText(filePath), path: filePath));
var compilation = CSharpCompilation
.Create(project.GetPropertyValue("AssemblyName"))
.AddReferences(a.MainModule.AssemblyReferences.Select(CreateMetadataReference))
.AddSyntaxTrees(trees);
foreach (var tree in compilation.SyntaxTrees)
{
var model = compilation.GetSemanticModel(tree);
var diag = model.GetDiagnostics();
}
MetadataReference CreateMetadataReference(AssemblyNameReference asmRef)
{
var asm = a.MainModule.AssemblyResolver.Resolve(asmRef);
var path = asm.MainModule.FileName;
if (!Path.IsPathRooted(path))
{
path = outDir + asm.MainModule.Name;
}
return MetadataReference.CreateFromFile(path);
}
}
代码已经编译,所以我使用Mono.Cecil
读取程序集引用列表,将它们转换为MetadataReference
s并在创建CSharpCompilation
对象时使用。
但是,diag
数组包含伪造的诊断消息,就像这个 - C:\xyz\tip\DB\DL\Db\FilterDB.cs(25,24): error CS0518: Predefined type 'System.String' 未定义或导入
这很假。手表 window 告诉我 compilation.References.First().Display
等于 C:\Program Files\dotnet\shared\Microsoft.NETCore.App.1.12\mscorlib.dll 正如我之前提到的 - 代码编译干净。
我做错了什么?
所以这个特定问题在 Roslyn 中是特殊情况——Roslyn 编译器会针对它知道未使用的语句发出诊断,然后您可以从那里处理这些语句。 GetSemanticModel.GetDiagnostics() 应该让您了解它们。 IDE 使用这些诊断来实现我们的“删除未使用的使用”功能。
为什么要用这种奇怪的方法?因为试图从其他 API 回答这个问题几乎是不可能的。 C# 中存在疯狂的边缘情况,您可以在其中删除 using 并且代码仍会编译, 但是 重载分辨率已更改,因此您的代码会更改含义。我们将它们计为“已使用”,但如果您的代码没有仔细了解重载决策如何考虑名称空间,您就不能这样做。
更一般地说,根据您尝试的目的,我们还有其他可能更有用的工具或 API。