Table of Contents

Tier 分析器(TCSG130-139)——声明驱动的编译期纪律封堵

把仓库工程纪律写成 .editorconfig 声明,编译期直接报错:分层依赖方向、禁用线程原语、 禁反射、禁同步阻塞、禁 fire-and-forget 丢弃。零默认诊断——声明即启用:不声明规则的 工程,分析器完全沉默。

快速上手

三行 .editorconfig 声明 + 严重级配置,纪律即生效:

# 全局节:规则声明(tier_layer.* / tier_forbidden.* 键)
tier_forbidden.pack = reflection | sync_over_async | fire_and_forget
tier_layer.forbidden_reference = TC.Tier.Core.Net => TC.Tier.Products

# 分级:src 报错、tests 放行(按路径节覆盖)
[src/**/*.cs]
dotnet_diagnostic.TCSG136.severity = error

[tests/**/*.cs]
dotnet_diagnostic.TCSG130.severity = none

之后凡引用方向违规 / 裸 new Thread / 反射调用 / .Result 阻塞 / _ = Task.Run(...) 丢弃, 编译直接红——不需要任何运行时检查或 code review 约定。

配置读数通道(.editorconfig / .globalconfig 双通道)

规则键两个通道都收,跨通道同键异值 = TCSG139 fail-fast(绝静默取一):

通道 键落点 适用
.editorconfig(含无节全局区/路径节) per-tree options → 分析器全树并集 日常推荐——版本随仓、按目录分级
.globalconfigis_global = true GlobalOptions 消费方不想要仓库 editorconfig、或 CI 需要显式单文件注入时
  • 治理键声明全树一致是契约:同一键在不同源出现不同值即配置冲突报错;同值多源声明合法(单份执法)。
  • dotnet_diagnostic.* 严重级键走标准 Roslyn 通道,不受上述合并语义约束。

规则清单

分层依赖族(TCSG130-133,tier_layer.* 键声明)

诊断 拦截形态
TCSG130 禁止引用 A => B 声明的目标(元数据引用与 using 命名空间双形态独立检测)
TCSG131 程序集声明零内部依赖,却引用了内部家族程序集(前缀判定)
TCSG132 引用了白名单外程序集
TCSG133 公开类型落在声明命名空间前缀之外(命名空间即归属)

声明语法:tier_layer.forbidden_reference = <源命名空间> => <禁止目标> [| 更多目标] ; 分号分隔多条

白名单扩展键:tier_layer.allowed_framework_prefix = <前缀1> | <前缀2>——在缺省框架豁免集 (System / Microsoft / netstandard / mscorlib)之外追加豁免前缀。典型两例:

# 生态包前缀(本家族程序集不构成"越界依赖")
tier_layer.allowed_framework_prefix = TC.Tier.
# Windows SDK 侧隐式框架程序集(不在 System.*/Microsoft.* 自动豁免内)
tier_layer.allowed_framework_prefix = TC.Tier. | WindowsBase

禁用模式族(TCSG134-138,tier_forbidden.pack 键声明,| 选择启用)

诊断 拦截形态 修法
TCSG134 裸线程原语(new Thread / ThreadPool 直投) 后台循环一律 BackgroundWorkerLoop;受控后台任务一律 TaskSink
TCSG135 热路径方法禁用形态(启发式——漏报可能,不承诺完备) 按消息指引替换
TCSG136 反射调用(破坏 AOT/裁剪/性能) 白盒协作走 InternalsVisibleTo
TCSG137 同步阻塞后台 Task(.Result/.Wait()——死锁 + 线程池耗尽) 一律 await(后台句柄加 WaitAsync
TCSG138 fire-and-forget 丢弃(_ = Task.Run(...)——异常静默吞/无背压/生命周期失控;ValueTask 更危险) TaskSink.Submit/SubmitFast 或显式观测

声明语法:tier_forbidden.pack = reflection | sync_over_async | fire_and_forget | bare_threads | hotpath_discipline(任选子集)。

豁免

  • 个别调用点:标准 #pragma warning disable TCSG138 + 相邻理由注释(写明为何此处形态有 意——治理内核的受控丢弃即此形态);
  • 实现组件本体(TCSG134):.editorconfigtier_forbidden.exempt.bare_threads = <FQN 前缀列表> 声明豁免;
  • 整类降级dotnet_diagnostic.TCSGxxx.severity = none(按路径节精细控制——tests 目录 常见放行)。

豁免纪律:#pragma 裸豁免——必须带相邻理由注释说明"为什么此处形态有意";无理由的 豁免与违规同样对待。

注入与豁免名单

Directory.Build.props 把本包作为 Analyzer 引用全局注入所有项目——以下项目自豁免(分析器 自身与其测试需普通引用形态):TC.Tier.CodeGenTC.Tier.CodeGen.AnalyzersTC.Tier.CodeGen.AbstractionsTC.Tier.CodeGen.TestsTC.Tier.AnalyzersTC.Tier.Analyzers.Tests

反模式

  1. 声明了 tier_forbidden.pack 却在 tests 全量放行后又全局抱怨没约束——分级是刻意的: 纪律红线只压 src
  2. #pragma 藏性能问题——豁免必须带理由注释,code review 审豁免理由而不是审违规;
  3. 在分析器豁免名单里加业务项目——名单只容纳"分析器自身与生成器基础设施",业务工程 加进去等于自废门禁。

想深入

  • TierFs 协议规范分析器(TCSG120-122):TierFs 规范分析器
  • 生成器诊断(TCSG020/054-060 族):src/TC.Tier.CodeGen 各生成器源码头注释