Appearance
术语分册 7:附加文件、配置、诊断与文本
这一册负责解释“输入不再只有 C# 源码时怎么办”,以及“怎么主动报诊断、怎么处理源码文本”。
AdditionalTextsProvider
- 一句话说明:把项目里的
AdditionalFiles接入增量管道 - 什么时候会用到:你要读取
.graphql、.json、模板文件等附加输入 - 对应教程:第 12 章
源码入口:
sample/09-graphql-query-generator/SampleApp/SampleApp.csprojsample/09-graphql-query-generator/GraphQLGenerator/GraphQLGenerator.cs
关键片段:
xml
<ItemGroup>
<AdditionalFiles Include="schema.graphql" />
</ItemGroup>csharp
var graphqlFiles = context.AdditionalTextsProvider
.Where(file => file.Path.EndsWith(".graphql"));为什么看它:
.csproj里的AdditionalFiles决定附加文件能不能被生成器看到AdditionalTextsProvider则是生成器侧接收这些文件的入口- 这两个片段要一起看,才能把“项目声明输入”和“生成器读取输入”串起来
AdditionalText
- 一句话说明:表示一份附加文件
- 什么时候会用到:你要读附加文件的路径和文本
关键成员:
| 成员 | 能拿到什么 |
|---|---|
Path | 文件路径 |
GetText(...) | 文件文本内容 |
AnalyzerConfigOptionsProvider
- 一句话说明:读取全局、语法树或附加文件的分析器配置
- 什么时候会用到:你想让生成行为受 MSBuild 属性或配置影响时
当前仓库状态:
- 当前仓库没有完整消费
AnalyzerConfigOptionsProvider的正式 sample - 所以这里先把对象职责讲清楚,不把它伪装成已经有现成案例
关键成员:
| 成员 | 能拿到什么 |
|---|---|
GlobalOptions | 全局配置 |
GetOptions(syntaxTree) | 某棵语法树的配置 |
GetOptions(additionalText) | 某个附加文件的配置 |
AnalyzerConfigOptions
- 一句话说明:实际的配置键值容器
- 什么时候会用到:你已经拿到配置对象,准备读取具体值
关键成员:
| 成员 | 能拿到什么 |
|---|---|
Keys | 可枚举的键 |
TryGetValue(...) | 读取某个配置值 |
KeyComparer | 键比较规则 |
DiagnosticDescriptor
- 一句话说明:定义一类诊断的模板
- 什么时候会用到:你要规范地定义错误、警告、提示时
源码入口 1:
sample/05-dto-mapper-generator/DtoMapperGenerator/Analyzers/DiagnosticDescriptors.cs
关键片段:
csharp
public static readonly DiagnosticDescriptor IncompatibleTypes = new(
id: "DTOMAPPER001",
title: "属性类型不兼容",
messageFormat: "无法将属性 '{0}' 从类型 '{1}' 映射到 '{2}'",
category: Category,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);为什么看它:
- 这个样例很适合理解
DiagnosticDescriptor就是“诊断模板” - 你能直观看到
id、标题、消息模板和严重级别如何被统一定义
源码入口 2:
sample/08-validator-generator/ValidatorGenerator/DiagnosticDescriptors.cs
为什么看它:
sample/08会把诊断和约束检查绑得更紧- 适合在看完
sample/05之后继续理解“业务规则越多,诊断定义越重要”
关键成员:
| 成员 | 能拿到什么 |
|---|---|
Id | 诊断 ID |
Title | 标题 |
MessageFormat | 消息模板 |
DefaultSeverity | 默认严重级别 |
IsEnabledByDefault | 默认是否启用 |
Diagnostic
- 一句话说明:一次具体的诊断实例
- 什么时候会用到:你已经有模板,准备生成某次具体报错
关键成员:
| 成员 | 能拿到什么 |
|---|---|
Descriptor | 对应模板 |
Id | 诊断 ID |
Severity | 严重级别 |
Location | 诊断位置 |
GetMessage() | 最终消息 |
Create(...) | 创建实例 |
Location
- 一句话说明:诊断应该挂到哪里
- 什么时候会用到:你要把错误挂到类名、属性名或具体语法节点上
关键成员:
| 成员 | 能拿到什么 |
|---|---|
None | 无具体位置 |
IsInSource | 是否位于源码中 |
SourceTree | 对应语法树 |
SourceSpan | 文本范围 |
GetLineSpan() | 行列范围 |
ReportDiagnostic(...)
- 一句话说明:把诊断真正上报给编译器和 IDE
- 什么时候会用到:你已经创建好
Diagnostic
典型链路:
text
DiagnosticDescriptor -> Diagnostic.Create(...) -> ReportDiagnostic(...)SourceText
- 一句话说明:带编码信息的源码文本对象
- 什么时候会用到:你想显式控制编码,或后续需要文本对象本身
关键成员:
| 成员 | 能拿到什么 |
|---|---|
Encoding | 文本编码 |
Length | 文本长度 |
Lines | 分行结果 |
ToString() | 文本内容 |
SourceText.From(...)
- 一句话说明:从字符串或流构造
SourceText - 什么时候会用到:你要显式指定
Encoding.UTF8时
源码入口 1:
sample/02-builder-generator/BuilderGenerator/BuilderGenerator.cs
关键片段:
csharp
ctx.AddSource("GenerateBuilderAttribute.g.cs", SourceText.From(attributeSource, Encoding.UTF8));为什么看它:
- 这是最简单的
SourceText.From(...)用法 - 适合先理解“为什么有人不直接把字符串传给
AddSource(...)”
源码入口 2:
sample/09-graphql-query-generator/GraphQLGenerator/GraphQLGenerator.cs
关键片段:
csharp
context.AddSource($"{type.Name}.g.cs", SourceText.From(code, Encoding.UTF8));为什么看它:
- 这个项目展示了
SourceText.From(...)在大批量输出场景里的常见用法 - 当生成内容来自复杂拼装逻辑时,显式走
SourceText会更清晰
CancellationToken
- 一句话说明:表示当前编译流程是否要求提前停止
- 什么时候会用到:你的转换、文件读取、长循环可能比较重时
最常见使用位置:
| 位置 | 意义 |
|---|---|
predicate / transform 参数 | 当前分析步骤可被取消 |
SourceProductionContext.CancellationToken | 当前输出步骤可被取消 |
file.GetText(token) | 读取附加文件时支持取消 |
最常见的扩展链路
csharp
var files = context.AdditionalTextsProvider
.Where(file => file.Path.EndsWith(".graphql"));
var options = context.AnalyzerConfigOptionsProvider.GlobalOptions;
var diagnostic = Diagnostic.Create(descriptor, location, args);
spc.ReportDiagnostic(diagnostic);下一步去哪里
- 想看驱动和附加文件的完整扩展示例:看 advanced/03-additional-files-and-config.md
- 想看诊断、文本和通用语法驱动专题:看 advanced/04-diagnostics-sourcetext-and-syntax-provider.md
- 返回索引:看 术语与 API 手册