Roslyn 去除多余using

原因

当你添加新代码的时候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

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/using-directive-errors

从文中可以看到 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;
        }


    }
}
相关推荐
躺下睡觉~6 小时前
Unity-Transform类-父子关系
java·unity·游戏引擎
躺下睡觉~6 小时前
Unity-Transform类-缩放和看向
unity·游戏引擎
君莫愁。8 小时前
【Unity】检测鼠标点击位置是否有2D对象
unity·c#·游戏引擎
咩咩觉主9 小时前
Unity实战案例全解析:PVZ 植物卡片状态分析
unity·c#·游戏引擎
蓝裕安12 小时前
伪工厂模式制造敌人
开发语言·unity·游戏引擎
谢泽浩16 小时前
Unity 给模型贴上照片
unity·游戏引擎
z2014z16 小时前
Unity Resource System 优化笔记
unity·游戏引擎
王维志16 小时前
Unity 高亮插件HighlightPlus介绍
unity·游戏引擎
zaizai100717 小时前
我的demo保卫萝卜中的技术要点
unity
菌菌巧乐兹19 小时前
Unity 百度AI实现无绿幕拍照抠像功能(详解版)
人工智能·百度·unity