# JavaScript / TypeScript Conventions

## Style guide

Airbnb JavaScript Style Guide is the common baseline. The project's `.eslintrc(.json|.cjs|.mjs)`, `.prettierrc`, `tsconfig.json`, and `package.json` `engines` override these defaults — read them **first**.

## Naming

| Element | Convention | Example |
|---|---|---|
| Variable / function | camelCase | `userCount`, `fetchProfile` |
| Class / type / interface / enum | PascalCase | `UserProfile`, `HttpClient`, `Status` |
| Constant (true immutable, module-level) | UPPER_SNAKE_CASE | `MAX_RETRIES` |
| Boolean variable | `is` / `has` / `should` / `can` prefix | `isLoading`, `hasPermission`, `shouldRetry` |
| React component | PascalCase | `UserCard` |
| Hook | `use` prefix | `useAuth` |
| File: React component | matches component name | `UserCard.tsx` |
| File: everything else | kebab-case | `format-date.ts` |

## Formatting

- Indent: **2 spaces**.
- Semicolons: pick one project-wide and never mix.
- Single quotes for strings, backticks for templates and any string needing interpolation.
- Trailing commas in multi-line literals / params.
- Prefer arrow functions for callbacks and small utilities; reserve `function` for top-level named declarations and class methods.
- Destructure when accessing 2 or more properties: `const { id, name } = user`.

## TypeScript-specific (mandatory)

- `tsconfig.json`: `"strict": true`. No exceptions. `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` are recommended.
- **Never** use `any`. If a type is truly unknown, use `unknown` and narrow.
- Let inference do its job for locals. Declare explicit types at:
  - Exported function signatures (parameters **and** return type).
  - Public class members.
  - Empty literals where inference would default to `never` or `{}` — `const items: User[] = []`.
- Use `readonly` for arrays, properties, and parameters that must not mutate.
- Discriminated unions over loose objects with optional flags. Use a literal `kind` / `type` field.
- **Never re-declare an authoritative type.** When the domain or a dependency already exports the enum / union for a set of state values (`FontStatus`, `OrderState`), import it — do not hand-maintain a parallel string union or literal list. Before writing a new state type, grep the state literals *and* the intended type name for an existing declaration. A new type's name must say whose values it holds.
- Type guards (`function isX(v): v is X`) over casts (`as X`). Casts are a last resort.
- `interface` for object shapes that may be extended; `type` for unions / mapped / conditional types.
- Generics: meaningful names when single letters are unclear (`TUser`, `TResult`).
- Never disable rules inline (`// eslint-disable`, `// @ts-ignore`, `// @ts-expect-error`) without a one-line comment explaining why.

## Required practices

- `const` by default, `let` only when reassigned. `var` is **forbidden**.
- Use `===` / `!==`, never `==` / `!=`.
- Use optional chaining `?.` and nullish coalescing `??` instead of long `&&` / `||` ladders.
- `for...of` over indexed `for` loops on iterables. `map` / `filter` / `reduce` over manual loops when transforming.
- `async` / `await` over raw `.then` chains. Mixing is forbidden in one function.
- Throw `Error` instances, never strings. Subclass `Error` for typed domain errors.
- Avoid `export default` unless the file has exactly one obvious export — named exports are easier to refactor and rename.
- Do not mutate function parameters or imported modules.

## Tests

- Framework: **Vitest**, **Jest**, or Node's built-in `node:test`. Match what the project uses.
- File suffix: `*.test.ts` or `*.spec.ts`, colocated with source (`src/foo/bar.ts` → `src/foo/bar.test.ts`).
- Structure: `describe(unit, () => { it('does X when Y', () => { ... }) })`.
- **One behaviour per `it` block.**
- Async tests: `await` every promise. Returning a promise without `await` hides assertion failures.
- Mocking: prefer dependency injection over module mocks. When you must mock, use `vi.mock` / `jest.mock` and reset between tests (`beforeEach(() => vi.clearAllMocks())`).
- React: **React Testing Library**, not Enzyme. Query by role / label / text, not by class or test-id, unless nothing else works.
- Snapshot tests only for stable, intentional output. Snapshots that "just changed" are noise.

### Self-mock signals to refuse (rule from `clean-code.md` → Testing discipline)

- `jest.spyOn(sut, 'someMethod').mockReturnValue(...)` / `.mockResolvedValue(...)` / `.mockImplementation(...)` where `sut` is the subject under test.
- `jest.spyOn(FooService.prototype, 'someMethod').mockReturnValue(...)` in `foo.service.spec.ts`.
- `sut.calculateTotal = jest.fn(...)` — manually overwriting a method on the SUT instance.
- `vi.mocked(sut)` followed by stubbing the SUT's own methods.
- Reaching into privates via `(sut as any).privateMethod()` or `sut['privateMethod']()` — the issue is the *private access*, not the cast itself.

`any` / `as any` in `*.spec.ts` / `*.test.ts` is otherwise fine — test setup justifies looser typing. Only flag it when it's the vehicle for one of the violations above.

What's fine: `jest.fn()` for collaborator stubs passed via constructor DI, `vi.mock('../mailer')` to fake a side-effecting module at the boundary, `expect(mailer.send).toHaveBeenCalledWith(...)` on an outcome boundary.

### Never read mock call arguments by position

`repository.save.mock.calls[0][2]` says nothing about what that argument means, and it keeps compiling after a signature change invalidates it. Assert the call with named intent:

```ts
expect(repository.save).toHaveBeenCalledWith(id, versionId, expectedInfos, transaction);
```

When the *absence* of a property is the point, assert the exact object or that property's absence directly — a loose `expect.objectContaining` passes even when the property is present. If positional access is genuinely unavoidable, name the index once inside a test helper (`savedInfosOf(repository.save)`) so the meaning lives in one place. Detect with `rg 'mock\.calls'` over the changed test files.

## Formatter / linter to run

- `prettier --write .` (formatting).
- `eslint --fix .` (style + correctness).
- `tsc --noEmit` (type check).
- All three should pass before commit. CI should fail on warnings, not just errors.

## Forbidden and Prohibited

- never use any type assertions (`as` or ``)
