Roslyn 去除多余using

原因

当你添加新代码的时候VS会自动帮你添加对应的using,但是它只会帮你加不会帮你减
由于运行时并不能使用UnityEditor命名空间里面的东西。你就算加了也会在打包的时候给你报错,除非使用宏包裹起来
因为我们打包都是在打包机上操作的。一般情况下自己本地是不会打包也就不会知道在代码里面有多余using
经常出现打包到一半因为有多余的using导致的打包失败。然后再通知程序修改上传重新打包

解决方案

VS右键有个删除using和对其排序功能。但是说实话并没有什么卵用。每次保存前都右键执行一次比上面的流程更繁琐
基于以上流程过于繁琐。并且浪费时间。有了以下方案。
在git的pre-commit钩子加上cs后缀文件的using检测。如果有多余的using直接删除

编译动态链接库

编译需要设置是否开启

var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe);
var compilation = CSharpCompilation.Create(assemblyName, options: options);

添加动态库所引用的dll

portableExecutable = MetadataReference.CreateFromFile(dllPath);
compilation = compilation.AddReferences(portableExecutable);

添加动态库所包含的文件

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警告。我们只需要根据这个移除对应的节点即可

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 函数,只需要返回空即可删除对应节点

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);
        }
    }

}

完整代码

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);
        }

    }
}
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;
        }
    }
}

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;
        }
    }
}

// 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;
        }
    }

}


// https://blog.csdn.net/qq_43024228/article/details/122719840
using System;
using System.IO;
using System.Text;

namespace CSharpAnalyzer
{
    public class FileEncoding
    {
        ///  
        /// 给定文件的路径,读取文件的二进制数据,判断文件的编码类型 
        ///  
        /// 文件路径 
        /// 文件的编码类型 
        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;
        }

        ///  
        /// 通过给定的文件流,判断文件的编码类型 
        ///  
        /// 文件流 
        /// 文件的编码类型 
        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;

        }

        ///  
        /// 判断是否是不带 BOM 的 UTF8 格式 
        ///  
        ///  
        ///  
        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;
        }


    }
}

你可能感兴趣的:(unity3d,roslyn,unity,using,语法树分析)