You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:46:09