诊断 API 
Trae IDE 的诊断 API 允许扩展报告和管理代码中的错误、警告和信息提示。
概述 
诊断功能包括:
- 错误和警告报告
- 代码问题标记
- 诊断信息管理
- 快速修复建议
- 诊断集合管理
主要接口 
Diagnostic 
typescript
interface Diagnostic {
  // 位置信息
  range: Range
  
  // 诊断内容
  message: string
  severity?: DiagnosticSeverity
  
  // 分类信息
  source?: string
  code?: string | number | {
    value: string | number
    target: Uri
  }
  
  // 关联信息
  relatedInformation?: DiagnosticRelatedInformation[]
  tags?: DiagnosticTag[]
}DiagnosticSeverity 
typescript
enum DiagnosticSeverity {
  Error = 0,
  Warning = 1,
  Information = 2,
  Hint = 3
}DiagnosticCollection 
typescript
interface DiagnosticCollection {
  readonly name: string
  
  // 设置诊断
  set(uri: Uri, diagnostics: Diagnostic[]): void
  set(entries: [Uri, Diagnostic[]][]): void
  
  // 删除诊断
  delete(uri: Uri): void
  clear(): void
  
  // 查询诊断
  get(uri: Uri): Diagnostic[] | undefined
  has(uri: Uri): boolean
  
  // 遍历
  forEach(callback: (uri: Uri, diagnostics: Diagnostic[], collection: DiagnosticCollection) => any, thisArg?: any): void
  
  // 销毁
  dispose(): void
}创建诊断集合 
基本创建 
typescript
import { languages, Diagnostic, DiagnosticSeverity, Range, Position } from 'trae-api'
// 创建诊断集合
const diagnosticCollection = languages.createDiagnosticCollection('myExtension')
// 创建诊断项
const diagnostic = new Diagnostic(
  new Range(new Position(0, 0), new Position(0, 10)),  // 位置范围
  '这里有一个错误',                                      // 错误消息
  DiagnosticSeverity.Error                              // 严重程度
)
// 设置诊断源
diagnostic.source = 'MyLinter'
diagnostic.code = 'E001'
// 添加到文件
diagnosticCollection.set(document.uri, [diagnostic])批量设置诊断 
typescript
// 为多个文件设置诊断
const diagnostics: [Uri, Diagnostic[]][] = [
  [Uri.file('/path/to/file1.js'), [
    new Diagnostic(
      new Range(0, 0, 0, 10),
      '未使用的变量',
      DiagnosticSeverity.Warning
    )
  ]],
  [Uri.file('/path/to/file2.js'), [
    new Diagnostic(
      new Range(5, 0, 5, 20),
      '语法错误',
      DiagnosticSeverity.Error
    )
  ]]
]
diagnosticCollection.set(diagnostics)诊断类型和严重程度 
错误诊断 
typescript
const errorDiagnostic = new Diagnostic(
  range,
  '语法错误:缺少分号',
  DiagnosticSeverity.Error
)
errorDiagnostic.source = 'TypeScript'
errorDiagnostic.code = 'TS1005'警告诊断 
typescript
const warningDiagnostic = new Diagnostic(
  range,
  '未使用的变量 "unusedVar"',
  DiagnosticSeverity.Warning
)
warningDiagnostic.source = 'ESLint'
warningDiagnostic.code = 'no-unused-vars'信息诊断 
typescript
const infoDiagnostic = new Diagnostic(
  range,
  '建议使用 const 而不是 let',
  DiagnosticSeverity.Information
)
infoDiagnostic.source = 'CodeStyle'提示诊断 
typescript
const hintDiagnostic = new Diagnostic(
  range,
  '可以简化为箭头函数',
  DiagnosticSeverity.Hint
)
hintDiagnostic.source = 'Refactoring'诊断标签 
不必要的代码 
typescript
import { DiagnosticTag } from 'trae-api'
const diagnostic = new Diagnostic(
  range,
  '未使用的导入',
  DiagnosticSeverity.Hint
)
diagnostic.tags = [DiagnosticTag.Unnecessary]已弃用的代码 
typescript
const deprecatedDiagnostic = new Diagnostic(
  range,
  '此方法已弃用,请使用 newMethod()',
  DiagnosticSeverity.Warning
)
deprecatedDiagnostic.tags = [DiagnosticTag.Deprecated]关联信息 
添加相关信息 
typescript
import { DiagnosticRelatedInformation, Location } from 'trae-api'
const diagnostic = new Diagnostic(
  range,
  '变量 "x" 已在此处声明',
  DiagnosticSeverity.Error
)
// 添加相关位置信息
diagnostic.relatedInformation = [
  new DiagnosticRelatedInformation(
    new Location(
      Uri.file('/path/to/other-file.js'),
      new Range(10, 0, 10, 10)
    ),
    '首次声明位置'
  ),
  new DiagnosticRelatedInformation(
    new Location(
      document.uri,
      new Range(5, 0, 5, 10)
    ),
    '之前的使用位置'
  )
]实用示例 
语法检查器 
typescript
class SyntaxChecker {
  private diagnosticCollection: DiagnosticCollection
  
  constructor() {
    this.diagnosticCollection = languages.createDiagnosticCollection('syntaxChecker')
  }
  
  async checkSyntax(document: TextDocument) {
    const diagnostics: Diagnostic[] = []
    const text = document.getText()
    const lines = text.split('\n')
    
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]
      
      // 检查缺少分号
      if (this.needsSemicolon(line)) {
        const diagnostic = new Diagnostic(
          new Range(i, line.length, i, line.length),
          '缺少分号',
          DiagnosticSeverity.Error
        )
        diagnostic.source = 'SyntaxChecker'
        diagnostic.code = 'missing-semicolon'
        diagnostics.push(diagnostic)
      }
      
      // 检查未使用的变量
      const unusedVars = this.findUnusedVariables(line, i)
      diagnostics.push(...unusedVars)
    }
    
    this.diagnosticCollection.set(document.uri, diagnostics)
  }
  
  private needsSemicolon(line: string): boolean {
    const trimmed = line.trim()
    return trimmed.length > 0 && 
           !trimmed.endsWith(';') && 
           !trimmed.endsWith('{') && 
           !trimmed.endsWith('}') &&
           !trimmed.startsWith('//')
  }
  
  private findUnusedVariables(line: string, lineNumber: number): Diagnostic[] {
    const diagnostics: Diagnostic[] = []
    const varRegex = /\b(let|const|var)\s+(\w+)/g
    let match
    
    while ((match = varRegex.exec(line)) !== null) {
      const varName = match[2]
      const startPos = match.index + match[1].length + 1
      const endPos = startPos + varName.length
      
      // 简单检查:如果变量名以下划线开头,认为是未使用的
      if (varName.startsWith('_')) {
        const diagnostic = new Diagnostic(
          new Range(lineNumber, startPos, lineNumber, endPos),
          `变量 "${varName}" 未使用`,
          DiagnosticSeverity.Warning
        )
        diagnostic.source = 'SyntaxChecker'
        diagnostic.code = 'unused-variable'
        diagnostic.tags = [DiagnosticTag.Unnecessary]
        diagnostics.push(diagnostic)
      }
    }
    
    return diagnostics
  }
  
  dispose() {
    this.diagnosticCollection.dispose()
  }
}代码质量检查器 
typescript
class CodeQualityChecker {
  private diagnosticCollection: DiagnosticCollection
  
  constructor() {
    this.diagnosticCollection = languages.createDiagnosticCollection('codeQuality')
  }
  
  async analyzeCode(document: TextDocument) {
    const diagnostics: Diagnostic[] = []
    const text = document.getText()
    
    // 检查函数复杂度
    const complexityIssues = this.checkComplexity(text)
    diagnostics.push(...complexityIssues)
    
    // 检查代码重复
    const duplicationIssues = this.checkDuplication(text)
    diagnostics.push(...duplicationIssues)
    
    // 检查命名规范
    const namingIssues = this.checkNaming(text)
    diagnostics.push(...namingIssues)
    
    this.diagnosticCollection.set(document.uri, diagnostics)
  }
  
  private checkComplexity(text: string): Diagnostic[] {
    const diagnostics: Diagnostic[] = []
    const lines = text.split('\n')
    
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]
      
      // 简单的复杂度检查:嵌套层级
      const indentLevel = (line.match(/^\s*/)?.[0].length || 0) / 2
      
      if (indentLevel > 4) {
        const diagnostic = new Diagnostic(
          new Range(i, 0, i, line.length),
          '代码嵌套层级过深,建议重构',
          DiagnosticSeverity.Information
        )
        diagnostic.source = 'CodeQuality'
        diagnostic.code = 'high-complexity'
        diagnostics.push(diagnostic)
      }
    }
    
    return diagnostics
  }
  
  private checkDuplication(text: string): Diagnostic[] {
    const diagnostics: Diagnostic[] = []
    const lines = text.split('\n')
    const lineMap = new Map<string, number[]>()
    
    // 记录每行内容出现的位置
    lines.forEach((line, index) => {
      const trimmed = line.trim()
      if (trimmed.length > 10) { // 只检查较长的行
        if (!lineMap.has(trimmed)) {
          lineMap.set(trimmed, [])
        }
        lineMap.get(trimmed)!.push(index)
      }
    })
    
    // 找出重复的行
    lineMap.forEach((positions, content) => {
      if (positions.length > 1) {
        positions.forEach(pos => {
          const diagnostic = new Diagnostic(
            new Range(pos, 0, pos, lines[pos].length),
            '发现重复代码,建议提取为函数',
            DiagnosticSeverity.Information
          )
          diagnostic.source = 'CodeQuality'
          diagnostic.code = 'code-duplication'
          
          // 添加其他重复位置的信息
          diagnostic.relatedInformation = positions
            .filter(p => p !== pos)
            .map(p => new DiagnosticRelatedInformation(
              new Location(Uri.file(''), new Range(p, 0, p, lines[p].length)),
              '其他重复位置'
            ))
          
          diagnostics.push(diagnostic)
        })
      }
    })
    
    return diagnostics
  }
  
  private checkNaming(text: string): Diagnostic[] {
    const diagnostics: Diagnostic[] = []
    const lines = text.split('\n')
    
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]
      
      // 检查变量命名(简单的驼峰命名检查)
      const varRegex = /\b(let|const|var)\s+([a-z][a-zA-Z0-9_]*)/g
      let match
      
      while ((match = varRegex.exec(line)) !== null) {
        const varName = match[2]
        
        // 检查是否使用了下划线(不符合驼峰命名)
        if (varName.includes('_') && !varName.startsWith('_')) {
          const startPos = match.index + match[1].length + 1
          const endPos = startPos + varName.length
          
          const diagnostic = new Diagnostic(
            new Range(i, startPos, i, endPos),
            `变量名 "${varName}" 建议使用驼峰命名`,
            DiagnosticSeverity.Information
          )
          diagnostic.source = 'CodeQuality'
          diagnostic.code = 'naming-convention'
          diagnostics.push(diagnostic)
        }
      }
    }
    
    return diagnostics
  }
}实时诊断更新 
typescript
class RealTimeDiagnostics {
  private diagnosticCollection: DiagnosticCollection
  private disposables: Disposable[] = []
  
  constructor() {
    this.diagnosticCollection = languages.createDiagnosticCollection('realTime')
    this.setupEventListeners()
  }
  
  private setupEventListeners() {
    // 监听文档变化
    this.disposables.push(
      workspace.onDidChangeTextDocument(event => {
        this.updateDiagnostics(event.document)
      })
    )
    
    // 监听文档打开
    this.disposables.push(
      workspace.onDidOpenTextDocument(document => {
        this.updateDiagnostics(document)
      })
    )
    
    // 监听文档关闭
    this.disposables.push(
      workspace.onDidCloseTextDocument(document => {
        this.diagnosticCollection.delete(document.uri)
      })
    )
  }
  
  private async updateDiagnostics(document: TextDocument) {
    // 防抖处理,避免频繁更新
    clearTimeout(this.updateTimer)
    this.updateTimer = setTimeout(() => {
      this.performDiagnostics(document)
    }, 500)
  }
  
  private updateTimer: NodeJS.Timeout | undefined
  
  private async performDiagnostics(document: TextDocument) {
    const diagnostics: Diagnostic[] = []
    
    // 执行各种检查
    const syntaxErrors = await this.checkSyntax(document)
    const styleIssues = await this.checkStyle(document)
    const logicWarnings = await this.checkLogic(document)
    
    diagnostics.push(...syntaxErrors, ...styleIssues, ...logicWarnings)
    
    this.diagnosticCollection.set(document.uri, diagnostics)
  }
  
  private async checkSyntax(document: TextDocument): Promise<Diagnostic[]> {
    // 语法检查逻辑
    return []
  }
  
  private async checkStyle(document: TextDocument): Promise<Diagnostic[]> {
    // 代码风格检查逻辑
    return []
  }
  
  private async checkLogic(document: TextDocument): Promise<Diagnostic[]> {
    // 逻辑检查逻辑
    return []
  }
  
  dispose() {
    this.disposables.forEach(d => d.dispose())
    this.diagnosticCollection.dispose()
    if (this.updateTimer) {
      clearTimeout(this.updateTimer)
    }
  }
}诊断管理 
清理诊断 
typescript
// 清理特定文件的诊断
diagnosticCollection.delete(document.uri)
// 清理所有诊断
diagnosticCollection.clear()
// 有条件地清理诊断
diagnosticCollection.forEach((uri, diagnostics) => {
  // 只保留错误级别的诊断
  const errors = diagnostics.filter(d => d.severity === DiagnosticSeverity.Error)
  if (errors.length !== diagnostics.length) {
    diagnosticCollection.set(uri, errors)
  }
})诊断过滤 
typescript
class DiagnosticFilter {
  static filterBySeverity(
    diagnostics: Diagnostic[], 
    severity: DiagnosticSeverity
  ): Diagnostic[] {
    return diagnostics.filter(d => d.severity === severity)
  }
  
  static filterBySource(
    diagnostics: Diagnostic[], 
    source: string
  ): Diagnostic[] {
    return diagnostics.filter(d => d.source === source)
  }
  
  static filterByRange(
    diagnostics: Diagnostic[], 
    range: Range
  ): Diagnostic[] {
    return diagnostics.filter(d => range.contains(d.range))
  }
}最佳实践 
- 性能优化: 使用防抖机制避免频繁更新诊断
- 用户体验: 提供清晰的错误消息和有用的相关信息
- 资源管理: 及时清理不需要的诊断信息
- 分类管理: 使用不同的诊断集合管理不同类型的问题
- 渐进式检查: 优先显示严重错误,然后是警告和提示