本文为大家带来 Laugh 的“主程必杀技”系列文章第三篇《Unity C# 自动化执行代码规范-命名规范检查》,包括检查命名空间和类名、public 和 private 方法名称、成员变量命名、临时变量命名。系列其他文章将每周更新,或可访问 Laugh 的 Unity 官方开发者社区主页,持续关注学习。
演示代码
获取链接:https://gitee.com/palang/unity-sharp-code-regulator.git
⦁ 检查继承关系
Unity C# 自动化执行代码规范
命名规范检查
环境准备
4. Microsoft.CodeAnalysis.CSharp,安装 3.8.0 版本 基本过程可以参考第一篇文章。
命名空间和类名检查
实现目标:
1.强制类名首字母大写,并且不能全部是大写;
编写 Analyzer 分析器:
public const string FORCE_NAMING_CONVENTIONS_ID= "FERR1003";`
using Analyzer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace CdeAnalyzer
{
/**
* Author: Laugh(笑微)
* https://developer.unity.cn/projects/65937455edbc2a001cbd8102
*/
[ ]
internal class ForceNSClassNamingConventions : DiagnosticAnalyzer
{
/// <summary>
/// 错误描述
/// </summary>
private static readonly DiagnosticDescriptor ForceNamingConventionsDescriptor =
new DiagnosticDescriptor(
DianogsticIDs.FORCE_NAMING_CONVENTIONS_ID, // ID
"命名空间或类名不符合规范", // Title
"命名空间或类名不符合规范", // Message format
DiagnosticCategories.Criterion, // Category
DiagnosticSeverity.Error, // Severity
isEnabledByDefault: true // Enabled by default
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(ForceNamingConventionsDescriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeAction(AnalyzeSymbol);
}
private static void AnalyzeSymbol(SyntaxTreeAnalysisContext context)
{
//找到文档的语法根树
var root = context.Tree.GetRoot(context.CancellationToken);
var classNodeList = root.DescendantNodes()?.OfType<ClassDeclarationSyntax>();
foreach (var cls in classNodeList)
{
var clsName = cls.Identifier.ToString();
var firstChar = clsName.First().ToString();
//如果全是小写或全是大写或首字母非大写,则不符合驼峰命名法(粗略检查),复杂的规矩可以自行定义
if (clsName == clsName.ToLower()
|| clsName == clsName.ToUpper()
|| firstChar != firstChar.ToUpper()
)
{
//报错
var diagnostic = Diagnostic.Create(ForceNamingConventionsDescriptor, cls.GetFirstToken().GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
var nsNodeList = root.DescendantNodes()?.OfType<NamespaceDeclarationSyntax>();
foreach(var ns in nsNodeList)
{
var nsName = ns.Name.ToString();
//拆分命名空间的级段
var nlist = nsName.Split(new char[] { '.' });
foreach(var n in nlist)
{
var firstChar = n.First().ToString();
//如果首字母非大写,则不符合驼峰命名法(粗略检查),复杂的规矩可以自行定义
if (firstChar != firstChar.ToUpper()
)
{
//报错
var diagnostic = Diagnostic.Create(ForceNamingConventionsDescriptor, ns.GetFirstToken().GetLocation());
context.ReportDiagnostic(diagnostic);
break;
}
}
}
}
}
}
public 和 private 方法名称规范检查
实现目标:
3. 添加排除检查的目录和类文件
定义排除工具类:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Analyzer
{
public class ConstraintDefinition
{
/// <summary>
/// 检查时排除的目录
/// </summary>
static List<string> AnalyzerExcludePath = new List<string>() {
"/PackageCache/",
"/ThirdLibs/",
"/Plugins/"
};
/// <summary>
/// 检查时排除的文件
/// </summary>
static List<string> AnalyzerExcludeFileName = new List<string>()
{
"Program.cs"
};
/// <summary>
/// 是否是需要排除检查
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static bool ExcludeAnalize(string path)
{
var fileName = Path.GetFileName(path);
if (AnalyzerExcludeFileName.Contains(fileName))
{
return true;
}
foreach(var file in AnalyzerExcludePath)
{
if(path.Contains(file))
{
return true;
}
}
return false;
}
}
}
命名规则使用同一个错误 ID:
public const string FORCE\_NAMING\_CONVENTIONS\_ID= "FERR1003";
新建分析器类 ForceFunctionNameConventions.cs,内容如下(具体步骤请看代码中的注释)
using Analyzer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace CdeAnalyzer
{
/**
* Author: Laugh(笑微)
* https://developer.unity.cn/projects/65937455edbc2a001cbd8102
*/
[ ]
internal class ForceFunctionNameConventions : DiagnosticAnalyzer
{
/// <summary>
/// 错误描述
/// </summary>
private static readonly DiagnosticDescriptor PublicFunDescriptor =
new DiagnosticDescriptor(
DianogsticIDs.FORCE_NAMING_CONVENTIONS_ID, // ID
"public 方法名不符合规范", // Title
"public 方法名不符合规范", // Message format
DiagnosticCategories.Criterion, // Category
DiagnosticSeverity.Error, // Severity
isEnabledByDefault: true // Enabled by default
);
private static readonly DiagnosticDescriptor PrivateFunDescriptor =
new DiagnosticDescriptor(
DianogsticIDs.FORCE_NAMING_CONVENTIONS_ID, // ID
"private 方法名不符合规范", // Title
"private 方法名不符合规范", // Message format
DiagnosticCategories.Criterion, // Category
DiagnosticSeverity.Error, // Severity
isEnabledByDefault: true // Enabled by default
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(PublicFunDescriptor, PrivateFunDescriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeAction(AnalyzeSymbol);
}
private static void AnalyzeSymbol(SyntaxTreeAnalysisContext context)
{
//找到文档的语法根树
var root = context.Tree.GetRoot(context.CancellationToken);
if (ConstraintDefinition.ExcludeAnalize(context.Tree.FilePath))
{//排除特殊目录
return;
}
var methodNodeList = root.DescendantNodes()?.OfType<MethodDeclarationSyntax>();
foreach (var method in methodNodeList)
{
var clsName = method.Identifier.ToString();
var firstChar = clsName.First().ToString();
var tokens = method.ChildTokens();
foreach ( var token in tokens)
{
//public 方法:首字母大写,大驼峰命名法(pascal)
if (token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PublicKeyword))
{
//全是大写或首字母非大写,则不符合大驼峰命名法(粗略检查),复杂的规矩可以自行定义
if (clsName == clsName.ToLower()
|| clsName == clsName.ToUpper()
|| firstChar != firstChar.ToUpper()
)
{
//报错
var diagnostic = Diagnostic.Create(PublicFunDescriptor, method.GetFirstToken().GetLocation());
context.ReportDiagnostic(diagnostic);
}
break;//只检查一次
}
else if (token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PrivateKeyword)
|| token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.ProtectedKeyword)
|| token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.InternalKeyword)
|| token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.IdentifierToken))
{
//其他:private protected 等等,使用小驼峰命名法
//首字母小写
if (firstChar != firstChar.ToLower()
)
{
//报错
var diagnostic = Diagnostic.Create(PrivateFunDescriptor, method.GetFirstToken().GetLocation());
context.ReportDiagnostic(diagnostic);
}
break;//只检查一次
}
}
}
}
}
}
代码修改后再使用。
成员变量命名规范检查
1. 强制 private protected 变量以下划线开头;
命名规则使用同一个错误 ID:
public const string FORCE\_NAMING\_CONVENTIONS\_ID= "FERR1003";
定义分析器类 ForceMemberVariableConventions.cs,内容如下:
using Analyzer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace CdeAnalyzer
{
/**
* Author: Laugh(笑微)
* https://developer.unity.cn/projects/65937455edbc2a001cbd8102
*/
[ ]
internal class ForceMemberVariableConventions : DiagnosticAnalyzer
{
/// <summary>
/// 错误描述
/// </summary>
private static readonly DiagnosticDescriptor PublicVarDescriptor =
new DiagnosticDescriptor(
DianogsticIDs.FORCE_NAMING_CONVENTIONS_ID, // ID
"类中不不能定义共有变量,请使用Getter Setter 或方法", // Title
"类中不不能定义共有变量,请使用Getter Setter 或方法", // Message format
DiagnosticCategories.Criterion, // Category
DiagnosticSeverity.Error, // Severity
isEnabledByDefault: true // Enabled by default
);
private static readonly DiagnosticDescriptor PrivateVarDescriptor =
new DiagnosticDescriptor(
DianogsticIDs.FORCE_NAMING_CONVENTIONS_ID, // ID
"private 变量名名不符合规范,必须以下划线(_)开头的小驼峰命名", // Title
"private 变量名名不符合规范,必须以下划线(_)开头的小驼峰命名", // Message format
DiagnosticCategories.Criterion, // Category
DiagnosticSeverity.Error, // Severity
isEnabledByDefault: true // Enabled by default
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(PublicVarDescriptor, PrivateVarDescriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeAction(AnalyzeSymbol);
}
private static void AnalyzeSymbol(SyntaxTreeAnalysisContext context)
{
//找到文档的语法根树
var root = context.Tree.GetRoot(context.CancellationToken);
if (ConstraintDefinition.ExcludeAnalize(context.Tree.FilePath))
{//排除特殊目录
return;
}
var fieldNodeList = root.DescendantNodes()?.OfType<FieldDeclarationSyntax>();
foreach (var field in fieldNodeList)
{
var filedName = field.Declaration.Variables.ToString();
var firstChar = filedName.First().ToString();
var tokens = field.ChildTokens();
foreach (var token in tokens)
{
//不能包含Public 变量
if (token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PublicKeyword))
{
//报错
var diagnostic = Diagnostic.Create(PublicVarDescriptor, field.GetFirstToken().GetLocation());
context.ReportDiagnostic(diagnostic);
break;//只检查一次
}
else if (token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PrivateKeyword)
|| token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.ProtectedKeyword)
|| token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.InternalKeyword)
|| token.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.IdentifierToken))
{
//其他:private protected 等等,使用_开头的小驼峰命名法
if (firstChar != "_" || filedName == filedName.ToUpper())
{
//报错
var diagnostic = Diagnostic.Create(PrivateVarDescriptor, field.GetFirstToken().GetLocation());
context.ReportDiagnostic(diagnostic);
}
break;//只检查一次
}
}
}
}
}
}
临时变量命名规范检查
1. 强制临时变量以小写字母开头;
命名规则使用同一个错误 ID:
public const string FORCE\_NAMING\_CONVENTIONS\_ID= "FERR1003";
using Analyzer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace CdeAnalyzer
{
/**
* Author: Laugh(笑微)
* https://developer.unity.cn/projects/65937455edbc2a001cbd8102
*/
[ ]
internal class ForceLocalVariableConventions : DiagnosticAnalyzer
{
/// <summary>
/// 错误描述
/// </summary>
private static readonly DiagnosticDescriptor LocalVarDescriptor =
new DiagnosticDescriptor(
DianogsticIDs.FORCE_NAMING_CONVENTIONS_ID, // ID
"临时变量命名不符合规范,请使用以字母开头的小驼峰命名法", // Title
"临时变量命名不符合规范,请使用以字母开头的小驼峰命名法", // Message format
DiagnosticCategories.Criterion, // Category
DiagnosticSeverity.Error, // Severity
isEnabledByDefault: true // Enabled by default
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(LocalVarDescriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeAction(AnalyzeSymbol);
}
private static void AnalyzeSymbol(SyntaxTreeAnalysisContext context)
{
//找到文档的语法根树
var root = context.Tree.GetRoot(context.CancellationToken);
if (ConstraintDefinition.ExcludeAnalize(context.Tree.FilePath))
{//排除特殊目录
return;
}
var localNodeList = root.DescendantNodes()?.OfType<LocalDeclarationStatementSyntax>();
foreach(var localNode in localNodeList)
{
var varList = localNode.Declaration.Variables;
foreach(var localVar in varList)
{
var localName = localVar.Identifier.Value.ToString();
var firstChar = localName.First().ToString();
if (firstChar.ToLower() != firstChar)
{//判断第一个字母是否是小写
//报错
var diagnostic = Diagnostic.Create(LocalVarDescriptor, localNode.GetFirstToken().GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
}
}
}
}