Open-Source Wikis

/

TypeScript

/

Systems

/

Diagnostics

microsoft/TypeScript

Diagnostics

How a single user-visible compiler error or warning is defined, raised, formatted, and surfaced.

Source

File Role
src/compiler/diagnosticMessages.json Master list of every diagnostic message (~2,130 entries, 8,559 lines)
src/compiler/diagnosticInformationMap.generated.ts Generated lookup map (created by hereby generate-diagnostics)
scripts/processDiagnosticMessages.mjs The generator
src/compiler/programDiagnostics.ts Program-level diagnostic stream + dedup
src/compiler/utilities.ts createDiagnosticForNode, chainDiagnosticMessages, formatting helpers
src/compiler/types.ts Diagnostic, DiagnosticMessage, DiagnosticCategory, DiagnosticMessageChain types

Purpose

Diagnostics are the compiler's user-facing output. They have to be:

  • Localisable. The English message lives in diagnosticMessages.json; translations live in src/loc/lcl/<locale>/diagnosticMessages.generated.json.lcl. The --locale flag and the editor's language pick the right translation.
  • Stable. Each entry has a numeric code (e.g., 2304: "Cannot find name '{0}'.") that scripts and editors rely on. New entries get new codes; existing codes don't change meaning.
  • Categorised. Each is Error, Warning, Suggestion, or Message. Suggestions only surface in the language service.
  • Locatable. Each Diagnostic carries a source file, position, and length so editors can underline the relevant code.
  • Chainable. A single error often has nested context ("Type X is not assignable to type Y" → "Property 'foo' is missing"). DiagnosticMessageChain represents the cascade.

How it works

graph LR
    Json["diagnosticMessages.json"] -->|hereby generate-diagnostics| Gen["diagnosticInformationMap.generated.ts"]
    Gen --> Code["Code references Diagnostics.X"]
    Code --> Create["createDiagnosticForNode / error()"]
    Create --> Stream["Diagnostic stream on Program / SourceFile"]
    Stream --> Format["formatDiagnostic / formatDiagnosticsWithColorAndContext"]
    Format --> Out["stderr (tsc) / language service / IDE"]

Defining a new diagnostic

  1. Add an entry to diagnosticMessages.json:

    "Property '{0}' does not exist on type '{1}'.": {
        "category": "Error",
        "code": 2339
    }
  2. Run hereby generate-diagnostics. This regenerates diagnosticInformationMap.generated.ts with a Diagnostics.Property_0_does_not_exist_on_type_1 constant.

  3. Reference it in code:

    error(
      node,
      Diagnostics.Property_0_does_not_exist_on_type_1,
      propName,
      typeStr
    );

The constant key is derived deterministically from the message — replacing punctuation with underscores and trimming. This is why diagnostic messages are stable strings, not just codes.

Formatting

tsc formats diagnostics for the terminal in two flavours:

  • formatDiagnostic(diagnostic, host) — single-line: path/to/file.ts(12,5): error TS2339: ....
  • formatDiagnosticsWithColorAndContext(diagnostics, host) — colourful, multi-line, with source-context excerpts. Default when stdout is a TTY.

The language service returns diagnostics raw and lets the editor handle presentation.

Diagnostic chaining

A type error like:

Type '{ a: number; }' is not assignable to type '{ a: string; b: number; }'.
  Types of property 'a' are incompatible.
    Type 'number' is not assignable to type 'string'.

is one root Diagnostic whose messageText is a DiagnosticMessageChain linked-list. The checker builds these chains as it descends into structural-comparison failures via chainDiagnosticMessages.

A Diagnostic can have relatedInformation: DiagnosticRelatedInformation[], each pointing to a different file/range. The checker uses this for "see also" hints — e.g., "the conflicting declaration is here" with a pointer to the other declaration.

Comment directives

// @ts-ignore, // @ts-expect-error, and // @ts-nocheck suppress diagnostics on the next line / whole file. The parser records these in SourceFile.commentDirectives. The diagnostic stream filters against the directive list at output time. @ts-expect-error reports a new diagnostic if the suppressed line was not actually erroring.

Diagnostic streams

A Program exposes several streams:

  • getOptionsDiagnostics()tsconfig.json problems, e.g., invalid target.
  • getGlobalDiagnostics() — missing libs, lib conflicts.
  • getSyntacticDiagnostics(sourceFile?) — parse errors.
  • getSemanticDiagnostics(sourceFile?) — type errors.
  • getDeclarationDiagnostics(sourceFile?)--declaration synthesis errors.
  • getSuggestionDiagnostics(sourceFile) — language-service suggestions.

Each is computed lazily and memoised. The programDiagnostics.ts module dedups identical diagnostics that come from multiple sources.

Integration points

  • tsc writes formatted diagnostics to stderr and uses the count to set the exit code.
  • tsserver sends raw diagnostic objects in semanticDiag, syntaxDiag, suggestionDiag events, throttled per-file.
  • Editors translate Diagnostic.code into doc links (e.g., https://typescript.tv/errors/#TS2304).
  • Code fixes in src/services/codefixes/ are registered against specific diagnostic codes.

Entry points for modification

  • New diagnostic — edit diagnosticMessages.json, run hereby generate-diagnostics, reference the new constant.
  • New formatting — formatDiagnostic* in utilities.ts.
  • New comment directive — extend commentDirectives in scanner.ts and applyCommentDirectives in programDiagnostics.ts.
  • Per-locale translations live under src/loc/lcl/ and are managed by Microsoft's localisation team — generally not changed by contributors.

See primitives/diagnostic for the data shape.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Diagnostics – TypeScript wiki | Factory