原因
当你添加新代码的时候VS会自动帮你添加对应的using,但是它只会帮你加不会帮你减
由于运行时并不能使用UnityEditor命名空间里面的东西。你就算加了也会在打包的时候给你报错,除非使用宏包裹起来
因为我们打包都是在打包机上操作的。一般情况下自己本地是不会打包也就不会知道在代码里面有多余using
经常出现打包到一半因为有多余的using导致的打包失败。然后再通知程序修改上传重新打包
解决方案
VS右键有个删除using和对其排序功能。但是说实话并没有什么卵用。每次保存前都右键执行一次比上面的流程更繁琐
基于以上流程过于繁琐。并且浪费时间。有了以下方案。
在git的pre-commit钩子加上cs后缀文件的using检测。如果有多余的using直接删除
编译动态链接库
编译需要设置是否开启
csharp
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe);
var compilation = CSharpCompilation.Create(assemblyName, options: options);
添加动态库所引用的dll
csharp
portableExecutable = MetadataReference.CreateFromFile(dllPath);
compilation = compilation.AddReferences(portableExecutable);
添加动态库所包含的文件
csharp
var option = new CSharpParseOptions(LanguageVersion.CSharp8, DocumentationMode.Parse, SourceCodeKind.Regular, symbols);
var encoding = FileEncoding.GetType(path);
var content = File.ReadAllText(path, encoding);
var tree = CSharpSyntaxTree.ParseText(content, option);
compilation = compilation.AddSyntaxTrees(tree);
以上所有包含的信息在csproj里面都有记录。直接读取并设置即可
检测是否有多余using
从文中可以看到 CS8019 是多余using警告。我们只需要根据这个移除对应的节点即可
csharp
CSharpCompilation compilation = CompilationHelper.GetCompilation(assemblyName);
var tree = CompilationHelper.GetSyntaxTree(assemblyName, path);
var root = tree.GetCompilationUnitRoot();
SemanticModel model = compilation.GetSemanticModel(tree);
var diagnostics = model.GetDiagnostics();
foreach (Diagnostic diagnostic in diagnostics)
{
if (diagnostic.Id == "CS8019")
{
if (root.FindNode(diagnostic.Location.SourceSpan) is UsingDirectiveSyntax node)
{
// 需要过滤的节点
}
}
}
语法树修改
通过CSharpSyntaxRewriter对语法树节点的修改
我们需要的是重载 VisitUsingDirective 函数,只需要返回空即可删除对应节点
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
namespace CSharpAnalyzer
{
public class UsingSyntaxRewriter : CSharpSyntaxRewriter
{
private Dictionary<UsingDirectiveSyntax, bool> filters = new Dictionary<UsingDirectiveSyntax, bool>();
public bool IsUpdate()
{
return filters.Count > 0;
}
public void Add(UsingDirectiveSyntax node)
{
if (node == null)
return;
filters[node] = true;
}
public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node)
{
if (filters.ContainsKey(node))
return null;
return base.VisitUsingDirective(node);
}
}
}
完整代码
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
namespace CSharpAnalyzer
{
public class CompilationHelper
{
static Dictionary<string,List<string>> pathDict = new Dictionary<string,List<string>>();
static Dictionary<string, string[]> symbolDict = new Dictionary<string, string[]>();
static Dictionary<string, CSharpCompilation> compilationDict = new Dictionary<string, CSharpCompilation>();
public static void LoadCsproj(string csproj)
{
if (!File.Exists(csproj))
{
throw new Exception($"{csproj} not found");
}
var assemblyName = Path.GetFileNameWithoutExtension(csproj);
if (compilationDict.ContainsKey(assemblyName))
return;
var dir = Path.GetDirectoryName(csproj);
XmlDocument document = new XmlDocument();
document.LoadXml(File.ReadAllText(csproj));
var root = document.FirstChild;
var project = root.NextSibling;
bool allowUnsafe = false;
string[] symbols = null;
List<string> dllList = new List<string>();
List<string> fileList = new List<string>();
foreach (XmlNode node in project.ChildNodes)
{
switch (node.Name)
{
case "PropertyGroup":
foreach (XmlNode n in node.ChildNodes)
{
if (n.Name == "DefineConstants")
{
var list = n.InnerText.Split(';').ToList();
list.Add("DEBUG");
list.Add("RELEASE");
list.Add("UNITY_EDITOR");
symbols = list.ToArray();
}
else if (n.Name == "AllowUnsafeBlocks")
{
bool.TryParse(n.InnerText, out allowUnsafe);
}
}
break;
case "ItemGroup":
foreach (XmlNode n in node.ChildNodes)
{
if (n.Name == "Compile")
{
fileList.Add(n.Attributes["Include"].Value);
}
else if (n.Name == "Reference")
{
dllList.Add(n.ChildNodes[0].InnerText);
}
else if (n.Name == "ProjectReference")
{
var name = Path.GetFileNameWithoutExtension(n.Attributes["Include"].Value);
var dll = Path.Combine(@"Library\ScriptAssemblies", name + ".dll");
dllList.Add(dll);
}
}
break;
}
}
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe);
var compilation = CSharpCompilation.Create(assemblyName, options: options);
foreach (var dll in dllList)
{
var dllPath = dll;
if (!dll.Contains(":"))
dllPath = Path.Combine(dir, dll);
var portableExecutable = PortableExecutableHelper.Get(dllPath);
if (portableExecutable != null)
{
compilation = compilation.AddReferences(portableExecutable);
}
}
symbolDict[assemblyName] = symbols;
List<string> files = new List<string>();
foreach (var file in fileList)
{
var path = Path.Combine(dir, file).Replace("\\", "/");
var tree = GetSyntaxTree(assemblyName, path);
compilation = compilation.AddSyntaxTrees(tree);
files.Add(path);
}
pathDict[assemblyName] = files;
compilationDict[assemblyName] = compilation;
}
public static CSharpCompilation GetCompilation(string assemblyName)
{
compilationDict.TryGetValue(assemblyName, out var compilation);
return compilation;
}
public static SyntaxTree GetSyntaxTree(string assemblyName, string path)
{
symbolDict.TryGetValue(assemblyName, out var symbol);
return SyntaxTreeHelper.GetSyntaxTree(path, symbol);
}
}
}
csharp
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.IO;
namespace CSharpAnalyzer
{
internal class PortableExecutableHelper
{
static Dictionary<string, PortableExecutableReference> dict = new Dictionary<string, PortableExecutableReference>();
internal static PortableExecutableReference Get(string path)
{
if (dict.TryGetValue(path, out var dll))
{
return dll;
}
if (File.Exists(path))
{
dll = MetadataReference.CreateFromFile(path);
if (dll != null)
{
dict[path] = dll;
return dll;
}
}
return null;
}
}
}
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Generic;
using System.IO;
namespace CSharpAnalyzer
{
public class SyntaxTreeHelper
{
static Dictionary<string, SyntaxTree> syntaxTreeDict = new Dictionary<string, SyntaxTree>();
public static SyntaxTree GetSyntaxTree(string path, string[] symbols, bool reload = false)
{
SyntaxTree result = null;
if (!reload)
{
if (syntaxTreeDict.TryGetValue(path, out result))
{
return result;
}
}
if (!File.Exists(path))
return result;
CSharpParseOptions option = null;
if (symbols != null && symbols.Length > 0)
{
option = new CSharpParseOptions(LanguageVersion.CSharp8, DocumentationMode.Parse, SourceCodeKind.Regular, symbols);
}
var encoding = FileEncoding.GetType(path);
var content = File.ReadAllText(path, encoding);
result = CSharpSyntaxTree.ParseText(content, option);
syntaxTreeDict[path] = result;
return result;
}
}
}
csharp
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/using-directive-errors
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.IO;
namespace CSharpAnalyzer
{
public class UnnecessaryImportsAnalyzer
{
public static bool Run(string path, string assemblyName)
{
if (!File.Exists(path))
return false;
CSharpCompilation compilation = CompilationHelper.GetCompilation(assemblyName);
if (compilation == null)
return false;
var tree = CompilationHelper.GetSyntaxTree(assemblyName, path.Replace(@"\","/"));
var root = tree.GetCompilationUnitRoot();
SemanticModel model = compilation.GetSemanticModel(tree);
var diagnostics = model.GetDiagnostics();
if (diagnostics.IsEmpty)
{
var encoding = FileEncoding.GetType(path);
if (encoding != System.Text.Encoding.UTF8)
{
var context = File.ReadAllText(path, encoding);
File.WriteAllText(path, context, System.Text.Encoding.UTF8);
return true;
}
return false;
}
{
//var usingSyntaxRewriter = new UsingSyntaxRewriter();
List<string> usings = new List<string>();
foreach (Diagnostic diagnostic in diagnostics)
{
if(diagnostic.Severity == DiagnosticSeverity.Error)
{
Console.WriteLine($"{path} {diagnostic.GetMessage()}");
return false;
}
if (diagnostic.Id == "CS8019")
{
if (root.FindNode(diagnostic.Location.SourceSpan) is UsingDirectiveSyntax node)
{
//usingSyntaxRewriter.Add(node);
usings.Add(node.ToString());
}
}
}
if (usings.Count > 0)
{
//var newRoot = usingSyntaxRewriter.Visit(root);
//var context = newRoot.GetText().ToString();
var context = root.GetText().ToString();
foreach (var _using in usings)
{
context = context.Replace(_using,string.Empty);
}
File.WriteAllText(path, context, System.Text.Encoding.UTF8);
return true;
}
else
{
var encoding = FileEncoding.GetType(path);
if (encoding != System.Text.Encoding.UTF8)
{
var context = File.ReadAllText(path, encoding);
File.WriteAllText(path, context, System.Text.Encoding.UTF8);
return true;
}
}
}
return false;
}
}
}
csharp
// https://blog.csdn.net/qq_43024228/article/details/122719840
using System;
using System.IO;
using System.Text;
namespace CSharpAnalyzer
{
public class FileEncoding
{
/// <summary>
/// 给定文件的路径,读取文件的二进制数据,判断文件的编码类型
/// </summary>
/// <param name="FILE_NAME">文件路径</param>
/// <returns>文件的编码类型</returns>
public static System.Text.Encoding GetType(string FILE_NAME)
{
FileStream fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read);
Encoding r = GetType(fs);
fs.Close();
return r;
}
/// <summary>
/// 通过给定的文件流,判断文件的编码类型
/// </summary>
/// <param name="fs">文件流</param>
/// <returns>文件的编码类型</returns>
public static System.Text.Encoding GetType(FileStream fs)
{
Encoding reVal = Encoding.Default;
BinaryReader r = new BinaryReader(fs, System.Text.Encoding.Default);
int i;
int.TryParse(fs.Length.ToString(), out i);
byte[] ss = r.ReadBytes(i);
if (IsUTF8Bytes(ss) || (ss[0] == 0xEF && ss[1] == 0xBB && ss[2] == 0xBF))
{
reVal = Encoding.UTF8;
}
else if (ss[0] == 0xFE && ss[1] == 0xFF && ss[2] == 0x00)
{
reVal = Encoding.BigEndianUnicode;
}
else if (ss[0] == 0xFF && ss[1] == 0xFE && ss[2] == 0x41)
{
reVal = Encoding.Unicode;
}
r.Close();
return reVal;
}
/// <summary>
/// 判断是否是不带 BOM 的 UTF8 格式
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private static bool IsUTF8Bytes(byte[] data)
{
int charByteCounter = 1; //计算当前正分析的字符应还有的字节数
byte curByte; //当前分析的字节.
for (int i = 0; i < data.Length; i++)
{
curByte = data[i];
if (charByteCounter == 1)
{
if (curByte >= 0x80)
{
//判断当前
while (((curByte <<= 1) & 0x80) != 0)
{
charByteCounter++;
}
//标记位首位若为非0 则至少以2个1开始 如:110XXXXX...........1111110X
if (charByteCounter == 1 || charByteCounter > 6)
{
return false;
}
}
}
else
{
//若是UTF-8 此时第一位必须为1
if ((curByte & 0xC0) != 0x80)
{
return false;
}
charByteCounter--;
}
}
if (charByteCounter > 1)
{
throw new Exception("非预期的byte格式");
}
return true;
}
}
}