# Code Style

This document describes the conventions used in `@ironarachne/word-generator`.
Most formatting is enforced automatically by [Biome](https://biomejs.dev/); the
rest is convention that reviewers will look for.

Run the automated checks before opening a pull request:

```bash
npm run check      # lint + build + test
npm run lint:fix   # apply formatting and safe lint fixes
```

## Tooling

Biome is the single source of truth for formatting and linting. There is no
second formatter — do not add Prettier, dprint, or EditorConfig rules that
compete with `biome.json`. If a rule needs to change, change it in `biome.json`
so every contributor picks it up.

TypeScript is compiled with `strict: true` and `module: "NodeNext"`. Both
settings are deliberate; code that needs them relaxed should be reworked
instead.

## Formatting

- Two-space indentation, spaces only.
- Double quotes for strings.
- Semicolons are required.
- Trailing commas in multi-line literals, parameter lists, and argument lists.
- Imports are organized by Biome's `organizeImports` assist. Do not hand-sort
  them.

## Modules and imports

- Every source file is an ES module. There is no CommonJS in this package.
- Relative imports **must** carry a `.js` extension, even though the source is
  TypeScript. `module: "NodeNext"` requires the emitted specifier:

  ```typescript
  import { allElements } from "./elements.js";
  ```

- Import types with `import type`, and re-export them with `export type`, so
  type-only imports are erased from the emitted JavaScript:

  ```typescript
  import type WordElementSet from "./elementset.js";
  ```

- `src/index.ts` is the only public entry point. It re-exports and contains no
  logic of its own. Anything not re-exported there is internal and may change
  without a major version bump.

## File layout

| File                       | Responsibility                                                 |
| -------------------------- | -------------------------------------------------------------- |
| `src/index.ts`             | Public surface. Re-exports only.                                |
| `src/generator.ts`         | The `WordGenerator` class — all generation logic lives here.     |
| `src/pattern-tokenizer.ts` | Pattern validation and tokenization.                            |
| `src/elementset.ts`        | The `WordElementSet` data class.                                 |
| `src/elements.ts`          | The built-in `allElements` table of phonological categories.     |

New generation logic belongs on `WordGenerator`. Parsing concerns belong in
`PatternTokenizer` — the generator consumes tokens and should not re-parse a
pattern string itself.

## Elements and symbols

`allElements` is a published contract, not an implementation detail. Every
symbol in it is documented twice: in the TSDoc block at the top of
`src/elements.ts` and in the symbol reference table in `README.md`. A change to
one without the other is a bug.

- One symbol maps to exactly one set. Adding a symbol that collides with an
  existing one is breaking.
- Uppercase letters and unlisted characters are terminals, emitted verbatim in
  lowercase. Do not claim a symbol that would shadow a useful terminal.
- Element strings are lowercase, and may be multi-character (`ch`, `zh`, `ng`).

## Naming

- `camelCase` for functions, methods, variables, and parameters.
- `PascalCase` for classes, interfaces, and type aliases.
- `SCREAMING_SNAKE_CASE` for module-level constants.
- Names describe the result, not the implementation: `generateSet`, not
  `loopGenerate`.

## Types

- Annotate the return type of every exported function and public method, even
  when inference would get it right. It is part of the published contract.
- Interface members that are not meant to be mutated are marked `readonly`.
- `any` is not used in this codebase. Reach for a generic or a union first.

## Class conventions

- Internal state (`elementMap`, `tokenCache`, `cachedSymbols`,
  `cachedElementSets`) and helpers are `private`. Only add to the public
  surface when callers genuinely need it.
- Fields are declared at the top of the class, before the constructor.
- The caches exist for speed and must stay invisible to callers: any mutation
  of `patterns` or the element sets has to invalidate whatever it affects, so
  that a cached run and a cold run always produce the same word.

## Documentation comments

Every exported symbol — class, method, function, interface, and interface
member — carries a TSDoc block. These are the source for the published API docs
generated by TypeDoc, so they are not optional.

```typescript
/**
 * Generates a word from a randomly chosen pattern.
 * @returns The generated word.
 * @throws An Error if there are no active patterns to choose from.
 */
generate(): string {
```

- Document every parameter with `@param` and every non-void return with
  `@returns`.
- Document thrown errors with `@throws`, describing each condition that throws.
- Write in the third person, present tense: "Generates a word", not "Generate"
  or "This will generate".
- Inline `//` comments explain *why*, not *what*.

## Errors

Validate arguments up front, before doing any work, and throw a plain `Error`.
Messages state what could not be done and why, and include the offending value
where it helps:

```typescript
throw new Error("Cannot generate: no patterns available.");
```

Do not silently clamp, coerce, or return a fallback value for invalid input. An
unparseable pattern throws; it does not quietly generate a shorter word.

## Determinism

This is the constraint that matters most in this package.

- All randomness flows through the injected `RNG` instance. Never call
  `Math.random()` in `src/`.
- A given pattern and seed must always produce the same word. Changing the
  order or contents of an element set, changing how many times the generator
  draws from the RNG, or changing how the tokenizer splits a pattern changes
  every downstream result and is a **breaking change** requiring a major
  version bump.
- Bumping the `@ironarachne/rng` major version is breaking here for the same
  reason: the sequence behind every seed changes.
- Methods must not mutate their arguments, and `generate()` must not mutate
  `patterns`.

## Tests

Tests live in `tests/` and run under [Vitest](https://vitest.dev/).

- Seed the RNG explicitly so assertions are deterministic. Do not assert on the
  output of a generator constructed without a seed — the default seeds from
  `Date.now()`.
- Assert that the same seed and pattern produce the same word twice, and that
  two generators with the same seed agree.
- Cover the boundaries: empty pattern lists, groups, repeat operators at the
  start and end, unmatched parentheses, and every documented `@throws`
  condition.
- Cover the symbol table itself, so a symbol cannot be dropped silently.

## Dependencies

This package ships with exactly one runtime dependency,
[`@ironarachne/rng`](https://github.com/ironarachne/rng), and that is the
budget. New runtime dependencies need a strong justification in the pull
request. Dev dependencies should earn their place too — a tool that overlaps
with one already here should replace it rather than sit alongside it.
