ChineseCodeCollector.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. using UnityEngine;
  2. using UnityEditor;
  3. using System.IO;
  4. using System.Text.RegularExpressions;
  5. using System.Collections.Generic;
  6. public class ChineseCodeCollectorUltimate : EditorWindow
  7. {
  8. private string searchPath = "Assets/Scripts";
  9. private int maxLinesPerFile = 20;
  10. private Vector2 scrollPos;
  11. private List<ChineseCodeInfo> results = new List<ChineseCodeInfo>();
  12. [MenuItem("Tools/收集中文代码")]
  13. static void Open() => GetWindow<ChineseCodeCollectorUltimate>("中文代码精准收集器");
  14. void OnGUI()
  15. {
  16. GUILayout.Label("中文硬编码精准收集器", EditorStyles.boldLabel);
  17. GUILayout.Label("只排除含有 LogTool./Debug. 的日志行,其他中文照收", EditorStyles.miniLabel);
  18. GUILayout.Space(10);
  19. GUILayout.BeginHorizontal();
  20. GUILayout.Label("搜索目录:", GUILayout.Width(90));
  21. searchPath = EditorGUILayout.TextField(searchPath);
  22. if (GUILayout.Button("浏览", GUILayout.Width(60)))
  23. {
  24. string p = EditorUtility.OpenFolderPanel("选择目录", searchPath, "");
  25. if (!string.IsNullOrEmpty(p) && p.StartsWith(Application.dataPath))
  26. searchPath = "Assets" + p.Substring(Application.dataPath.Length);
  27. }
  28. GUILayout.EndHorizontal();
  29. GUILayout.BeginHorizontal();
  30. GUILayout.Label("每文件最多收集:", GUILayout.Width(110));
  31. maxLinesPerFile = EditorGUILayout.IntField(maxLinesPerFile, GUILayout.Width(60));
  32. maxLinesPerFile = Mathf.Max(1, maxLinesPerFile);
  33. GUILayout.Label("行");
  34. GUILayout.EndHorizontal();
  35. GUILayout.Space(10);
  36. if (GUILayout.Button("开始精准扫描", GUILayout.Height(40)))
  37. ScanWithPrecision();
  38. if (results.Count > 0)
  39. {
  40. GUILayout.Label($"发现 {results.Count} 个文件含有非日志中文", EditorStyles.boldLabel);
  41. scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
  42. foreach (var info in results)
  43. DrawResult(info);
  44. EditorGUILayout.EndScrollView();
  45. if (GUILayout.Button("导出报告(TXT)", GUILayout.Height(35)))
  46. ExportReport();
  47. }
  48. }
  49. void ScanWithPrecision()
  50. {
  51. results.Clear();
  52. if (!Directory.Exists(searchPath))
  53. {
  54. EditorUtility.DisplayDialog("错误", "目录不存在:" + searchPath, "OK");
  55. return;
  56. }
  57. var files = Directory.GetFiles(searchPath, "*.cs", SearchOption.AllDirectories);
  58. foreach (var file in files)
  59. {
  60. // 只排除 Editor 文件夹下的脚本
  61. if (file.Contains("/Editor/") || file.Contains("\\Editor\\"))
  62. continue;
  63. var info = AnalyzeFilePrecisely(file);
  64. if (info != null && info.lines.Count > 0)
  65. results.Add(info);
  66. }
  67. Debug.Log($"精准扫描完成!共 {results.Count} 个文件含有非日志中文");
  68. }
  69. ChineseCodeInfo AnalyzeFilePrecisely(string path)
  70. {
  71. string[] lines = File.ReadAllLines(path);
  72. string className = ExtractClassName(lines);
  73. var chineseLines = new List<ChineseLine>();
  74. bool inRegion = false;
  75. bool inMultiLineComment = false;
  76. for (int i = 0; i < lines.Length; i++)
  77. {
  78. string raw = lines[i];
  79. string trimmed = raw.Trim();
  80. // 空行跳过
  81. if (string.IsNullOrWhiteSpace(trimmed)) continue;
  82. // #region 区域完全跳过
  83. if (trimmed.StartsWith("#region") || trimmed.Contains(" #region "))
  84. inRegion = true;
  85. if (trimmed.StartsWith("#endregion") || trimmed.Contains(" #endregion"))
  86. {
  87. inRegion = false;
  88. continue;
  89. }
  90. if (inRegion) continue;
  91. // 多行注释处理
  92. if (inMultiLineComment)
  93. {
  94. if (trimmed.Contains("*/")) inMultiLineComment = false;
  95. continue;
  96. }
  97. if (trimmed.Contains("/*") && !trimmed.Contains("*/"))
  98. {
  99. inMultiLineComment = true;
  100. continue;
  101. }
  102. // 单行注释跳过
  103. if (trimmed.StartsWith("//")) continue;
  104. // 这行如果包含日志调用,直接跳过这行
  105. if (IsLogLine(trimmed))
  106. continue;
  107. // 是否包含中文
  108. if (!ContainsChinese(raw))
  109. continue;
  110. chineseLines.Add(new ChineseLine
  111. {
  112. lineNumber = i + 1,
  113. code = raw.TrimEnd()
  114. });
  115. if (chineseLines.Count >= maxLinesPerFile)
  116. break;
  117. }
  118. if (chineseLines.Count > 0)
  119. {
  120. return new ChineseCodeInfo
  121. {
  122. filePath = path.Replace(Application.dataPath, "Assets"),
  123. className = className ?? Path.GetFileNameWithoutExtension(path),
  124. lines = chineseLines
  125. };
  126. }
  127. return null;
  128. }
  129. // 判断是否为日志行
  130. bool IsLogLine(string line)
  131. {
  132. // 常见日志调用模式(不区分大小写)
  133. string lower = line.ToLower();
  134. return lower.Contains("logtool.") ||
  135. lower.Contains("debug.log") ||
  136. lower.Contains("debug.warn") ||
  137. lower.Contains("debug.error") ||
  138. lower.Contains("debug.assert") ||
  139. lower.Contains("debug.draw") ||
  140. lower.Contains("debug.break") ||
  141. lower.Contains("logger.") ||
  142. lower.Contains("log.") && lower.Contains("info") ||
  143. lower.Contains("log.") && lower.Contains("warning") ||
  144. lower.Contains("log.") && lower.Contains("error");
  145. }
  146. bool ContainsChinese(string text)
  147. {
  148. return Regex.IsMatch(text, @"[\u4e00-\u9fff]");
  149. }
  150. string ExtractClassName(string[] lines)
  151. {
  152. foreach (string line in lines)
  153. {
  154. var m = Regex.Match(line, @"class\s+(\w+)");
  155. if (m.Success) return m.Groups[1].Value;
  156. }
  157. return null;
  158. }
  159. void DrawResult(ChineseCodeInfo info)
  160. {
  161. EditorGUILayout.BeginVertical("box");
  162. EditorGUILayout.LabelField($"{Path.GetFileName(info.filePath)}", EditorStyles.boldLabel);
  163. EditorGUILayout.LabelField($"类名:{info.className}", EditorStyles.miniLabel);
  164. EditorGUILayout.SelectableLabel(info.filePath, EditorStyles.miniLabel, GUILayout.Height(16));
  165. foreach (var cl in info.lines)
  166. {
  167. EditorGUILayout.BeginHorizontal();
  168. GUILayout.Label(cl.lineNumber.ToString("D4"), GUILayout.Width(50));
  169. GUILayout.Label(">", GUILayout.Width(15));
  170. EditorGUILayout.SelectableLabel(cl.code, GUILayout.Height(18));
  171. EditorGUILayout.EndHorizontal();
  172. }
  173. EditorGUILayout.EndVertical();
  174. GUILayout.Space(6);
  175. }
  176. void ExportReport()
  177. {
  178. string path = EditorUtility.SaveFilePanel("导出中文报告", "", "非日志中文硬编码报告.txt", "txt");
  179. if (string.IsNullOrEmpty(path)) return;
  180. using (var sw = new StreamWriter(path, false, System.Text.Encoding.UTF8))
  181. {
  182. sw.WriteLine("============================================");
  183. sw.WriteLine(" Unity 非日志中文硬编码精准检测报告");
  184. sw.WriteLine("============================================");
  185. sw.WriteLine($"生成时间:{System.DateTime.Now:yyyy-MM-dd HH:mm:ss}");
  186. sw.WriteLine($"搜索目录:{searchPath}");
  187. sw.WriteLine($"已排除:#region 区域、Editor 文件夹、所有日志行(LogTool./Debug./Log.等)");
  188. sw.WriteLine($"共发现 {results.Count} 个文件含有非日志中文");
  189. sw.WriteLine();
  190. foreach (var r in results)
  191. {
  192. sw.WriteLine($"文件:{r.filePath}");
  193. sw.WriteLine($"类名:{r.className}");
  194. sw.WriteLine("非日志中文行:");
  195. foreach (var l in r.lines)
  196. sw.WriteLine($" 第 {l.lineNumber,4} 行:{l.code}");
  197. sw.WriteLine(new string('-', 70));
  198. }
  199. }
  200. Debug.Log("精准中文报告已导出:" + path);
  201. EditorUtility.RevealInFinder(path);
  202. }
  203. }
  204. // 数据结构
  205. [System.Serializable]
  206. public class ChineseCodeInfo
  207. {
  208. public string filePath;
  209. public string className;
  210. public List<ChineseLine> lines;
  211. }
  212. [System.Serializable]
  213. public class ChineseLine
  214. {
  215. public int lineNumber;
  216. public string code;
  217. }