UVP 价值专家 | 主程必杀技:Unity C# 代码整洁之道(二)

文摘   游戏   2024-05-17 20:02   上海  
这篇文章来自 2024 年度 Unity 价值专家提名人选 Laugh。Laugh 专注游戏研发及管理 15 年,目前在巨人网络担任技术负责人、专家,负责 MMO 类型的架构设计、产线设计及团队建设。他在 Unity 官方开发者社区发布了“主程必杀技”系列文章,探索使用 Source Generator 和 Analyzer 在 Unity 中实现代码规范框架要求的方式,帮助主程更高效地进行技术管理。
点击阅读原文,可以访问 Laugh 在 Unity 官方开发者社区的个人主页,阅读更多技术干货。

上一篇文章中,我们通过开发 Analyzer 实现了强制使用大括号包括逻辑分支 (if...else...) 的写法,不符合要求的代码会直接报 Error 且无法编译,方便主程进行代码规范管理和框架要求,而不再需要花费大量时间 Review。

本文为大家带来 Laugh 的“主程必杀技”系列文章第二篇《Unity Analyzer 自动化执行代码规范 -强制加注释(类注释/方法注释)》,系列其他文章将每周更新,或可访问 Laugh 的 Unity 官方开发者社区主页,持续关注学习。

演示代码

获取链接:https://gitee.com/palang/unity-sharp-code-regulator.git

C# Code Analyzer 入门系列文章
 Analyzer 初体验:强制使用大括号包括逻辑分支 (if...else...)
 强制加注释(上)类注释 | 强制加注释(下)方法注释
 命名规范检查(一)命名空间和类名
 命名规范检查(二)public 和 private 方法名称规范检查
 命名规范检查(三)成员变量命名规范检查
 命名规范检查(四)临时变量命名检查

 检查继承关系

Unity Analyzer 自动化执行代码规范

强制加注释

注释的重要性,主程们应该够能感同深受,这里不再啰嗦。但有多少项目拥有完整的注释,很多人也都心知肚明。今天举个小的例子,来说明如何给类和方法强制添加 xml 文档注释,个性化的需求可以自己添加。

一、环境准备

1. VS 2022
2. 版本:团结引擎 1.0
3. Analyzer 库项目类库框架选择 .Net Standard 2.0 (兼容 Unity) 

4. Microsoft.CodeAnalysis.CSharp,安装 3.8.0 版本 基本过程可以参考上一篇文章

二、实现目标

1. 强制所有类都加 xml 文档注释

2. 强制所有 public 方法都加 xml 文档注释

/// <summary>/// 这是一个测试类,主要用于强制注释的测试[/// </summary>public class TestClass](/// </summary>public class TestClass){    /// <summary>    /// 这是一个测试方法,用户测试方法注释    /// </summary>    public void Test()    {
}}
XML 文档注释的详细介绍: 

https://learn.microsoft.com/zh-CN/visualstudio/ide/reference/generate-xml-documentation-comments?view=vs-2019

三、编写 Analyzer 分析器

首先定义错误 ID 在类 DianogsticIDs 中添加成员

public const string FORCE_Comments_ID = "FERR1002";using System;using System.Collections.Generic;using System.Text;
namespace Analyzer{ public static class DianogsticIDs { public const string FORCE_BRACE_ID = "FERR1001"; public const string FORCE_Comments_ID = "FERR1002"; }}

然后在 AnalyzerReleases.Unshipped.md 文件添加该规则,如下:

; Unshipped analyzer release; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
### New RulesRule ID | Category | Severity | Notes--------|----------|----------|-------FERR1001 | Criterion | Error | ForceStatementBraceFERR1002 | Criterion | Error | ForceClassComments

  1. 类注释

新建分析器类 ForceClassComments.cs,内容如下:
using Analyzer;using Microsoft.CodeAnalysis;using Microsoft.CodeAnalysis.CSharp;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;using System.Text.RegularExpressions;/** * Author: Laugh(笑微) * https://developer.unity.cn/projects/65937455edbc2a001cbd8102 */namespace CdeAnalyzer{    [DiagnosticAnalyzer(LanguageNames.CSharp)]    internal class ForceClassComments : DiagnosticAnalyzer    {        /// <summary>        /// 错误描述        /// </summary>        private static readonly DiagnosticDescriptor ForceCommentsDescriptor =            new DiagnosticDescriptor(                DianogsticIDs.FORCE_Comments_ID,          // ID                "类必须添加XML文档注释",    // Title                "类必须添加XML文档注释", // Message format                DiagnosticCategories.Criterion,                // Category                DiagnosticSeverity.Error, // Severity                isEnabledByDefault: true    // Enabled by default            );        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(ForceCommentsDescriptor);        /// <summary>        /// 初始化分析方法        /// </summary>        /// <param name="context"></param>        public override void Initialize(AnalysisContext context)        {            context.RegisterSyntaxTreeAction(AnalyzeSymbol);        }        /// <summary>        /// 检测方法        /// </summary>        /// <param name="context"></param>        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 classDeclaration = (ClassDeclarationSyntax)cls;                var tokens = cls.ChildTokens();                foreach (var token in tokens)                {                    //要列举多种关键字:public, internal, protected, private, no keyword                    if(token.IsKind(SyntaxKind.PublicKeyword)                        || token.IsKind(SyntaxKind.InternalKeyword)                        || token.IsKind(SyntaxKind.ClassKeyword)                        || token.IsKind(SyntaxKind.ProtectedKeyword)                        || token.IsKind(SyntaxKind.PrivateKeyword))                    {                        //获取注释文本                        var comments = token.GetAllTrivia().ToSyntaxTriviaList().ToString();
//获取有效注释行 List<SyntaxTrivia> liables = new List<SyntaxTrivia>(); var triviaList = token.GetAllTrivia(); foreach (var trivia in triviaList) { if(trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)) { if(!trivia.ToString().Contains("<summary>") && !trivia.ToString().Contains("</summary>")) { liables.Add(trivia); } } }
var liablesStr = string.Join("",liables);
liablesStr = liablesStr.Replace("///", "").Trim(); if(liablesStr.Length < 4)/*注释长度过短,可以自己添加其他规则*/ { //报错 var diagnostic = Diagnostic.Create(ForceCommentsDescriptor, cls.GetFirstToken().GetLocation()); context.ReportDiagnostic(diagnostic); } } //只判断第一个符合条件的 break; } } } }}
代码可以直接使用。

  2. 方法注释

新建分析器类 ForceFunctionComments.cs,内容如下(具体看代码中的注释)

using Microsoft.CodeAnalysis.Diagnostics;using Microsoft.CodeAnalysis;using System;using System.Collections.Generic;using System.Text;using System.Collections.Immutable;using Analyzer;using Microsoft.CodeAnalysis.CSharp.Syntax;using Microsoft.CodeAnalysis.CSharp;using System.Linq;
namespace CdeAnalyzer{ [DiagnosticAnalyzer(LanguageNames.CSharp)] internal class FroceFunctionComments : DiagnosticAnalyzer { public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(ForceCommentsDescriptor); /// <summary> /// 错误描述 /// </summary> private static readonly DiagnosticDescriptor ForceCommentsDescriptor = new DiagnosticDescriptor( DianogsticIDs.FORCE_Comments_ID, // ID "类必须添加XML文档注释", // Title "类必须添加XML文档注释", // Message format DiagnosticCategories.Criterion, // Category DiagnosticSeverity.Error, // Severity isEnabledByDefault: true // Enabled by default );
/// <summary> /// 初始化分析方法 /// </summary> /// <param name="context"></param> public override void Initialize(AnalysisContext context) { context.RegisterSyntaxTreeAction(AnalyzeSymbol); }
/// <summary> /// 检测方法 /// </summary> /// <param name="context"></param> private static void AnalyzeSymbol(SyntaxTreeAnalysisContext context) { //找到文档的语法根树 var root = context.Tree.GetRoot(context.CancellationToken); //找到所有类定义 var methodNodeList = root.DescendantNodes()?.OfType<MethodDeclarationSyntax>(); foreach (var meth in methodNodeList) { var classDeclaration = (MethodDeclarationSyntax)meth; var tokens = meth.ChildTokens(); foreach (var token in tokens) { //关键字:public,其他关键字可以自行处理:internal, protected, private, no keyword if (token.IsKind(SyntaxKind.PublicKeyword)) { //获取注释文本 var comments = token.GetAllTrivia().ToSyntaxTriviaList().ToString();
//获取有效注释行 List<SyntaxTrivia> liables = new List<SyntaxTrivia>(); var triviaList = token.GetAllTrivia(); foreach (var trivia in triviaList) { if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)) { if (!trivia.ToString().Contains("<summary>") && !trivia.ToString().Contains("</summary>")) { liables.Add(trivia); } } }
var liablesStr = string.Join("", liables);
liablesStr = liablesStr.Replace("///", "").Trim(); if (liablesStr.Length < 4)/*注释长度过短,可以自己添加其他规则*/ { //报错 var diagnostic = Diagnostic.Create(ForceCommentsDescriptor, meth.GetFirstToken().GetLocation()); context.ReportDiagnostic(diagnostic); } } //只判断第一个符合条件的 break; } } } }}

代码可以直接使用。


下周将带来 Laugh 的“主程必杀技” Unity C# 代码整洁之道系列文章第三辑《命名规范检查》,敬请期待~
Laugh 是 2024 年度 Unity 价值专家提名人选。Unity 价值专家(UVP)是通过原创作品启发国内创作者的 Unity 专业人员,点击这里提名/自荐
长按关注
Unity 官方开发者服务平台
第一时间了解 Unity 社区动向,学习开发技巧

 点击“阅读原文”,访问 Laugh 的社区主页 



Unity官方开发者服务平台
Unity引擎官方开发者服务平台,分享技术干货、学习课程、产品信息、前沿案例、活动资讯、直播信息等内容。
 最新文章