Druhá deklarace const v try-bloku (TDZ) maskuje validační chyby jako 500
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Registrační (nebo jiný validující) endpoint vrací „Internal server error” (500) pokaždé, když uživatel pošle formulář s JAKOUKOLIV validační chybou (chybí pole, slabé heslo…). Místo srozumitelné hlášky „Vyplňte bankovní účet” dostane uživatel generickou 500. Plně validní data ale projdou. V logu: Cannot access 'lang2' before initialization (bundler přejmenoval stíněnou proměnnou).
Root cause
Sekce “Root cause”Ve funkci je proměnná deklarovaná u vstupu a používaná napříč try blokem, a NÍŽE v témže try bloku je deklarovaná podruhé pod stejným názvem:
const lang = pickLangFromHeader(req); // (1) function scopetry { if (!valid(email)) errors.push(tApi('reg.invalid_email', lang)); // (2) ⚠ TDZ // ... desítky dalších tApi(..., lang) const lang = body.language || 'cs'; // (3) hoistne se na VRCHOL try-bloku sendEmail(tApi('welcome', lang));} catch (e) { return json500(tApi('err.internal', lang)); // (4) tady už outer lang — OK}const na řádku (3) je block-scoped a hoistne se na začátek try bloku. Tím každá reference lang mezi vrcholem bloku a řádkem (3) — tj. všechny validační tApi(..., lang) — spadne do Temporal Dead Zone a vyhodí ReferenceError: Cannot access 'lang' before initialization. Výjimku chytí catch, kde už lang odkazuje na vnější (definovanou) proměnnou, takže se tiše vrátí „Internal server error”. Happy-path bez validačních chyb se k žádné dřívější referenci nedostane → projde, takže bug vypadá náhodně.
Fix
Sekce “Fix”Přejmenuj vnitřní proměnnou (nestínit):
const lang = pickLangFromHeader(req);try { if (!valid(email)) errors.push(tApi('reg.invalid_email', lang)); // ✓ outer lang const emailLang = body.language || 'cs'; // ✓ jiný název, žádná TDZ sendEmail(tApi('welcome', emailLang));} catch (e) { /* … */ }Po opravě validační chyba vrátí korektní 400 s lokalizovanou hláškou, ne 500.
Jak se tomu vyvarovat v jiných systémech
Sekce “Jak se tomu vyvarovat v jiných systémech”- Detection: grep duplicitní deklarace v jedné funkci:
grep -nE "const (lang|id|user|data) =" soubora hledej dva výskyty téhož jména. ESLintno-shadow+no-redeclareto chytí staticky. Pozor: TypeScript to NEohlásí jako chybu (je to validní JS s block-scope), takžetsc/build projde čistě. - Anti-pattern: dlouhá funkce s
tryblokem, kde se „pomocná” proměnná deklaruje až v půlce, ačkoli se stejné jméno už používá výš. - Lepší přístup: krátké funkce; deklaruj všechny proměnné nahoře; catch-all
catchnikdy nesmí vracet generickou 500 bez zalogování skutečné výjimky (jinak ti TDZ/typové chyby splynou s „normální” 500 a nikdo si jich nevšimne).
Sister bugs / související
Sekce “Sister bugs / související”Generický catch → 500 je tady spoluviník — kdyby endpoint logoval err.message viditelně (ne jen do tabulky), Cannot access 'lang' before initialization by se odhalilo hned. Příbuzné: jakákoli „náhodná 500 jen někdy” často = výjimka na konkrétní větvi spolknutá do generické odpovědi.