Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Prefer explicit literal const maps over Object.fromEntries with type assertions
Write lookup maps as hand-typed literal objects with `as const` instead of building them from arrays with Object.fromEntries and an `as` cast.
Bad example
| 1 | const STATUSES = [ |
| 2 | { code: 0, value: 'open' as const, label: 'Open' }, |
| 3 | { code: 1, value: 'closed' as const, label: 'Closed' }, |
| 4 | ] as const; |
| 5 |
|
| 6 | export type StatusValue = (typeof STATUSES)[number]['value']; |
| 7 |
|
| 8 | // `as` defeats type checking exactly where the mapping could be wrong |
| 9 | export const VALUE_TO_CODE: Record<StatusValue, number> = Object.fromEntries( |
| 10 | STATUSES.map((s) => [s.value, s.code]), |
| 11 | ) as Record<StatusValue, number>; |
| 12 |
|
| 13 | export const CODE_TO_VALUE: Record<number, StatusValue> = Object.fromEntries( |
| 14 | STATUSES.map((s) => [s.code, s.value]), |
| 15 | ) as Record<number, StatusValue>; |
Explanation (EN)
Object.fromEntries returns a loosely-typed object, so an `as` assertion is forced to restore the intended type. The cast hides any mistake in keys or values, and the array-to-map indirection is harder to scan than the maps themselves.
Objašnjenje (HR)
Object.fromEntries vraca labavo tipiziran objekt, pa je `as` tvrdnja nužna da se vrati željeni tip. Cast skriva svaku grešku u ključevima ili vrijednostima, a posredovanje preko polja teže je čitati od samih mapa.
Good example
| 1 | export const STATUS_CODE = { |
| 2 | OPEN: 0, |
| 3 | CLOSED: 1, |
| 4 | } as const; |
| 5 |
|
| 6 | export type StatusValue = keyof typeof VALUE_TO_CODE; |
| 7 |
|
| 8 | export const VALUE_TO_CODE = { |
| 9 | open: STATUS_CODE.OPEN, |
| 10 | closed: STATUS_CODE.CLOSED, |
| 11 | } as const; |
| 12 |
|
| 13 | export const CODE_TO_VALUE: Record<number, StatusValue> = { |
| 14 | [STATUS_CODE.OPEN]: 'open', |
| 15 | [STATUS_CODE.CLOSED]: 'closed', |
| 16 | }; |
| 17 |
|
| 18 | export const STATUS_LABEL: Record<number, string> = { |
| 19 | [STATUS_CODE.OPEN]: 'Open', |
| 20 | [STATUS_CODE.CLOSED]: 'Closed', |
| 21 | }; |
Explanation (EN)
Each map is written out as a literal so the compiler fully type-checks every key and value, autocomplete works, and there is no assertion to mask errors. The code reads top-to-bottom without an intermediate array transform.
Objašnjenje (HR)
Svaka mapa zapisana je kao literal pa kompajler u potpunosti provjerava svaki ključ i vrijednost, autocomplete radi, a nema tvrdnje koja bi prikrila greške. Kod se čita od vrha prema dolje bez međukoraka s transformacijom polja.
Exceptions / Tradeoffs (EN)
When there are many entries and they are genuinely the single source of truth, a typed builder helper that returns a strongly-typed map (no raw `as`) can be acceptable.
Iznimke / Tradeoffi (HR)
Kad ima puno unosa i oni su stvarno jedini izvor istine, prihvatljiv je tipizirani pomoćni builder koji vraća jako tipiziranu mapu (bez sirovog `as`).