Skip to main content
The Diagnostics API provides access to syntax errors, type errors, and code suggestions. This powers error highlighting and the Problems panel in editors.

Overview

TypeScript provides three types of diagnostics:

Syntactic

Fast syntax-only errors

Semantic

Type system errors

Suggestions

Code improvement hints

getSyntacticDiagnostics

Returns syntax errors in a file without type checking.

Signature

function getSyntacticDiagnostics(
  fileName: string
): DiagnosticWithLocation[];
fileName
string
required
Path to the source file to check.

Return Value

interface DiagnosticWithLocation extends Diagnostic {
  file: SourceFile;              // Source file containing the error
  start: number;                 // Character offset of error start
  length: number;                // Length of the error span
}

interface Diagnostic {
  category: DiagnosticCategory;  // Error, Warning, Suggestion, Message
  code: number;                  // Error code (e.g., 1005)
  messageText: string | DiagnosticMessageChain;
  source?: string;               // Source of diagnostic (e.g., "ts")
  reportsUnnecessary?: boolean;  // Code is unnecessary
  reportsDeprecated?: boolean;   // Code is deprecated
}

Characteristics

Fast

No type checking required

Local

Only analyzes single file

Always Available

Works even with type errors

Subset

Some errors need semantics

Example: Syntactic Diagnostics

Syntax Errors
import * as ts from 'typescript';

const code = `
function add(a, b {
  return a + b
}

const x = 42;
const y = "hello"
`;

const sourceFile = ts.createSourceFile(
  'test.ts',
  code,
  ts.ScriptTarget.Latest
);

const diagnostics = service.getSyntacticDiagnostics('test.ts');

diagnostics.forEach(diagnostic => {
  const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
    diagnostic.start
  );
  
  const message = typeof diagnostic.messageText === 'string'
    ? diagnostic.messageText
    : diagnostic.messageText.messageText;
  
  console.log(
    `${diagnostic.file.fileName}:${line + 1}:${character + 1}`,
    `- ${message} (${diagnostic.code})`
  );
});

getSemanticDiagnostics

Returns type system errors and warnings for a file.

Signature

function getSemanticDiagnostics(
  fileName: string
): Diagnostic[];
fileName
string
required
Path to the source file to check.

Return Value

Returns Diagnostic[] which may not have location information for global errors.
The first call to getSemanticDiagnostics may be slow as it initializes the type system. Subsequent calls are faster.

Characteristics

Slower

Requires type checking

Global

Analyzes across files

Complete

All type errors

Incremental

Caches results

Example: Semantic Diagnostics

Type Errors
const code = `
function greet(name: string): string {
  return "Hello, " + name;
}

// Type error: number is not assignable to string
greet(42);

interface User {
  name: string;
  age: number;
}

const user: User = {
  name: "Alice"
  // Missing property 'age'
};

// Type error: Property 'foo' does not exist
user.foo;
`;

const diagnostics = service.getSemanticDiagnostics('app.ts');

diagnostics.forEach(diagnostic => {
  if (diagnostic.file) {
    const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
      diagnostic.start!
    );
    
    console.log(
      `${diagnostic.file.fileName}:${line + 1}:${character + 1}`
    );
  }
  
  const message = ts.flattenDiagnosticMessageText(
    diagnostic.messageText,
    '\n'
  );
  
  console.log(`  ${message}`);
  console.log(`  Error code: ${diagnostic.code}\n`);
});

getSuggestionDiagnostics

Returns suggestion diagnostics that proactively recommend improvements.

Signature

function getSuggestionDiagnostics(
  fileName: string
): DiagnosticWithLocation[];

Suggestion Types

Code that can be removed:
  • Unused variables
  • Unused imports
  • Unreachable code
import { unused } from './module'; // Suggestion: Remove unused import

function test() {
  const x = 1; // Suggestion: 'x' is declared but never used
  return;
  console.log('unreachable'); // Suggestion: Unreachable code
}
Functions that could be async:
// Suggestion: This may be converted to an async function
function fetchData() {
  return fetch('/api').then(r => r.json());
}

// Better:
async function fetchData() {
  const response = await fetch('/api');
  return response.json();
}
Usage of deprecated APIs:
/** @deprecated Use newFunction instead */
function oldFunction() { }

// Suggestion: 'oldFunction' is deprecated
oldFunction();

Example: Suggestions

Suggestion Diagnostics
const code = `
import { unused, used } from './utils';

function process() {
  console.log(used);
}

function convertToAsync() {
  return Promise.resolve(42).then(x => x * 2);
}

/** @deprecated Use newApi instead */
function oldApi() { }

oldApi();
`;

const suggestions = service.getSuggestionDiagnostics('app.ts');

suggestions.forEach(suggestion => {
  const { line, character } = suggestion.file.getLineAndCharacterOfPosition(
    suggestion.start
  );
  
  console.log(`Suggestion at ${line + 1}:${character + 1}`);
  console.log(`  ${suggestion.messageText}`);
  
  if (suggestion.reportsUnnecessary) {
    console.log('  (Unnecessary code)');
  }
  
  if (suggestion.reportsDeprecated) {
    console.log('  (Deprecated)');
  }
});

getCompilerOptionsDiagnostics

Returns diagnostics related to compiler options and configuration.

Signature

function getCompilerOptionsDiagnostics(): Diagnostic[];

Example: Configuration Errors

Config Diagnostics
// tsconfig.json has invalid options
const diagnostics = service.getCompilerOptionsDiagnostics();

diagnostics.forEach(diagnostic => {
  console.log(diagnostic.messageText);
  console.log(`Error code: ${diagnostic.code}`);
});

Diagnostic Categories

enum DiagnosticCategory {
  Warning = 0,
  Error = 1,
  Suggestion = 2,
  Message = 3,
}

Filtering by Category

Filter Diagnostics
const allDiagnostics = [
  ...service.getSyntacticDiagnostics('app.ts'),
  ...service.getSemanticDiagnostics('app.ts')
];

// Get only errors
const errors = allDiagnostics.filter(
  d => d.category === ts.DiagnosticCategory.Error
);

// Get only warnings
const warnings = allDiagnostics.filter(
  d => d.category === ts.DiagnosticCategory.Warning
);

console.log(`Found ${errors.length} errors and ${warnings.length} warnings`);

Formatting Diagnostics

Format for Display
import * as ts from 'typescript';

function formatDiagnostic(diagnostic: ts.Diagnostic): string {
  if (diagnostic.file) {
    const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
      diagnostic.start!
    );
    
    const message = ts.flattenDiagnosticMessageText(
      diagnostic.messageText,
      '\n',
      0
    );
    
    const category = ts.DiagnosticCategory[diagnostic.category].toLowerCase();
    
    return (
      `${diagnostic.file.fileName}(${line + 1},${character + 1}): ` +
      `${category} TS${diagnostic.code}: ${message}`
    );
  } else {
    return ts.flattenDiagnosticMessageText(
      diagnostic.messageText,
      '\n'
    );
  }
}

const diagnostics = service.getSemanticDiagnostics('app.ts');
diagnostics.forEach(d => console.log(formatDiagnostic(d)));

Diagnostic Message Chains

Some diagnostics have nested messages:
interface DiagnosticMessageChain {
  messageText: string;
  category: DiagnosticCategory;
  code: number;
  next?: DiagnosticMessageChain[];
}

Flattening Message Chains

Flatten Messages
function flattenDiagnosticMessageText(
  diagnostic: ts.Diagnostic
): string {
  if (typeof diagnostic.messageText === 'string') {
    return diagnostic.messageText;
  }
  
  return ts.flattenDiagnosticMessageText(
    diagnostic.messageText,
    '\n',
    0
  );
}

// Example chained message:
// "Type 'X' is not assignable to type 'Y'.
//   Types of property 'foo' are incompatible.
//     Type 'A' is not assignable to type 'B'."

Real-World Integration

Complete Diagnostic Provider
import * as ts from 'typescript';

class DiagnosticProvider {
  constructor(private service: ts.LanguageService) {}

  async getDiagnostics(
    fileName: string,
    options?: {
      includeSyntactic?: boolean;
      includeSemantic?: boolean;
      includeSuggestions?: boolean;
    }
  ) {
    const { 
      includeSyntactic = true,
      includeSemantic = true,
      includeSuggestions = false
    } = options || {};

    const diagnostics: ts.Diagnostic[] = [];

    if (includeSyntactic) {
      diagnostics.push(
        ...this.service.getSyntacticDiagnostics(fileName)
      );
    }

    if (includeSemantic) {
      diagnostics.push(
        ...this.service.getSemanticDiagnostics(fileName)
      );
    }

    if (includeSuggestions) {
      diagnostics.push(
        ...this.service.getSuggestionDiagnostics(fileName)
      );
    }

    return diagnostics.map(d => this.formatDiagnostic(d));
  }

  private formatDiagnostic(diagnostic: ts.Diagnostic) {
    const severity = this.getSeverity(diagnostic);
    const message = this.getMessage(diagnostic);
    const location = this.getLocation(diagnostic);
    const codeActions = this.getCodeFixes(diagnostic);

    return {
      severity,
      message,
      code: diagnostic.code,
      source: diagnostic.source || 'ts',
      location,
      codeActions,
      tags: this.getTags(diagnostic)
    };
  }

  private getSeverity(diagnostic: ts.Diagnostic) {
    switch (diagnostic.category) {
      case ts.DiagnosticCategory.Error:
        return 'error';
      case ts.DiagnosticCategory.Warning:
        return 'warning';
      case ts.DiagnosticCategory.Suggestion:
        return 'information';
      case ts.DiagnosticCategory.Message:
        return 'hint';
    }
  }

  private getMessage(diagnostic: ts.Diagnostic) {
    return ts.flattenDiagnosticMessageText(
      diagnostic.messageText,
      '\n'
    );
  }

  private getLocation(diagnostic: ts.Diagnostic) {
    if (!diagnostic.file || diagnostic.start === undefined) {
      return null;
    }

    const start = diagnostic.file.getLineAndCharacterOfPosition(
      diagnostic.start
    );
    const end = diagnostic.file.getLineAndCharacterOfPosition(
      diagnostic.start + (diagnostic.length || 0)
    );

    return {
      file: diagnostic.file.fileName,
      range: {
        start: { line: start.line, character: start.character },
        end: { line: end.line, character: end.character }
      }
    };
  }

  private getCodeFixes(diagnostic: ts.Diagnostic) {
    if (!diagnostic.file || diagnostic.start === undefined) {
      return [];
    }

    const fixes = this.service.getCodeFixesAtPosition(
      diagnostic.file.fileName,
      diagnostic.start,
      diagnostic.start + (diagnostic.length || 0),
      [diagnostic.code],
      {},
      {}
    );

    return fixes.map(fix => ({
      title: fix.description,
      kind: fix.fixName,
      edits: this.convertTextChanges(fix.changes)
    }));
  }

  private getTags(diagnostic: ts.Diagnostic) {
    const tags: string[] = [];
    
    if (diagnostic.reportsUnnecessary) {
      tags.push('unnecessary');
    }
    
    if (diagnostic.reportsDeprecated) {
      tags.push('deprecated');
    }
    
    return tags;
  }

  private convertTextChanges(changes: readonly ts.FileTextChanges[]) {
    return changes.map(change => ({
      file: change.fileName,
      edits: change.textChanges.map(tc => ({
        range: {
          start: tc.span.start,
          end: tc.span.start + tc.span.length
        },
        newText: tc.newText
      }))
    }));
  }
}

// Usage
const provider = new DiagnosticProvider(service);

// Get all diagnostics
const diagnostics = await provider.getDiagnostics('app.ts', {
  includeSyntactic: true,
  includeSemantic: true,
  includeSuggestions: true
});

diagnostics.forEach(diagnostic => {
  console.log(`[${diagnostic.severity}] ${diagnostic.message}`);
  if (diagnostic.location) {
    console.log(`  at ${diagnostic.location.file}:${diagnostic.location.range.start.line + 1}`);
  }
  if (diagnostic.codeActions.length > 0) {
    console.log('  Available fixes:');
    diagnostic.codeActions.forEach(action => {
      console.log(`    - ${action.title}`);
    });
  }
});

Performance Tips

Always call getSyntacticDiagnostics before getSemanticDiagnostics. Syntax errors are faster to compute and may make semantic checking unnecessary.
const syntactic = service.getSyntacticDiagnostics('app.ts');
if (syntactic.length === 0) {
  // Only check semantics if syntax is valid
  const semantic = service.getSemanticDiagnostics('app.ts');
}
Diagnostic results are cached by the Language Service. Don’t recompute unless the file has changed.
const cache = new Map<string, ts.Diagnostic[]>();

function getDiagnosticsWithCache(fileName: string, version: string) {
  const key = `${fileName}:${version}`;
  if (cache.has(key)) {
    return cache.get(key)!;
  }
  
  const diagnostics = service.getSemanticDiagnostics(fileName);
  cache.set(key, diagnostics);
  return diagnostics;
}
Only check changed files, not the entire project:
const changedFiles = getChangedFilesSinceLastCheck();

for (const fileName of changedFiles) {
  const diagnostics = [
    ...service.getSyntacticDiagnostics(fileName),
    ...service.getSemanticDiagnostics(fileName)
  ];
  updateDiagnosticsForFile(fileName, diagnostics);
}
Don’t check on every keystroke. Debounce diagnostic requests:
let diagnosticTimer: NodeJS.Timeout;

function scheduleCheck(fileName: string) {
  clearTimeout(diagnosticTimer);
  diagnosticTimer = setTimeout(() => {
    const diagnostics = getDiagnostics(fileName);
    showDiagnostics(diagnostics);
  }, 500); // Wait 500ms after last change
}

See Also

Language Service API

Main Language Service reference

Completions

Code completion API

Program API

Program and compilation

Type Checker

Type system internals

Build docs developers (and LLMs) love