# Code Style

This document describes the conventions used in `@ironarachne/words`. 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`. That is deliberate; code that needs
it 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

- Relative imports **must** carry a `.js` extension, even though the source is
  TypeScript:

  ```typescript
  import { capitalize } from "./casing.js";
  ```

- Import types with `import type`, and re-export them with `export type`, so
  type-only imports are erased from the emitted JavaScript.
- `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

Source is split by domain, one file per concern, each with a matching test
file beside it:

| File               | Responsibility                                          |
| ------------------ | ------------------------------------------------------- |
| `src/index.ts`     | Public surface. Re-exports only.                        |
| `src/arrays.ts`    | Array helpers used by the text builders.                |
| `src/casing.ts`    | Case conversion: capitalize, title, camel, snake, kebab.|
| `src/grammar.ts`   | Articles, pronouns, copulas, possessives, quantifying.  |
| `src/inflection.ts`| Pluralization, singularization, comparatives.           |
| `src/numbers.ts`   | Ordinals and number-to-word conversion.                 |
| `src/text.ts`      | Sentence and phrase assembly.                           |

New functionality belongs in the domain file it fits. A new domain gets a new
file, its own test file, and a line in `src/index.ts` — not a grab-bag module.

Word lists and irregular-form tables live as module-level constants at the top
of the file that uses them, not inline in the function body.

## Naming

- `camelCase` for functions, variables, and parameters.
- `PascalCase` for interfaces and type aliases.
- `SCREAMING_SNAKE_CASE` for module-level constants (`SMALL_WORDS`).
- Names describe the result, not the implementation: `arrayToPhrase`, not
  `joinWithCommas`.

## Types

- Annotate the return type of every exported function, even when inference
  would get it right. It is part of the published contract.
- Prefer generics over `unknown` or `any` when a value passes through
  unchanged.
- `any` is not used in this codebase. Reach for a generic or a union first.

## Documentation comments

Every exported symbol carries a TSDoc block. These are the source for the
published API docs generated by TypeDoc, so they are not optional. This package
writes them with explicit types in braces, matching the existing files:

```typescript
/**
 * Returns the pronoun for a gender in the given word case.
 * @param {string} gender - The gender to look up. Unknown values fall back to neutral.
 * @param {string} wordCase - One of "subjective", "possessive", or "objective".
 * @returns {string} The pronoun.
 * @throws {Error} If wordCase is not one of the three supported values.
 */
```

- 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: "Returns the plural form", not
  "Return" or "This will return".
- Inline `//` comments explain *why*, not *what* — a comment earns its place
  when the English rule being encoded is not obvious from the code.

## Errors

Validate arguments up front, before doing any work, and throw a plain `Error`.
Messages state the offending value and what was expected:

```typescript
throw new Error(
  `Unknown word case: "${wordCase}". Expected "subjective", "possessive", or "objective".`,
);
```

Empty strings are not an error. Functions that take a word return the input
unchanged for `""` rather than throwing — see `capitalize`.

## Language behavior

This is the constraint that matters most in this package.

- English is full of exceptions. When a rule cannot cover a word, add it to the
  irregular table rather than bending the rule and breaking three other words.
- Changing the answer the library already gave for input it already handled is
  a **breaking change** and needs a major version bump. Widening coverage to
  input that previously fell through the rules is not.
- Every irregular form added needs a test asserting it, in both directions
  where the function pair supports it (`pluralize`/`singularize`).
- Handle multi-byte code points correctly. Case functions iterate with
  `[...word]`, not `word[0]`, so emoji and astral characters survive.

## Tests

Tests live beside the source as `src/<domain>.test.ts` and run under
[Vitest](https://vitest.dev/).

- One `describe` per exported function, named for it.
- Cover the boundaries: empty strings, single characters, already-plural input,
  zero and one for anything that quantifies, and every documented `@throws`
  condition.
- Assert that non-mutating functions leave their input untouched.

## Dependencies

This package ships with zero runtime dependencies, and that is a feature. 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.
