Skip to content

术语分册 7:附加文件、配置、诊断与文本

这一册负责解释“输入不再只有 C# 源码时怎么办”,以及“怎么主动报诊断、怎么处理源码文本”。

AdditionalTextsProvider

  • 一句话说明:把项目里的 AdditionalFiles 接入增量管道
  • 什么时候会用到:你要读取 .graphql.json、模板文件等附加输入
  • 对应教程:第 12 章

源码入口:

  • sample/09-graphql-query-generator/SampleApp/SampleApp.csproj
  • sample/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);

下一步去哪里

基于当前仓库文档副本构建的 VitePress 站点