Kommentare sind in JavaScript mehr als Notizen für Menschen. Mit JSDoc und der // @ts-check-Direktive werden sie zu einer vollwertigen Typ- und Dokumentationsschicht, die der TypeScript-Compiler als Quelle akzeptiert — ohne dass die Datei selbst TypeScript wäre. Dieser Artikel zeigt die drei Kommentar-Arten der Sprache, die wichtigsten JSDoc-Tags, das Zusammenspiel mit @typedef und @template und die Stelle, an der Hashbang-Zeilen syntaktisch kein Kommentar sind.

Die drei Kommentar-Arten

JavaScript kennt zwei Kommentar-Token, daraus drei semantisch unterschiedliche Formen: Line-Comment (//), Block-Comment (/* */) und Doc-Comment (/** */). Der Doc-Comment ist syntaktisch nur ein Block-Comment mit doppeltem Stern am Anfang, wird aber von Tools wie TypeScript, ESLint, IDEs und JSDoc-Generatoren als Quelle für Metadaten erkannt.

JavaScript kommentar-arten.js
// Line-Comment — bis zum Zeilenende

/* Block-Comment — kann
   mehrere Zeilen umfassen */

/**
 * Doc-Comment — wird von TypeScript, IDEs
 * und JSDoc-Tools als Metadaten gelesen.
 * @param {number} n
 * @returns {number}
 */
function double(n) { return n * 2; }

Wann sind Kommentare sinnvoll?

Die Kurzregel: Why, nicht What. Code beschreibt das Was; ein Kommentar erklärt das Warum — typisch für überraschende Workarounds, externe Constraints, historische Entscheidungen oder absichtliche Verstöße gegen Best Practices. Kommentare, die nur den nächsten Statement-Block paraphrasieren, sind Noise und veralten schneller als der Code, den sie beschreiben.

JavaScript why-nicht-what.js
// SCHLECHT: paraphrasiert das Offensichtliche
// erhoeht counter um 1
counter++;

// GUT: erklaert das Warum
// Safari 15 liefert hier NaN bei leerem Input — explizit auf 0 fallen
counter = parseInt(input, 10) || 0;

// GUT: TODO/FIXME mit Kontext
// TODO(MB, 2026-08): nach Drop von Node 18 auf structuredClone wechseln
const cloned = JSON.parse(JSON.stringify(data));

// GUT: Markierung für obscure Workaround
// HACK: Race-Condition mit React-18-Concurrent — Microtask schiebt nach commit
queueMicrotask(() => syncDom());

Verbreitete Tags wie TODO, FIXME, HACK, NOTE, XXX werden von vielen IDEs und Linter-Konfigurationen erkannt und in einer eigenen Liste aggregiert.

JSDoc-Tags im Überblick

JSDoc kennt über 80 Tags, in der Praxis ist eine kleine Kernmenge ausreichend. Die wichtigsten:

TagZweckBeispiel
@paramParameter inkl. Typ@param {string} name
@returnsRückgabewert@returns {Promise<void>}
@throwsMögliche Exception@throws {TypeError}
@exampleCode-Beispiel@example + Code-Block
@deprecatedMigration-Marker@deprecated use foo() instead
@seeReferenz / Querverweis@see {@link foo}
@typedefType-Alias-Definition@typedef {Object} User
@templateGenerischer Type-Parameter@template T
@callbackFunction-Type-Definition@callback Predicate
@typeVariable-Type@type {number[]}
@overrideMethode überschreibt Parent@override
@readonlynicht änderbar@readonly

Type-Annotations in JSDoc

JSDoc unterstützt einen kompakten Typ-Ausdrucks-Syntax in geschweiften Klammern. Primitive (string, number, boolean), Arrays (string[] oder Array<string>), Objekt-Literale (&#123;a: number, b: string&#125;), Unions (string | number), Optionals ([name]) und Promises (Promise<T>) sind direkt darstellbar.

JavaScript jsdoc-types.js
/**
 * Holt einen User per ID.
 *
 * @param {number} id - Eindeutige User-ID.
 * @param {{ cache?: boolean, signal?: AbortSignal }} [opts]
 * @returns {Promise<{ id: number, name: string } | null>}
 * @throws {TypeError} bei ungueltiger ID.
 * @example
 *   const user = await fetchUser(42);
 */
export async function fetchUser(id, opts = {}) {
    if (typeof id !== 'number') throw new TypeError('id must be number');
    const res = await fetch(`/api/users/${id}`, { signal: opts.signal });
    return res.ok ? res.json() : null;
}

VS Code, WebStorm und JetBrains-IDEs lesen diese Annotationen ohne weitere Konfiguration und liefern Hover-Hints, Auto-Completion und Inline-Errors — auch in reinen .js-Dateien.

// @ts-check: TypeScript-Lite ohne Build

Die Direktive // @ts-check als erste Zeile einer Datei aktiviert die TypeScript-Typprüfung für diese eine Datei — auch in einem reinen JavaScript-Projekt. Der TypeScript-Compiler liest die JSDoc-Typen, prüft Aufrufe und meldet Mismatches als Fehler. Das Ergebnis sind 80 % der TypeScript-Sicherheit ohne Compile-Step, ohne tsconfig.json und ohne Source-Map-Komplexität.

JavaScript ts-check.js
// @ts-check

/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function add(a, b) {
    return a + b;
}

add(1, 2);      // OK
// add('1', 2); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
// add(1);      // Error: Expected 2 arguments, but got 1.

Für ganze Projekte gibt es die Variante checkJs: true in einer jsconfig.json — dann gilt die Prüfung global, ohne Direktive in jeder Datei. Einzelne Dateien lassen sich mit // @ts-nocheck ausnehmen, einzelne Zeilen mit // @ts-ignore (oder besser // @ts-expect-error).

Generics mit @template

Generische Funktionen sind in JSDoc über @template möglich. Der Type-Parameter steht über dem Doc-Block und kann dann in @param/@returns referenziert werden.

JavaScript generics.js
// @ts-check

/**
 * @template T
 * @param {T} value
 * @returns {T}
 */
function identity(value) {
    return value;
}

const n = identity(42);       // T = number
const s = identity('hello');  // T = string
console.log(n, s);

/**
 * @template T, U
 * @param {T[]} arr
 * @param {(item: T) => U} mapFn
 * @returns {U[]}
 */
function map(arr, mapFn) {
    return arr.map(mapFn);
}

const lengths = map(['a', 'bb', 'ccc'], s => s.length); // number[]
console.log(lengths);
Output
42 hello
[ 1, 2, 3 ]

Constraints — analog zu TypeScripts T extends string — schreibt man als @template &#123;string&#125; T. Der Type-Parameter darf dann nur Subtypen von string aufnehmen.

Type-Aliase mit @typedef

@typedef definiert wiederverwendbare Typ-Namen — das JSDoc-Pendant zu TypeScripts type oder interface. Der Alias steht in einem eigenen Doc-Block, der keinen Code beschreibt, sondern nur einen Typ definiert.

JavaScript typedef.js
// @ts-check

/**
 * @typedef {Object} User
 * @property {number} id
 * @property {string} name
 * @property {string} [email] - optional
 * @property {'admin' | 'user' | 'guest'} role
 */

/**
 * @typedef {(user: User) => boolean} UserPredicate
 */

/**
 * @param {User[]} users
 * @param {UserPredicate} pred
 * @returns {User[]}
 */
function filterUsers(users, pred) {
    return users.filter(pred);
}

const admins = filterUsers(
    [{ id: 1, name: 'Mia', role: 'admin' }],
    (u) => u.role === 'admin'
);

Mit @import (TypeScript 5.5+) lassen sich Typen aus anderen Dateien einbinden — der saubere Weg, um @typedef in einer eigenen types.js zu sammeln und zentral zu pflegen.

Tools im Ökosystem

JSDoc ist nicht an ein einzelnes Tool gebunden. Drei verschiedene Werkzeugklassen lesen die gleichen Doc-Blocks:

  • Editor-Integration: VS Code, WebStorm, IntelliJ liefern Hover-Hints und Auto-Completion direkt aus JSDoc — ohne Konfiguration.
  • Type-Checker: Der TypeScript-Compiler (tsc) prüft JSDoc-Typen mit --checkJs. Standalone-Variante über // @ts-check.
  • Doku-Generator: Das ursprüngliche jsdoc-CLI generiert HTML-Dokumentation. Moderner: TypeDoc, das mit JSDoc-Annotationen und TypeScript-Typen gleichermaßen arbeitet.
  • Linter: Die ESLint-Plugins eslint-plugin-jsdoc und eslint-plugin-tsdoc prüfen Konsistenz, vorhandene Tags, korrekten Typsyntax.
Bash setup-jsdoc-toolchain.sh
# Type-Check für ganze Projekt-Dateien (ohne TS-Build)
npx -p typescript tsc --checkJs --noEmit --allowJs src/**/*.js

# ESLint-Plugin für JSDoc-Konsistenz
npm i -D eslint-plugin-jsdoc

# Standalone-Doku generieren
npx jsdoc -r src/ -d docs/

# Alternativ: TypeDoc (läuft auch auf JSDoc)
npx typedoc --entryPoints src --out docs

Hashbang ist KEIN Kommentar

Seit ES2023 ist #!/usr/bin/env node als erste Zeile eines Scripts oder Moduls offiziell Teil der Sprache — als Hashbang-Grammatik, nicht als Kommentar. Vorher mussten Node-CLIs ihn mit Tricks akzeptieren, oder das Script begann mit einer Funktion und einem JSDoc-Block, in dem der Shebang versteckt war.

JavaScript cli.mjs
#!/usr/bin/env node
// ^ Hashbang — muss in ZEILE 1 stehen, KEIN Whitespace davor.
// Ist syntaktisch ein Hashbang-Grammar-Element, kein Kommentar.

import { argv } from 'node:process';

console.log('Args:', argv.slice(2));

Praktisch verhält sich der Hashbang wie ein Line-Kommentar — das Token wird vom Parser verworfen — aber er ist nur an genau einer Position erlaubt: Zeile 1, ab Spalte 0. Jeder andere Hashbang ist SyntaxError.

Anmerkungen

JSDoc + // @ts-check liefert 80 % TypeScript-Sicherheit ohne Build

Für reine JS-Projekte mit Type-Safety-Wunsch ist die Kombination ideal: keine Compile-Phase, keine Source-Maps, keine zusätzlichen Tools — nur ein Doc-Block und eine Zeile am Datei-Anfang. Der TypeScript-Compiler erkennt fehlerhafte Aufrufe, falsche Property-Zugriffe und Mismatches in den meisten Praxisfällen. Was fehlt: erweiterte Features wie Conditional Types, Mapped Types, Branded Types — dort lohnt der Schritt zu echtem TypeScript.

VS Code parst JSDoc nativ — Hover-Hints ohne Setup

Schon ohne jsconfig.json liest der TypeScript-Server in VS Code die Annotationen und zeigt Typ-Informationen beim Hover, Auto-Completion bei Property-Zugriff und Signatur-Hilfen bei Funktions-Aufrufen. Das funktioniert auch in Bibliotheks-Code: NPM-Pakete mit JSDoc-Annotationen liefern Type-Hints, ohne @types/* bereitzustellen.

@typedef ist das JSDoc-Äquivalent zu TypeScripts type/interface

Es erlaubt Object-Shapes, Union-Types, Function-Signatures, Tuples. Mit @property als Sub-Tag werden Felder definiert, optional über [name]. Komplexere Typ-Konstrukte wie Mapped-Types oder Conditional-Types fehlen, aber 80 % der typischen Domain-Typen lassen sich abbilden. Wer mehrere @typedefs in einer Datei sammelt, kann sie über @import oder import('./types').User-Syntax in andere Dateien holen.

Block-Kommentare können nicht verschachtelt werden

/* außen /* innen */ rest */ bricht beim ersten */ ab — der Rest ist Syntax-Müll und meldet Parser-Fehler. Folge: Wer einen Code-Block mit Block-Kommentar auskommentieren will, sollte sicher sein, dass kein /* */ drin steckt. Zuverlässiger ist Line-Comment-Multi-Cursor im Editor — oder die Direktive-Variante if (false) { ... } für temporäres Deaktivieren.

TODO und FIXME werden von vielen Tools als Tasks erkannt

VS Code-Extensions wie Todo Tree und Better Comments aggregieren TODO, FIXME, HACK, NOTE projekt-weit und zeigen sie farbig hervorgehoben. ESLint-Regel no-warning-comments lässt sich konfigurieren, um FIXME als Build-Warning auszugeben — ein guter CI-Hebel, um lange schlummernde Hotfixes sichtbar zu machen.

@deprecated triggert Strikethrough in IDEs

VS Code, WebStorm und IntelliJ rendern Aufrufe einer @deprecated-markierten Funktion mit Durchstreich-Effekt. Idealer Migrations-Marker während Refactorings: alte API als deprecated markieren, neue API daneben setzen, in der nächsten Major-Version löschen. Die Strikethrough-Anzeige ist sofort sichtbar und braucht keinen Linter-Lauf.

Hashbang #!/usr/bin/env node ist seit ES2023 eigene Syntax

Vor ES2023 war der Shebang formal kein gültiges JavaScript — Node ersetzte die Zeile beim Lesen oder akzeptierte sie über einen V8-Patch. Jetzt ist Hashbang Teil der Spezifikation: erste Zeile eines Scripts oder Moduls, kein Whitespace davor. Tools wie Bun, Deno, esbuild und neuere TypeScript-Versionen kennen die Syntax — alte Tools meckern eventuell noch.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Syntax & Sprachkern

Zur Übersicht