如何使用TypeScript API创建自定义诊断信息?
在TypeScript自定义转换器中生成自定义诊断的可行方案
我之前也踩过这个坑——TypeScript的CustomTransformer API确实没直接提供输出诊断的入口,官方也把内部的诊断创建函数藏得很深,但其实我们可以通过几个绕路的方式实现需求,核心思路是不需要依赖内部API,直接构造符合规范的Diagnostic对象,下面是具体的实现方案:
1. 手动构造Diagnostic对象(核心基础)
虽然TypeScript没有公开Diagnostic的构造函数,但它本质是一个接口,只要我们创建的对象符合接口定义的结构,就能被TypeScript的诊断系统识别。比如:
import * as ts from "typescript"; function createCustomDiagnostic( node: ts.Node, message: string, category: ts.DiagnosticCategory = ts.DiagnosticCategory.Error, code: number = 9999 // 用自定义错误码,避免和官方冲突 ): ts.Diagnostic { const sourceFile = node.getSourceFile(); return { file: sourceFile, start: node.getStart(sourceFile), length: node.getWidth(sourceFile), messageText: message, category, code, // 可选:如果需要更详细的信息,可以添加source、relatedInformation等字段 }; }
这个函数完全基于公开API实现,不需要调用任何内部方法,就能生成合法的Diagnostic对象。
2. 在转换器中收集诊断(自定义上下文方案)
转换器本身无法直接输出诊断,但我们可以扩展TransformationContext,添加一个诊断收集的机制:
// 扩展上下文接口,添加诊断收集方法 interface DiagnosticAwareContext extends ts.TransformationContext { addDiagnostic(diag: ts.Diagnostic): void; getCollectedDiagnostics(): ts.Diagnostic[]; } // 创建包装后的上下文 function createDiagnosticContext(baseContext: ts.TransformationContext): DiagnosticAwareContext { const diagnostics: ts.Diagnostic[] = []; return { ...baseContext, addDiagnostic(diag) { diagnostics.push(diag); }, getCollectedDiagnostics() { return [...diagnostics]; // 返回副本避免外部修改 }, }; } // 自定义转换器 function myCustomTransformer(context: DiagnosticAwareContext) { return (sourceFile: ts.SourceFile) => { const visitor = (node: ts.Node): ts.Node => { // 示例:检测到不符合规则的节点时添加诊断 if (ts.isFunctionDeclaration(node) && !node.name) { const diag = createCustomDiagnostic( node, "不允许使用匿名函数声明", ts.DiagnosticCategory.Warning, 10001 ); context.addDiagnostic(diag); } return ts.visitEachChild(node, visitor, context); }; return visitor(sourceFile); }; }
在调用转换时,使用我们自定义的上下文,之后就能取出收集到的诊断:
// 假设已经获取了program和sourceFile const baseContext = ts.createTransformationContext(program.getCompilerOptions(), program); const diagnosticContext = createDiagnosticContext(baseContext); const transformedSource = myCustomTransformer(diagnosticContext)(sourceFile); const customDiagnostics = diagnosticContext.getCollectedDiagnostics(); // 之后可以把customDiagnostics和官方诊断合并输出 const allDiagnostics = [...program.getSemanticDiagnostics(sourceFile), ...customDiagnostics];
3. 集成到LanguageService中(如果需要在IDE中显示)
如果你的需求是让自定义诊断在IDE(比如VS Code)中显示,需要扩展LanguageService,重写诊断相关的方法,把自定义诊断合并进去:
class CustomLanguageService implements ts.LanguageService { private delegate: ts.LanguageService; private customDiagnosticsMap = new Map<ts.SourceFile, ts.Diagnostic[]>(); constructor(delegate: ts.LanguageService) { this.delegate = delegate; } // 重写获取语义诊断的方法 getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { const delegateDiagnostics = this.delegate.getSemanticDiagnostics(fileName); const sourceFile = this.delegate.getProgram()?.getSourceFile(fileName); const customDiagnostics = sourceFile ? this.customDiagnosticsMap.get(sourceFile) || [] : []; return [...delegateDiagnostics, ...customDiagnostics]; } // 其他方法直接委托给原LanguageService // ...(省略所有其他LanguageService方法的委托实现) // 提供方法来添加自定义诊断 addCustomDiagnostics(sourceFile: ts.SourceFile, diagnostics: ts.Diagnostic[]) { this.customDiagnosticsMap.set(sourceFile, diagnostics); } }
在完成转换后,调用addCustomDiagnostics把收集到的诊断传入,这样IDE就能获取到自定义诊断并显示了。
关键注意事项
- 自定义错误码尽量使用官方未占用的范围(比如9000+),避免冲突
- 确保Diagnostic的
start和length字段准确对应节点位置,这样IDE才能正确定位到错误 - 如果需要多文件的诊断管理,记得用Map或者其他结构按文件存储诊断
内容的提问来源于stack exchange,提问作者David Given
相关产品推荐
相关产品推荐

