在项目开发中,我们通过会在脚本中有一些成对存在的方法,例如添加监听和移除监听,如果它们不能成对,可能会导致某些地方产生bug,所以提供了一个事件监听的检测工具。
?主要使用了正则表达式去匹配。
使用AddListener与RemoveListener或RemoveAllListeners去匹配。
核心方法如下:
/// <summary>
/// 根据路径匹配成对监听
/// </summary>
/// <param name="path"></param>
private static void FindVmListener(string path)
{
var codes = File.ReadAllLines(path, Encoding.UTF8).ToList();
try
{
for (int i = 0,countCountI = codes.Count; i < countCountI; i++)
{
string addListenerRegStr = "(\\S+).AddListener\\((\\S+)\\)";
Regex addListenerReg = new Regex(addListenerRegStr);
Match addListenerMatch = addListenerReg.Match(codes[i]);
var flag = false;
if (addListenerMatch.Success)
{
//在这个文件中没有找到对应的移除监听项
string removeListenerRegStr =
$"(.+){addListenerMatch.Groups[1].Value}.RemoveListener\\({addListenerMatch.Groups[2].Value}\\)(.+)";
Regex removeListenerReg = new Regex(removeListenerRegStr);
string removeAllListenerRegStr =
$"(.+){addListenerMatch.Groups[1].Value}.RemoveAllListeners(.+)";
Regex removeAllListenerReg = new Regex(removeAllListenerRegStr);
//用这个去重新配置如果没有匹配到说明有错误
for (int j = 0, countCountJ = codes.Count; j < countCountJ; j++)
{
Match removeListenerMatch = removeListenerReg.Match(codes[j]);
Match removeAllListenerMatch = removeAllListenerReg.Match(codes[j]);
if (removeListenerMatch.Success || removeAllListenerMatch.Success)
{
flag = true;
break;
}
}
if (!flag)
{
string str = $"{path} 第 ${i + 1} 行 AddListener:{addListenerMatch.Value} RemoveListener:{removeListenerRegStr}";
Debug.LogError(str);
}
}
}
}
catch (Exception e)
{
Debug.LogError($"Path:{path} ExceptionMessage:{e.Message}");
}
}
主要思路是通过传入文件路径读出文件内容后,根据以下串进行正则匹配:
string addListenerRegStr = "(\\S+).AddListener\\((\\S+)\\)";
如果匹配到了指定的内容,再根据匹配到内容Groups的第一个Value去组合成需要成组的另外一个待匹配串:
string removeListenerRegStr =?
$"(.+){addListenerMatch.Groups[1].Value}.RemoveListener\\({addListenerMatch.Groups[2].Value}\\)(.+)";
? ? ? ? ? ? ? ? ? ? ? ??
string removeAllListenerRegStr =?
$"(.+){addListenerMatch.Groups[1].Value}.RemoveAllListeners(.+)";
上述方法的缺点:
- 循环遍历次数过多,另外有一种Leetcode上的两数之和问题,可以用空间换取时间,通过声明字典缓存行内容再去进行匹配,可以消除部分循环,节省时间复杂度。
- 无法完全匹配出所有不合法的文件,会有疏漏或匹配出错的地方,比如换行的话是没办法正确匹配的。不过一般这种情况相对较少,受限于IDE的局限性,暂无更好的思路。
在查找出不合法的代码文件后,双击控制台能直接定位到指定的代码行,于是查阅了一些网上的资料,发现如下方法,主要是通过反射获取UnityEditor提供的方法:
private static string GetCallStackInfos()
{
var asbUnityEditor = Assembly.GetAssembly(typeof(EditorWindow)); if (asbUnityEditor == null) return null;
var typConsole = asbUnityEditor.GetType("UnityEditor.ConsoleWindow"); if (typConsole == null) return null;
var fieldInfo = typConsole.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic); if (fieldInfo == null) return null;
var insConsole = fieldInfo.GetValue(null); if (insConsole == null) return null;
if (focusedWindow == (EditorWindow) insConsole)
{
var typListViewState = asbUnityEditor.GetType("UnityEditor.ListViewState"); if (typListViewState == null) return null;
var fieldListView = typConsole.GetField("m_ListView", BindingFlags.Instance | BindingFlags.NonPublic); if (fieldListView == null) return null;
var oListView = fieldListView.GetValue(insConsole); if (oListView == null) return null;
var txtContent = typConsole.GetField("m_ActiveText", BindingFlags.Instance | BindingFlags.NonPublic); if (txtContent == null) return null;
return txtContent.GetValue(insConsole).ToString();
}
return null;
}
private static bool _findFlag;
[UnityEditor.Callbacks.OnOpenAssetAttribute(0)]
static bool OnOpenAsset(int instance, int line)
{
if (_findFlag)
{
_findFlag = false;
return false;
}
string stackTrace = GetCallStackInfos();
if (string.IsNullOrEmpty(stackTrace)) { return false; }
var path = Regex.Match(stackTrace, "Assets.*?cs").ToString();
Match m = Regex.Match(stackTrace, "第.*?行");
string strLine = Regex.Match(m.ToString(), @"([1-9]\d*\.?\d*)|(0\.\d*[1-9])").ToString();
int.TryParse(strLine, out var lineInCs);
if (!string.IsNullOrEmpty(path))
{
_findFlag = true;
return AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path), lineInCs);
}
return false;
}
总结:
此工具基本能用于匹配在脚本中需要成对存在的代码段,用于做客户端开发人员脚本检验使用,但存在一些小缺点,总体还是适用性很强,可以根据自己的业务需要很方便的定制化脚本代码校验工具。
如果觉得还不错的话,欢迎收藏、点赞、评论和关注我,我会持续带来更多实用性的通用工具的制作分享博客。
|