Files
plugin-library/Assets/01.CodeChecker/Editor/Rules/CheckClass.cs

267 lines
11 KiB
C#
Raw Normal View History

2025-03-04 16:02:44 +08:00
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
{
/// <summary>
/// Class [类] 命名规范
/// 使用 Pascal Case。
/// 使用名词或者名词性词组命名。
/// 文件名应和类名相同。
/// 不要轻易使用缩写。
/// 不使用type前缀。
/// 比如C来标识Class。
/// 比如使用FileStream而不是CFileStream。
/// 不使用下划线。
/// 在合适的时候,使用单词复合来标识从某个基类继承而来。
/// 比如xxxException。
/// 同一功能下,可以考虑同一命名前缀。
/// </summary>
public class CheckClass : ICheckable
{
public List<Detail> Check(string filepath, CompilationUnitSyntax root)
{
var results = new List<Detail>();
var classDecs = root.DescendantNodes().OfType<ClassDeclarationSyntax>().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<Detail> 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 = "类名禁用以下划线开始",
});
}
}
/// <summary>
/// Static Field [静态变量] 命名规范
/// 使用 Pascal Case。
/// 使用名词、名词性词组或者名词地缩写来命名静态变量。
/// 不要在静态变量名称中使用匈牙利命名法[变量名=属性+类型+对象描述]。
/// 在任何可能的情况下推荐你使用静态properties[属性]而不是public static fields。
/// </summary>
private static void CheckField(ClassDeclarationSyntax dec, List<Detail> results)
{
var fields = dec.DescendantNodes().OfType<FieldDeclarationSyntax>();
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<Detail> results)
{
var methods = dec.DescendantNodes().OfType<MethodDeclarationSyntax>();
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<Detail> results)
{
var properties = dec.DescendantNodes().OfType<PropertyDeclarationSyntax>();
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命名",
});
}
}
}
}
}