Lektion 21: eslint - Pluggable JavaScript & TypeScript Linter
Tool-Lektion | 35 Minuten
Dieser Abschnitt fuehrt dich durch Installation, Konfiguration und praktische Nutzung von ESLint -- vom ersten Setup bis zur Framework-spezifischen Integration.
Lernziele
- Metadata
- Verstehen warum dieses Tool wichtig ist
- Die wichtigsten Einsatzgebiete kennen
- Installation und grundlegende Nutzung beherrschen
- Production-ready Patterns anwenden
Lektion 21: eslint - Pluggable JavaScript & TypeScript Linter
Metadata
- Kategorie: Fortgeschrittene Tools
- Schwierigkeit: Mittel bis Fortgeschritten
- Voraussetzungen: Lektion 01-04 (Grundlagen), Lektion 20 (prettier), Node.js Kenntnisse
- Lernzeit: 60-90 Minuten
- Zielgruppe: JavaScript/TypeScript Entwickler, die Code-Qualität sicherstellen möchten
🚀 Claude Code Relevanz: ESLint prueft von Claude Code generierten JavaScript/TypeScript-Code automatisch auf Bugs, Security-Luecken und Best-Practice-Verstoesse -- ein unverzichtbarer Qualitaetsfilter im AI-Workflow.
Berechtigung: Warum eslint?
Problem ohne eslint:
// Potenzielle Bugs und Code-Smell
var x = 10; // 'var' statt 'const'
if (x = 11) { console.log("Bug!"); } // Assignment statt Vergleich
function unused() {} // Ungenutzte Funktion
const arr = [1,,3]; // Sparse Array
function getData() {
const result = fetch('/api'); // Fehlendes 'await'
return result;
}
// Keine Warnung bei fehlenden Error-Handlings
try {
JSON.parse(data);
} catch(e) {} // Leerer Catch-Block
Lösung mit eslint:
$ eslint script.js
/path/to/script.js
1:1 warning Unexpected var, use let or const instead no-var
2:5 error Expected a conditional expression, not assignment no-cond-assign
3:10 warning 'unused' is defined but never used no-unused-vars
4:16 error Unexpected comma in middle of array no-sparse-arrays
7:18 error Missing await in async context require-await
12:12 error Empty block statement no-empty
✖ 6 problems (4 errors, 2 warnings)
2 errors and 1 warning potentially fixable with the --fix option.
Kernvorteile:
✅ Bug-Prevention Erkennt häufige Fehlerquellen früh
✅ Code-Qualität Erzwingt Best Practices und Patterns
✅ Auto-Fix 70%+ der Probleme automatisch behebbar
✅ Pluggable Erweiterbar mit Custom Rules + Plugins
✅ Framework-Support React, Vue, Angular, TypeScript
✅ CI/CD-Ready Blockiert fehlerhafte Commits
Zwecke: Wofür wird eslint verwendet?
. **Static Code Analysis**
- Syntax-Fehler finden vor Runtime
- Logische Fehler erkennen (z.B. unreachable code)
- Type-Safety (mit @typescript-eslint)
. **Code-Style Enforcement**
- Konsistente Coding-Standards im Team
- Naming-Conventions durchsetzen
- Import-Order regulieren
. **Security Auditing**
- Unsichere Patterns erkennen (eval, innerHTML)
- XSS-Vulnerabilities aufdecken
- Dependency-Vulnerabilities warnen
. **Framework-spezifische Best Practices**
- React Hooks Rules
- Vue Reactivity Patterns
- Angular Dependency Injection
. **Migration & Refactoring**
- Veraltete APIs finden
- Legacy-Code modernisieren
- Breaking Changes früh erkennen
Verwendung
Dieser Abschnitt fuehrt dich durch Installation, Konfiguration und praktische Nutzung von ESLint -- vom ersten Setup bis zur Framework-spezifischen Integration.
Installation
ESLint wird ueber npm installiert. Die projektlokale Installation ist empfohlen, damit jedes Projekt seine eigene Version und Konfiguration haben kann.
macOS (mit npm):
Installiere ESLint zusammen mit den benoetigten Plugins fuer dein Projekt:
# Projekt-lokal (empfohlen)
npm install --save-dev eslint
# Mit TypeScript Support
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
# Mit React Support
npm install --save-dev eslint eslint-plugin-react eslint-plugin-react-hooks
# Alle zusammen
npm install --save-dev \
eslint \
@typescript-eslint/parser \
@typescript-eslint/eslint-plugin \
eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-config-prettier
Ubuntu/Debian:
# Node.js/npm installieren (falls nicht vorhanden)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# ESLint installieren
npm install --save-dev eslint
Arch Linux:
# Via npm (empfohlen)
npm install --save-dev eslint
# Oder global (AUR)
yay -S eslint
Quick Start: Erste Schritte
Der schnellste Weg zu einem funktionierenden ESLint-Setup fuehrt ueber den interaktiven Wizard.
. **ESLint initialisieren (interaktiv)**
Der Init-Wizard fragt nach deinem Projekttyp und erstellt automatisch die passende Konfiguration:
# ESLint Config Wizard
npx eslint --init
# Fragen:
# ✔ How would you like to use ESLint? › To check syntax, find problems, and enforce code style
# ✔ What type of modules does your project use? › JavaScript modules (import/export)
# ✔ Which framework does your project use? › React
# ✔ Does your project use TypeScript? › Yes
# ✔ Where does your code run? › Browser, Node
# ✔ How would you like to define a style for your project? › Use a popular style guide
# ✔ Which style guide do you want to follow? › Airbnb
# ✔ What format do you want your config file to be in? › JavaScript
. **Einzelne Datei prüfen**
# Datei linten
eslint script.js
# Mit Auto-Fix
eslint --fix script.js
# Mehrere Dateien
eslint src/*.js
. **Ganzes Projekt linten**
# Alle JS/TS Dateien
eslint "src/**/*.{js,jsx,ts,tsx}"
# Mit Cache (schneller bei wiederholten Runs)
eslint --cache "src/**/*.{js,jsx,ts,tsx}"
# Nur Fehler, keine Warnings
eslint --quiet src/
💡 Tipp: Aktiviere den Cache mit eslint --cache, um wiederholte Laeufe drastisch zu beschleunigen. In grossen Projekten kann das die Laufzeit von 30+ Sekunden auf unter 2 Sekunden reduzieren.
. **Basis-Config erstellen (.eslintrc.json)**
cat > .eslintrc.json << 'EOF'
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended"
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"no-unused-vars": "warn",
"no-console": "off",
"prefer-const": "warn"
}
}
EOF
. **ESLint Ignore-File**
cat > .eslintignore << 'EOF'
# Dependencies
node_modules/
package-lock.json
# Build outputs
dist/
build/
*.min.js
*.bundle.js
# Generated
coverage/
.next/
out/
# Config
*.config.js
EOF
Advanced Usage
Fortgeschrittene ESLint-Konfigurationen fuer TypeScript, React, Custom Rules und das neue Flat Config Format.
. **TypeScript-Projekt Setup**
Fuer TypeScript-Projekte wird ein spezieller Parser benoetigt, der die TypeScript-Syntax versteht und typenbasierte Rules ermoeglicht:
// .eslintrc.json
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-floating-promises": "error"
}
}
. **React-Projekt mit Hooks**
React-Projekte profitieren von speziellen Plugins, die Hook-Regeln und Accessibility-Checks erzwingen:
{
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended"
],
"plugins": ["react", "react-hooks", "jsx-a11y"],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
. **Custom Rule erstellen**
Fuer projektspezifische Anforderungen kannst du eigene ESLint-Rules entwickeln. Diese Rule verbietet direkte DOM-Manipulation in React-Komponenten:
// eslint-plugin-custom/rules/no-direct-dom.js
module.exports = {
meta: {
type: "problem",
docs: {
description: "Disallow direct DOM manipulation",
category: "Best Practices",
},
fixable: null,
schema: [],
},
create(context) {
return {
CallExpression(node) {
const methods = ['getElementById', 'querySelector', 'querySelectorAll'];
if (
node.callee.object &&
node.callee.object.name === 'document' &&
methods.includes(node.callee.property.name)
) {
context.report({
node,
message: "Avoid direct DOM manipulation. Use ref or state instead.",
});
}
},
};
},
};
// .eslintrc.json
{
"plugins": ["./eslint-plugin-custom"],
"rules": {
"custom/no-direct-dom": "error"
}
}
. **Multi-Environment Config**
// .eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
extends: ['eslint:recommended'],
// Override für Tests
overrides: [
{
files: ['/*.test.js', '/*.spec.js'],
env: {
jest: true,
},
rules: {
'no-unused-expressions': 'off',
},
},
// Override für Node-Scripts
{
files: ['scripts/**/*.js'],
env: {
node: true,
},
rules: {
'no-console': 'off',
},
},
],
};
⚠️ Warnung: Ab ESLint v9 ist das neue Flat Config Format (eslint.config.js) Standard. Die alte .eslintrc-Konfiguration wird in v10 komplett entfernt. Migriere fruehzeitig, um Kompatibilitaetsprobleme zu vermeiden.
. **Flat Config (ESLint v9+)**
Das neue Flat Config Format ersetzt die verschachtelte .eslintrc-Konfiguration durch ein einfacheres, JavaScript-basiertes System:
// eslint.config.js (neuer Flat Config)
import js from '@eslint/js';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
export default [
js.configs.recommended,
{
files: ['**/*.{js,ts}'],
languageOptions: {
parser: tsParser,
parserOptions: {
project: './tsconfig.json',
},
},
plugins: {
'@typescript-eslint': tsPlugin,
},
rules: {
...tsPlugin.configs.recommended.rules,
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
},
},
];
🚀 Beispiel: Nutze TIMING=1 eslint src/ um herauszufinden, welche Rules am meisten Zeit benoetigen. So kannst du gezielt langsame Rules deaktivieren oder durch schnellere Alternativen ersetzen.
. **ESLint mit Prettier kombinieren**
# Prettier-Integration installieren
npm install --save-dev eslint-config-prettier eslint-plugin-prettier
# .eslintrc.json
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended" // ← MUSS am Ende stehen!
],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}
Best Practices
Bewaeaehrte Strategien fuer den effektiven Einsatz von ESLint in Projekten unterschiedlicher Groesse.
. **Strict Config für neue Projekte**
Bei neuen Projekten kannst du von Anfang an strenge Regeln setzen. Die folgenden Rules erzwingen moderne JavaScript-Patterns und begrenzen die Code-Komplexitaet:
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/strict"
],
"rules": {
"no-var": "error",
"prefer-const": "error",
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"eqeqeq": ["error", "always"],
"curly": ["error", "all"],
"no-console": ["warn", { "allow": ["warn", "error"] }],
"max-lines-per-function": ["warn", { "max": 50 }],
"complexity": ["warn", 10]
}
}
💡 Tipp: Bei der Kombination mit Prettier muss"prettier"immer als letztes Element in derextends-Liste stehen, da es alle Style-Rules der vorherigen Konfigurationen deaktiviert.
. **Security-focused Config**
Das Security-Plugin erkennt potenzielle Sicherheitsluecken wie eval()-Aufrufe, unsichere regulaere Ausdruecke und Object-Injection-Angriffsvektoren:
# Security Plugin installieren
npm install --save-dev eslint-plugin-security
# .eslintrc.json
{
"plugins": ["security"],
"extends": ["plugin:security/recommended"],
"rules": {
"security/detect-eval-with-expression": "error",
"security/detect-non-literal-regexp": "warn",
"security/detect-unsafe-regex": "error",
"security/detect-object-injection": "warn"
}
}
. **Pre-commit Hook Integration**
Pre-commit Hooks verhindern, dass fehlerhafter Code ueberhaupt committet wird. In Kombination mit lint-staged werden nur die geaenderten Dateien geprueft:
# Husky + lint-staged Setup
npm install --save-dev husky lint-staged
npx husky init
# package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
]
}
}
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
. **CI/CD Integration**
# .github/workflows/lint.yml
name: ESLint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Annotate code linting results
uses: ataylorme/eslint-annotate-action@v2
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
report-json: "eslint_report.json"
. **Monorepo mit mehreren Configs**
// root .eslintrc.js
module.exports = {
root: true,
extends: ['./packages/eslint-config-base'],
};
// packages/web/.eslintrc.js
module.exports = {
extends: ['../../.eslintrc.js', 'plugin:react/recommended'],
env: {
browser: true,
},
};
// packages/api/.eslintrc.js
module.exports = {
extends: ['../../.eslintrc.js'],
env: {
node: true,
},
rules: {
'no-console': 'off',
},
};
Beispiele
Beispiel 1: Bugs früh erkennen
// Vorher: script.js (bugs versteckt)
function calculate(x, y) {
if (x = 10) { // Assignment statt Vergleich!
return x + y;
}
return x * y;
}
const result = calculate(5, 3);
// ESLint
$ eslint script.js
2:7 error Expected a conditional expression, not assignment no-cond-assign
// Nachher: Fixed
function calculate(x, y) {
if (x === 10) { // ✓ Korrekter Vergleich
return x + y;
}
return x * y;
}
Beispiel 2: React Hooks Rules
// Vorher: Component.jsx (Hook-Fehler)
function UserProfile({ userId }) {
if (!userId) {
return null;
}
// ❌ Hooks dürfen nicht conditional sein!
const [user, setUser] = useState(null);
useEffect(() => {
fetch(/api/users/${userId})
.then(res => res.json())
.then(setUser);
}); // ❌ Fehlende Dependencies!
return <div>{user?.name}</div>;
}
// ESLint
$ eslint Component.jsx
7:9 error React Hook "useState" is called conditionally react-hooks/rules-of-hooks
12:5 warn React Hook useEffect has a missing dependency react-hooks/exhaustive-deps
// Nachher: Fixed
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
if (!userId) return;
fetch(/api/users/${userId})
.then(res => res.json())
.then(setUser);
}, [userId]); // ✓ Dependencies hinzugefügt
if (!userId) return null;
return <div>{user?.name}</div>;
}
Beispiel 3: TypeScript Type-Safety
// Vorher: api.ts (Type-Issues)
async function fetchData(id: number) {
const response = await fetch(/api/data/${id});
const data = response.json(); // ❌ Fehlendes 'await'
return data;
}
function process(value: any) { // ❌ 'any' Type
return value.toUpperCase(); // ❌ Potentieller Runtime-Error
}
// ESLint (@typescript-eslint)
$ eslint api.ts
3:16 error Promises must be awaited @typescript-eslint/no-floating-promises
6:20 warn Unexpected any @typescript-eslint/no-explicit-any
7:17 error Unsafe member access @typescript-eslint/no-unsafe-member-access
// Nachher: Fixed
async function fetchData(id: number): Promise<unknown> {
const response = await fetch(/api/data/${id});
const data = await response.json(); // ✓ Await hinzugefügt
return data;
}
function process(value: string): string { // ✓ Expliziter Type
if (typeof value !== 'string') {
throw new Error('Expected string');
}
return value.toUpperCase();
}
Beispiel 4: Security-Vulnerabilities finden
// Vorher: unsafe.js
function renderHTML(userInput) {
document.getElementById('output').innerHTML = userInput; // ❌ XSS!
}
function evaluateCode(code) {
eval(code); // ❌ Code Injection!
}
const apiKey = 'sk-1234567890abcdef'; // ❌ Hardcoded Secret
// ESLint (mit security plugin)
$ eslint unsafe.js
2:47 error Unsafe assignment to innerHTML security/detect-non-literal-innerHTML
6:3 error eval can be harmful no-eval
9:7 warn Possible hardcoded secret security/detect-possible-secrets
// Nachher: Fixed
function renderHTML(userInput) {
const sanitized = DOMPurify.sanitize(userInput); // ✓ Sanitized
document.getElementById('output').textContent = sanitized;
}
function evaluateCode(code) {
// Use Function() with validation or AST parsing
// eval() entfernt
}
const apiKey = process.env.API_KEY; // ✓ Aus Environment
Beispiel 5: Import-Order durchsetzen
// Vorher: messy-imports.js
import { Button } from './components/Button';
import React from 'react';
import fs from 'fs';
import _ from 'lodash';
import './styles.css';
// ESLint (mit eslint-plugin-import)
$ eslint messy-imports.js
1:1 error 'react' import should occur before './components/Button' import/order
// Nachher: Fixed (auto-fixable)
// 1. Node builtins
import fs from 'fs';
// 2. External packages
import React from 'react';
import _ from 'lodash';
// 3. Internal modules
import { Button } from './components/Button';
// 4. Styles
import './styles.css';
Beispiel 6: Accessibility (a11y) Checks
// Vorher: Form.jsx
function LoginForm() {
return (
<div>
<input type="text" placeholder="Username" /> {/* ❌ Fehlendes Label */}
<img src="logo.png" /> {/* ❌ Fehlendes alt */}
<div onClick={handleClick}>Click me</div> {/* ❌ Kein button */}
</div>
);
}
// ESLint (mit jsx-a11y)
$ eslint Form.jsx
4:7 error Form elements must have labels jsx-a11y/label-has-associated-control
5:7 error img elements must have alt prop jsx-a11y/alt-text
6:7 warn Visible, non-interactive elements should not have click handlers jsx-a11y/click-events-have-key-events
// Nachher: Fixed
function LoginForm() {
return (
<div>
<label htmlFor="username">Username</label>
<input id="username" type="text" />
<img src="logo.png" alt="Company Logo" />
<button onClick={handleClick}>Click me</button>
</div>
);
}
Beispiel 7: Code-Complexity Warnung
// Vorher: complex.js
function processOrder(order) {
if (order.type === 'premium') {
if (order.amount > 100) {
if (order.customer.vip) {
if (order.shipping === 'express') {
// ... deep nesting (Cyclomatic Complexity: 8)
}
}
}
}
}
// ESLint
$ eslint complex.js
1:10 warn Function 'processOrder' has complexity 8 complexity
// Nachher: Refactored
function processOrder(order) {
const isPremium = order.type === 'premium';
const isHighValue = order.amount > 100;
const isVIP = order.customer.vip;
const isExpress = order.shipping === 'express';
if (isPremium && isHighValue && isVIP && isExpress) {
// ... (Complexity: 2)
}
}
Beispiel 8: Async/Await Best Practices
// Vorher: async-bugs.js
async function getData() {
try {
const response = fetch('/api/data'); // ❌ Fehlendes await
return response.json(); // ❌ Fehlendes await
} catch (error) {
console.log(error); // ❌ Error nicht gehandled
}
}
// ESLint
$ eslint async-bugs.js
3:20 error Promise not awaited @typescript-eslint/no-floating-promises
4:12 error Promise not awaited @typescript-eslint/no-floating-promises
// Nachher: Fixed
async function getData(): Promise<Data> {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(HTTP ${response.status});
}
return await response.json();
} catch (error) {
console.error('Failed to fetch data:', error);
throw error; // ✓ Re-throw für Caller
}
}
Beispiel 9: Batch-Fixing mit --fix
# Alle Dateien automatisch fixen
eslint --fix "src/**/*.{js,jsx,ts,tsx}"
# Nur bestimmte Rules fixen
eslint --fix --fix-type problem,suggestion src/
# Report generieren
eslint -f json -o eslint-report.json src/
# HTML-Report für bessere Lesbarkeit
eslint -f html -o report.html src/
Beispiel 10: ESLint + Jest Integration
// .eslintrc.json
{
"overrides": [
{
"files": ["/*.test.js", "/*.spec.js"],
"extends": ["plugin:jest/recommended"],
"plugins": ["jest"],
"env": {
"jest/globals": true
},
"rules": {
"jest/no-disabled-tests": "warn",
"jest/no-focused-tests": "error",
"jest/no-identical-title": "error",
"jest/prefer-to-have-length": "warn",
"jest/valid-expect": "error"
}
}
]
}
// Test mit ESLint-Check
$ eslint src/**/*.test.js
tests/user.test.js
12:3 error Focused tests are not allowed jest/no-focused-tests
18:5 warn Use toHaveLength() matcher jest/prefer-to-have-length
Integration in Claude Code Workflows
. **AI-gestützte ESLint-Config-Optimierung**
# ESLint-Report generieren und von Claude analysieren lassen
eslint src/ -f json > eslint-report.json
cat eslint-report.json | claude "Analyze eslint violations and suggest config improvements"
# Custom Rule mit Claude entwickeln
cat > rule-prompt.txt << 'EOF'
Create an ESLint rule that:
- Detects usage of deprecated API functions
- Suggests modern alternatives
- Is auto-fixable where possible
EOF
cat rule-prompt.txt | claude "Generate ESLint custom rule code"
. **Automatisches Refactoring mit ESLint + Claude**
# Workflow: ESLint findet Issues → Claude schlägt Fixes vor
cat > auto-refactor.sh << 'EOF'
#!/bin/bash
# ESLint-Report erstellen
eslint src/ -f json > report.json
# Claude analysiert und schlägt Fixes vor
cat report.json | claude "Analyze these ESLint violations and generate fix suggestions with code examples" > fixes.md
# Developer reviewed fixes manuell
cat fixes.md
EOF
chmod +x auto-refactor.sh
. **Code-Review mit ESLint-Annotations**
# Pre-Review ESLint-Check
git diff main...HEAD --name-only | \
grep -E '\.(js|jsx|ts|tsx)