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.
// 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.
// 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:
| Tag | Zweck | Beispiel |
|---|---|---|
@param | Parameter inkl. Typ | @param {string} name |
@returns | Rückgabewert | @returns {Promise<void>} |
@throws | Mögliche Exception | @throws {TypeError} |
@example | Code-Beispiel | @example + Code-Block |
@deprecated | Migration-Marker | @deprecated use foo() instead |
@see | Referenz / Querverweis | @see {@link foo} |
@typedef | Type-Alias-Definition | @typedef {Object} User |
@template | Generischer Type-Parameter | @template T |
@callback | Function-Type-Definition | @callback Predicate |
@type | Variable-Type | @type {number[]} |
@override | Methode überschreibt Parent | @override |
@readonly | nicht ä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 ({a: number, b: string}), Unions (string | number), Optionals ([name]) und Promises (Promise<T>) sind direkt darstellbar.
/**
* 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.
// @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.
// @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);42 hello
[ 1, 2, 3 ]Constraints — analog zu TypeScripts T extends string — schreibt man als @template {string} 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.
// @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.
# 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 docsHashbang 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.
#!/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
- JSDoc – Official Documentation
- JSDoc Reference – TypeScript Handbook
- Lexical grammar – MDN
- eslint-plugin-jsdoc