在Unity项目中使用Roslyn Code Analysis

先放上github repo Unity-CodeAnalyzerPlugin 一个给Unity项目添加CodeAnalyzer引用的插件。

  • 在IDE中使用Roslyn CodeAnalyzer
  • Unity中使用CodeAnalyzer
  • Unity Plugin的实现与使用

在IDE中使用Roslyn CodeAnalyzer

Roslyn Code Analysis 提供了许多静态代码分析的功能,如代码重构、诊断或者是自动补全等等。各种IDE的代码提示都可以使用roslyn的用code analysis作为language service的提供者。

在visual studio中,有两种使自定义code analyzer的方式。一种是通过vsix的扩展package进行安装,编写一个code analyzer并编译成dll,同时将该dll pack进一个vsix的packqge,最后在visual studio中安装插件就可以看到自定义代码分析的提示了。另一种方法是每一个csproject的引用里面可以添加对analyzer dll的引用。加载引用之后和安装vsix扩展是同样的效果。不过使用vsix安装的分析器默认是对全局开启的,而reference的方式只对当前引用的项目有效。

而在visual studio code中,所有的编辑器扩展是使用js或者ts编写的,本身vscode是基于electron,所以不支持c#的插件。对于跨平的vscode来说,cross platform的language service是一件比较麻烦的事情,于是Microsoft提出了一个language service protocol。通过定义一套RPC call 来让编辑器或ide支持不同语言,RPC协议定义了各种IDE的语言特性的数据接口。这样每个编辑器支持的语言的language service可以使用任意语言来编写,只需要实现LSP就可以了。而在vscode中的c#的language service protocol实现就是omnisharp,一个基于roslyn的workspace。大致的工作方式是omnisharp 通过roslyn的workspace API 读取sln或csproj。用户在vscode中的操作会被转换成LSP的RPC调用,omnisharp收到RPC调用后通过roslyn的api操作工程或者是脚本文件生成对应的诊断或代码提示。不过自定义的code analyzer经过测试vscode只在build时才会生成诊断信息,而不会像vs一样在编辑时动态提示。

Unity中使用CodeAnalyzer

unity工程的项目结果是基于msbuild的。sln与csproject支持使用mono在MacOS上进行开发。ID支持vs、vscode与mono developer(不再支持)。

在unity中使用code analyzer有几种方式:

一种是按照上文描述的通过vsix安装。但是如果我们需要编写针对不同项目的code analyzer就需要安装多个扩展,并且在切换不同项目时开启或关闭VS extension不是非常方便。

另一种方式是独立的code analyzer,github上有相关的开源项目UnityEngineAnalyzer,可以通过cli调用进行代码分析。

最后一种就是添加带有code analyzer的dll 引用。但是由于unity的asset刷新机制,每次有项目工程中的cs文件资源ie mono script的改动 unity编辑器就会重新生成csproj文件。为了保证生成proj文件的正确性并没有进行增量更新而是全部替换。这样我们手动添加到project中的analyzer就被移除了。

于是我编写了一个插件,可以hook每次重新生成csproj文件,并将code analyzer注入到csproj中。

Unity Plugin的实现与使用

首先是hook csproj文件的刷新。

我们通过unity OnPostprocessAllAssets回调来监听monoscript的改动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class UnityCSProjectAnalyzerPostprocessor : AssetPostprocessor
{
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
if (importedAssets.Any(asset => asset.ToLower().EndsWith(".cs")) ||
deletedAssets.Any(asset => asset.ToLower().EndsWith(".cs")) ||
movedAssets.Any(asset => asset.ToLower().EndsWith(".cs")) ||
movedFromAssetPaths.Any(asset => asset.ToLower().EndsWith(".cs")))
{
ModifyCSProject();
}
}
private static void ModifyCSProject()
{
CodeAnalyzerEditorWindow.OnCSProjectModified();
}
}

其次是解析unity的sln与csproj。这里需要注意的是使用vscode和vs作为代码编辑器打开工程生成的csproj文件名规则是不同的。在vscode中是以assembly-csharp.csprojassembly-csharp-editor.csproj 而在vs中是<projname>.csproj<projectname>.editor.csproj

在csproject中添加analyzer的引用:

1
2
3
<ItemGroup>
<Analyzer Include="..\..\..\Linty.Analyzers.dll" />
</ItemGroup>

使用自己编写xml linq来解析csproj是由于考虑到老版本的unity 还只支持到.net 3.5 并不能使用roslyn的workspace或msbuild的lib来解析项目。

使用方式

将Plugin导入Unity后,EditorWindow来选择analyzer的dll路径,可以对Assembly-CSharpAssembly-Editor分别添加Analyzer。

Analyzer
Analyzer

在MenuItem的CodeAnalysis\Config打开CodeAnalyzerEditorWindow. `CodeAnalysis\Refresh刷新引用的analyzer dll,每次删除脚本或者创建脚本都会自动刷新引用的analyzer.