17 KiB
AtomUI 编译期诊断规范
本文档定义 AtomUI 编译期 warning / error 的统一维护规则,适用于 Roslyn analyzer、source generator diagnostic、AOT 兼容诊断,以及后续 AXAML、Theme、Token 等可在编译期发现的问题。
编译期诊断的目标是把可静态证明的问题尽早暴露给开发者。诊断不是运行时异常说明,也不是一次性 bug 修复记录。
适用范围
新增或修改以下能力时必须遵循本文档:
DiagnosticAnalyzer报告的 warning / error。- source generator 通过
ReportDiagnostic报告的问题。 - AOT、trimming、动态成员访问、字符串 path 等编译期兼容性检查。
- 后续 AXAML、Theme、Token、资源注册等静态检查。
专项文档只描述领域背景和触发场景;诊断 ID、命名、severity、编码组织、测试要求统一维护在本文档中。
诊断 ID 规则
AtomUI 诊断 ID 使用固定格式:
ATOMUI<DOMAIN><NNN>
示例:
ATOMUIAOT001
ATOMUIGEN001
ATOMUIXAML001
ATOMUITOKEN001
规则:
ATOMUI是固定项目前缀。DOMAIN使用大写英文领域名,不加下划线、点号或连字符。NNN使用三位数字,从001开始递增。- ID 一旦发布,不能复用,不能改变语义。
- 诊断废弃时在注册表中标记 deprecated,不把旧 ID 分配给新问题。
.NET / Roslyn 生态通常使用 CS8602、CA1852、IDE0005、IL2026、SYSLIB0011、RS2008 这类“字母前缀 + 数字”格式。AtomUI 也采用这种格式,避免 ATOMUI_AOT_001、ATOMUI.AOT.001、ATOMUI-AOT-001。
这种格式更适合 .editorconfig 和 #pragma:
dotnet_diagnostic.ATOMUIAOT001.severity = error
#pragma warning disable ATOMUIAOT001
领域前缀
已保留的领域前缀:
| Prefix | 领域 | 用途 |
|---|---|---|
AOT |
AOT / trimming | NativeAOT、trimming、动态成员访问、字符串 path |
GEN |
Source generator | generator 输入、生成失败、生成物一致性 |
XAML |
AXAML | AXAML 静态结构、绑定、资源引用 |
TOKEN |
Theme / Token | Token 定义、注册、资源键、主题约束 |
LOC |
Localization | Language Catalog、XLIFF、语言元数据和静态语言包 |
LINK |
Linked Registration | AOT/Trim 注册管线:粒度、Unit 归属、sidecar 协议与应用计划 |
新增领域前缀前必须先更新本文档,说明用途和 owner。
当前诊断注册表
| ID | Category | Severity | Trigger | Fix | Owner |
|---|---|---|---|---|---|
ATOMUIAOT001 |
AOT | Warning | 已知 data member path 的 model type,但找不到 generated accessor | 给类型、接口或基类添加 [GenerateDataMemberAccessors],或显式传入 IDataMemberAccessorDescriptor |
DataMemberAccessors |
ATOMUIAOT002 |
AOT | Warning | model type 有 generated accessor,但目标 path 不会被生成 | 改成可访问实例属性、修正 path,或显式传入 descriptor | DataMemberAccessors |
ATOMUIAOT003 |
AOT | Warning | 使用字符串常量 path,编译期无法确认 model type | 使用 nameof(Type.Property),或显式传入 descriptor |
DataMemberAccessors |
ATOMUIGEN001 |
Generator | Warning | [GenerateScopedResourceHost] 标记的类型不是非泛型 partial 顶层 class |
改为非泛型 partial class,或移除该 attribute |
ScopedResourceHost |
ATOMUIGEN002 |
Generator | Warning | [GenerateScopedResourceHost] 标记的类型未继承 AvaloniaObject |
只在非 Visual AvaloniaObject 描述对象上使用该 attribute |
ScopedResourceHost |
ATOMUIGEN003 |
Generator | Warning | [GenerateScopedResourceHost] 标记的类型继承了 Control、StyledElement 或 Visual |
Visual 控件应使用视觉树资源宿主,不使用该 attribute | ScopedResourceHost |
ATOMUIGEN004 |
Generator | Warning | [GenerateScopedResourceHost] 标记的类型已经实现 IResourceHost 或 IThemeVariantHost |
删除手写实现后再使用该 attribute,或移除该 attribute | ScopedResourceHost |
ATOMUIGEN013 |
Generator | Error | Control Design Token 继承链包含未标记或具体中间层,或没有到达 AbstractControlDesignToken |
显式标记每个非根层,并把中间层改为 abstract |
ControlDesignToken |
ATOMUIGEN020 |
Generator | Error | 具体 [ControlDesignToken] 终端未声明 sealed |
封闭终端;需要复用时另建标记的抽象定义层 | ControlDesignToken |
ATOMUIGEN021 |
Generator | Error | Control Design Token 定义层或终端使用泛型 | 改为非泛型 Token 类型 | ControlDesignToken |
ATOMUIGEN022 |
Generator | Error | 继承链中的属性同名、隐藏或形成重复 schema key | 只保留一个具有稳定语义的属性声明 | ControlDesignToken |
ATOMUIGEN023 |
Generator | Error | Own Token 属性为 static、indexer、显式接口实现、访问器不可访问、init-only、required 或 virtual/abstract/override |
改为生成代码可访问的非 virtual 实例普通 get/set 属性,或使用 [NotTokenDefinition] 排除辅助属性 |
ControlDesignToken |
ATOMUIGEN024 |
Generator | Error | 后续层 CalculateTokenValues 没有把唯一的直接 base 调用放在 block body 第一条并原样转发参数 |
以 base.CalculateTokenValues(isDarkMode); 开始方法,再计算本层值 |
ControlDesignToken |
ATOMUIGEN025 |
Generator | Error | 抽象 Control Design Token 定义层名称不以非空 Token 后缀结束 |
使用表达家族语义的 *Token 名称 |
ControlDesignToken |
ATOMUIGEN026 |
Generator | Error | Control Design Token 声明为嵌套类型 | 把 Token 移到 namespace 下作为顶级类;泛型容器中的嵌套 Token 仍使用 ATOMUIGEN021 |
ControlDesignToken |
ATOMUIGEN027 |
Generator | Error | Semantic Part 的 Control/Part 声明、名称、path、class、cardinality 或 customization 非法 | 按 Semantic Part 声明契约修正 [SemanticPart] |
SemanticParts |
ATOMUIGEN028 |
Generator | Error | 同一 Control 的 Part name、path 或 selector class 重复 | 为每个 Part 使用唯一名称、path 和 .semantic-* class |
SemanticParts |
ATOMUIGEN029 |
Generator | Error | Semantic Part ContractType 不是 public StyledElement |
使用稳定且公开可引用的 Avalonia 类型 | SemanticParts |
ATOMUIGEN030 |
Generator | Error | SelectorAndTheme 缺少合法的 public get/set ControlTheme 属性契约 |
修正 ThemePropertyName、属性类型、public setter 或 ContractType |
SemanticParts |
ATOMUIGEN031 |
Generator | Warning | Semantic Part 未声明引入版本 | 设置非空 Since |
SemanticParts |
ATOMUIGEN032 |
Generator | Error | 某个 ControlTemplate 的 marker 数量不满足 cardinality | 为每个适用模板补齐或移除 marker,或修正 cardinality | SemanticParts |
ATOMUIGEN033 |
Generator | Error | marker 节点类型不能赋值给 ContractType |
把 class 放到兼容节点,或重新设计稳定 ContractType | SemanticParts |
ATOMUIGEN034 |
Generator | Error | Control 声明静态 Part,但不存在适用的可分析 ControlTemplate | 提供叶子 ControlTheme/ControlTemplate,或仅在真实动态创建场景使用 RuntimeCreated |
SemanticParts |
ATOMUIGEN035 |
Generator | Error | 同一 ControlTemplate 节点同时承担多个 Semantic Part | 将不同公开职责拆分到独立节点,每个节点只保留一个 Semantic Part marker | SemanticParts |
ATOMUIGEN036 |
Generator | Error | 已声明 Part 的 Classes.semantic-* marker 使用了 false 或动态值 |
使用静态 Classes.semantic-*="True";状态变化通过属性或伪类表达 |
SemanticParts |
ATOMUIGEN037 |
Generator | Error | Semantic Style 的完整 CLR identity 与另一生成候选、当前程序集已有类型或 canonical XML namespace 下可见的引用程序集 public 类型冲突 | 调整 Control 或 Part path 命名,或移除占用 AtomUI.Theme.Styling 生成 identity 的类型 |
SemanticParts |
ATOMUIGEN101 |
Generator | Warning | Gallery source code display generator 发现参与默认源码匹配的 ShowCasePanel 缺少 Name |
给 ShowCasePanel 设置稳定 Name,或使用显式源码 key 规则 |
GallerySourceCodeDisplay |
ATOMUILOC001 |
Localization | Error | AtomUI 固定语言数据记录的 schema、标识符、BCP 47 标签或元数据无效 | 按数据 schema 修正发生错误的具体记录 | LanguageTags |
ATOMUILOC002 |
Localization | Error | AtomUI 固定语言数据包含重复的属性标识符或规范 BCP 47 标签 | 删除重复记录并为每个属性和标签保留唯一映射 | LanguageTags |
ATOMUILOC003 |
Localization | Error | [LanguageCatalog] 声明不是可生成的稳定 enum 契约 |
使用 public 非泛型 enum、至少一个隐式顺序资源项且不使用 [Flags] |
LocalizationGenerator |
ATOMUILOC004 |
Localization | Error | Catalog 成员缺少显式正整数 ID,或 ID 重复、越界 | 为每个成员分配唯一且永久保留的显式正 Int32 ID |
LocalizationGenerator |
ATOMUILOC005 |
Localization | Error | XLIFF 文档不符合 AtomUI 支持的 XLIFF 2.1 结构或语言元数据规则 | 修正 XML namespace、版本、语言标签、file/unit/segment 结构或翻译状态 | LocalizationGenerator |
ATOMUILOC006 |
Localization | Error | XLIFF 的 Catalog identity、unit 或源契约与代码声明不匹配 | 使用目标 Catalog 导出的模板同步 file ID、unit Key、源文本、占位符和 fingerprint | LocalizationGenerator |
ATOMUILOC007 |
Localization | Error | 翻译文本不可发布或 CompositeFormat 参数契约无效 | 提供 translated target,并保持源/目标占位符索引与格式语法一致 | LocalizationGenerator |
ATOMUILOC008 |
Localization | Error | 应用类型无法实现生成式语言 bootstrap | 保留唯一的非抽象 partial Avalonia Application host,或移除应用级语言输入 | LocalizationGenerator |
ATOMUILOC009 |
Localization | Error | 静态语言包包含运行时代码/二进制、非法路径、缺失必需 metadata、未满足 Verified 要求或混合目标语言 | 删除运行时资产,并使用模板生成的声明式 contentFiles/buildTransitive 包结构 | AtomUI.Build.Tasks |
ATOMUILOC010 |
Localization | Warning | 静态语言包没有取得目标模块的权威 en-US 契约,打包只能执行延迟契约校验 |
添加作者期 PrivateAssets=all 组件 PackageReference;社区包也可保留 Deferred 并由消费应用完成完整校验 |
AtomUI.Build.Tasks |
ATOMUILINK001 |
LinkedRegistration | Error | linked 应用的 Application Plan owner 不唯一 | 保证只有一个入口工程满足 plan owner 条件 | LinkedPublishGenerator |
ATOMUILINK002 |
LinkedRegistration | Warning | 动态 AtomUI usage 无法解析到 Registration Unit,当前包使用 full fallback | Unit 已知时添加 AtomUIRegistrationUnitRoot,完全动态时添加 AtomUIPackageRoot |
LinkedPublishGenerator |
ATOMUILINK003 |
LinkedRegistration | Warning | 包没有兼容的 linked manifest,需要 full fallback | 升级包版本,或添加 AtomUIPackageRoot 显式声明边界 |
LinkedPublishGenerator |
ATOMUILINK004 |
LinkedRegistration | Error | 显式 root 无法解析 | 已知 Unit 使用 AtomUIRegistrationUnitRoot,整包使用 AtomUIPackageRoot |
LinkedPublishGenerator |
ATOMUILINK005 |
LinkedRegistration | Error | 包的粒度、Unit 或 PackageShared 声明非法,或 sidecar 候选未经解析到达 Generator | 修正包声明;消费端先按 assembly identity 与 contractHash 完成 canonical resolution | LinkedPublishGenerator、AtomUI.Build.Tasks |
ATOMUILINK006 |
LinkedRegistration | Error | linked-registration 输入 manifest 不兼容或损坏 | 修复 sidecar 版本或内容后重新生成 | AtomUI.Build.Tasks |
ATOMUILINK007 |
LinkedRegistration | Warning | loose AXAML 或动态资源源可能加载某包,该包使用 full fallback | 添加 AtomUIPackageRoot 显式声明边界 |
LinkedPublishGenerator |
ATOMUILINK008 |
LinkedRegistration | Error | 包被使用但其 UseXxxControls() 注册入口未被调用 |
在应用入口调用注册方法或声明对应 root | LinkedPublishGenerator |
ATOMUILINK009 |
LinkedRegistration | Error | Control package 注册入口方法无效 | 按 [ControlPackageRegistrationEntry] 契约修正入口签名 |
OrdinaryGenerator |
ATOMUILINK010 |
LinkedRegistration | Warning | 动态 usage 未被静态注册计划覆盖,仅经该点创建的控件不会注册 | Unit 已知时添加 AtomUIRegistrationUnitRoot,完全动态时添加 AtomUIPackageRoot;该点从不创建 AtomUI 控件时无需处理 |
LinkedPublishGenerator |
ATOMUILINK011 |
LinkedRegistration | Warning | [AotTrimUnit] attribute 与 AtomUIRegistrationUnit metadata 对同一文件声明了不同 Unit |
统一两处声明的 Unit 值,或删除其一 | LinkedPublishGenerator |
ATOMUILINK012 |
LinkedRegistration | Warning | 同一文件内多个类型声明了不同的显式 Registration Unit | 拆分文件,或统一为同一 Unit 值 | LinkedPublishGenerator |
ATOMUIGEN005 和 ATOMUIGEN006 原本约束 [ControlDesignToken] 类型上的 public const ID,该手工 ID 契约已
删除,因此这两个诊断不在新主题架构中复用。无参数 [ControlDesignToken] 本身继续保留,只负责标记 Own Token
类型,不携带 Control 类型或 identity。Control identity 由可主题化 Control 和主题资产约定生成;相关歧义、Token
继承、Global/Own 重名、资产依赖和注册错误应使用新的独立诊断 ID。
Severity 规则
- 默认使用
Warning。 - 只有生成结果必然错误、编译产物不可用、或继续编译会掩盖严重错误时才使用
Error。 - 对迁移期可能大量触发的问题,先使用
Warning,再由具体项目通过.editorconfig升级为Error。 - 不要用 analyzer warning 替代运行时 guard。编译期无法证明安全时,运行时仍要有明确异常或 fallback 边界。
代码组织
公共诊断定义集中放在:
src/AtomUI.Generator/Diagnostics/
├── AtomUIDiagnosticIds.cs
├── AtomUIDiagnosticCategories.cs
└── AtomUIDiagnosticDescriptors.cs
规则:
- diagnostic ID 必须来自
AtomUIDiagnosticIds。 - category 必须来自
AtomUIDiagnosticCategories。 DiagnosticDescriptor优先定义在AtomUIDiagnosticDescriptors。- analyzer 可以放在对应领域目录,例如
DataMemberAccessors/、DesignToken/、Language/。 - analyzer 不要在业务文件中散落硬编码 ID、category 或重复 descriptor。
Descriptor 编码规则
每个 DiagnosticDescriptor 必须满足:
Id使用AtomUIDiagnosticIds常量。Category使用AtomUIDiagnosticCategories常量。Title使用英文短句,不加句号。MessageFormat使用英文,必须包含触发对象,并给出修复方向。DefaultSeverity按本文档 severity 规则选择。isEnabledByDefault默认true。Location指向最小可修复语法节点,例如 path、attribute、类型名,而不是整个文件或整个 class。- 一个 diagnostic 只表达一个根因,不把多个修复方向混到一个 ID 中。
Analyzer 编码规则
- 只报告可以静态证明的问题;无法证明时可以报告“不可验证”类诊断,但不能编造具体类型或 path。
- 避免对 generated code 报告诊断,除非诊断专门检查 generated code。
- 不要在 analyzer 中执行昂贵的全编译扫描;优先注册具体 syntax / symbol action。
- 不要依赖源码字符串匹配判断语义;使用
SemanticModel、ISymbol、ITypeSymbol等 Roslyn API。 - 诊断位置要落在用户需要修改的表达式上。
- analyzer 自身不能引入运行时依赖,必须保持可作为 analyzer 包加载。
测试规则
每个新增 diagnostic 必须包含:
- 正例:应报告,并断言 ID、数量和关键 message。
- 反例:正确写法不报告。
- 边界:无法静态证明时不误报,或按设计报告“不可验证”类诊断。
- 如果诊断与 AOT 相关,至少覆盖 generated accessor、显式 descriptor 或等价修复路径中的一种。
测试应放在对应 generator 测试项目中:
tests/AtomUI.Generator.Tests/
测试中如果故意验证 fallback 或错误路径,需要用局部 #pragma warning disable <ID> 标明意图,不要在项目级别全局 suppress。
文档要求
新增 diagnostic 时必须同步更新:
- 本文档的诊断注册表。
- 对应领域文档,例如 AOT 规则写到 aot-programming-guidelines.md。
- 如果诊断需要用户迁移,补充修复示例。