using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using UnityEngine; namespace wvdet.CodeChecker { /// /// Class [类] 命名规范 /// 使用 Pascal Case。 /// 使用名词或者名词性词组命名。 /// 文件名应和类名相同。 /// 不要轻易使用缩写。 /// 不使用type前缀。 /// 比如,C来标识Class。 /// 比如,使用FileStream而不是CFileStream。 /// 不使用下划线。 /// 在合适的时候,使用单词复合来标识从某个基类继承而来。 /// 比如,xxxException。 /// 同一功能下,可以考虑同一命名前缀。 /// public class CheckClass : ICheckable { public List Check(string filepath, CompilationUnitSyntax root) { var results = new List(); var classDecs = root.DescendantNodes().OfType().ToList(); foreach (var dec in classDecs) { CheckClassName(dec, results); CheckProperty(dec, results); CheckField(dec, results); CheckMethod(dec, results); } var filename = Path.GetFileNameWithoutExtension(filepath); if (classDecs.Count > 0 && classDecs.All(x => x.Identifier.ValueText != filename)) { results.Add(new Detail { level = Level.Warning, guideline = "文件名应和类名相同", }); } return results; } private static void CheckClassName(ClassDeclarationSyntax dec, List results) { var identifier = dec.Identifier.ValueText; var lineNumber = dec.GetLocation().GetLineSpan().StartLinePosition.Line; if (CheckerUtils.IsChineseIdentifier(identifier, lineNumber, out var ret)) results.Add(ret); if (char.IsLower(identifier[0])) { results.Add(new Detail { level = Level.Warning, line = lineNumber, codeSnippet = identifier.Trim(), suggestion = CheckerUtils.ConvertPascalCase(identifier), guideline = "类名必须使用PascalCase", }); } if (identifier[0] == '_') { results.Add(new Detail { level = Level.Error, line = lineNumber, codeSnippet = identifier.Trim(), suggestion = CheckerUtils.ConvertPascalCase(identifier.Substring(1)), guideline = "类名禁用以下划线开始", }); } } /// /// Static Field [静态变量] 命名规范 /// 使用 Pascal Case。 /// 使用名词、名词性词组或者名词地缩写来命名静态变量。 /// 不要在静态变量名称中使用匈牙利命名法[变量名=属性+类型+对象描述]。 /// 在任何可能的情况下推荐你使用静态properties[属性]而不是public static fields。 /// private static void CheckField(ClassDeclarationSyntax dec, List results) { var fields = dec.DescendantNodes().OfType(); foreach (var field in fields) { var modifiers = field.Modifiers; var isConst = modifiers.Any(x => x.IsKind(SyntaxKind.ConstKeyword)); var isPublic = modifiers.Any(x => x.IsKind(SyntaxKind.PublicKeyword)); var isStatic = modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword)); var identifier = field.GetIdentifier().ValueText; var lineNumber = field.GetLocation().GetLineSpan().StartLinePosition.Line; if (CheckerUtils.IsChineseIdentifier(identifier, lineNumber, out var ret)) results.Add(ret); if (isConst) { if (identifier.Any(char.IsLower)) results.Add(new Detail { level = Level.Error, line = lineNumber, codeSnippet = field.GetFieldGreenText(), guideline = "Const Field[常量]全大写命名,下划线分隔。例如:SCENE_CAMERA。", }); continue; } if (isPublic) { if (isStatic && char.IsLower(identifier[0])) { results.Add(new Detail { level = Level.Error, line = lineNumber, codeSnippet = field.GetFieldGreenText(), guideline = "public静态变量首字母必须大写", }); } else if (!isStatic && char.IsUpper(identifier[0])) { results.Add(new Detail { level = Level.Error, line = lineNumber, codeSnippet = field.GetFieldGreenText(), guideline = "public字段首字母必须小写", }); } } else //私有 { if (char.IsUpper(identifier[0])) { results.Add(new Detail { level = Level.Error, line = lineNumber, codeSnippet = field.GetFieldGreenText(), guideline = "private及protected字段首字母必须小写", }); } var declarator = field.GetPredefined(); if (declarator != null) { if (declarator.Keyword.IsKind(SyntaxKind.BoolKeyword) && (identifier.Length < 3 || !identifier.StartsWith("is"))) { results.Add(new Detail { level = Level.Warning, line = lineNumber, codeSnippet = field.GetFieldGreenText(), guideline = "private及protected布尔字段应以is开始,如isFileFound", }); } } if (identifier.TrimStart('_').Contains('_')) { results.Add(new Detail { level = Level.Warning, line = lineNumber, codeSnippet = field.GetFieldGreenText(), guideline = "private及protected中间不使用下划线(如:isPlayer)", }); } } } } private static void CheckMethod(ClassDeclarationSyntax dec, List results) { var methods = dec.DescendantNodes().OfType(); foreach (var method in methods) { var identifier = method.Identifier.ValueText; var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line; if (CheckerUtils.IsChineseIdentifier(identifier, lineNumber, out var ret)) results.Add(ret); if (!char.IsUpper(identifier[0])) { results.Add(new Detail { level = Level.Error, line = lineNumber, codeSnippet = identifier.Trim(), suggestion = CheckerUtils.ConvertPascalCase(identifier), guideline = "方法名必须使用PascalCase", }); } method.ParameterList.Parameters.ToList().ForEach(parameter => { var parameterName = parameter.Identifier.ValueText; if (CheckerUtils.IsChineseIdentifier(parameterName, lineNumber, out var ret2)) results.Add(ret2); if (!char.IsLower(parameterName[0])) { results.Add(new Detail { level = Level.Error, line = lineNumber, codeSnippet = parameter.GetText().ToString().Trim(), guideline = "方法参数名必须使用小写字母开始", }); } if (identifier.Contains('_')) { results.Add(new Detail { level = Level.Error, line = lineNumber, codeSnippet = parameter.GetText().ToString().Trim(), guideline = "方法参数名禁止包含下划线(_)", }); } }); } } private static void CheckProperty(ClassDeclarationSyntax dec, List results) { var properties = dec.DescendantNodes().OfType(); foreach (var property in properties) { var identifier = property.Identifier.ValueText; var lineNumber = property.GetLocation().GetLineSpan().StartLinePosition.Line; if (CheckerUtils.IsChineseIdentifier(identifier, lineNumber, out var ret)) results.Add(ret); if (!char.IsUpper(identifier[0])) { results.Add(new Detail { level = Level.Error, line = lineNumber, codeSnippet = property.GetText().ToString().Trim(), suggestion = CheckerUtils.ConvertPascalCase(identifier), guideline = "属性名须使用Camel Case命名", }); } } } } }