Skip to content

ADR 0002 — Core implementation language

  • Status: Accepted
  • Date: 2026-09-26
  • Decides: D1 in roadmap.md

Context

The core (CST interface, rule engine, configuration, printer, verifier, CLI, LSP) must be written in one primary language. The choice affects:

  • Iteration speed during the design-heavy early phases, when the rule format, preserve semantics, and comment attachment are still being discovered.
  • Plugin authoring. Language packs may contain code hooks; the host language is the language plugin authors must write.
  • Maintainer expertise. The maintainers work primarily in Python, with TypeScript as a second language.
  • Performance. Editor format-on-save needs roughly < 100 ms for a typical file; CI needs whole-repository runs in seconds to minutes.
  • Distribution to both Python and JavaScript/Svelte users.

Decision

  1. The core is written in Python, minimum version 3.12. Code follows PEP 8 and is fully type-annotated, checked with mypy --strict.
  2. The printer is isolated behind a narrow interface: it receives a Doc IR value and print options and returns text; it imports nothing from parsing, rules, or configuration. The Doc IR is plain, immutable data (frozen dataclasses / tuples) with no behaviour that depends on Python object identity. This keeps a later port of the printer (and, if needed, the rule-engine inner loop) to Rust via PyO3 a contained change.
  3. Performance is measured from Phase 1. A benchmark corpus and a timing job are part of CI from the first language pack. A Rust port is considered only when a benchmark shows a Python component is the bottleneck against the targets above; that decision gets its own ADR.
  4. Distribution:
  5. PyPI package rainbow-fmt (primary);
  6. standalone executables for Linux, macOS, and Windows (tool to be chosen in the packaging task, e.g. PyInstaller or Nuitka);
  7. an npm wrapper package that runs the standalone executable, so JS and Svelte projects can add rainbow-fmt as a dev dependency without a Python toolchain.
  8. Plugin code hooks are Python callables, discovered through the rainbow_fmt.languages entry-point group.

Consequences

  • Fast iteration and a large contributor pool for the design phases; tomllib, pathlib, concurrent.futures, and importlib.metadata cover much of the infrastructure from the standard library.
  • Raw throughput will be lower than native formatters (Ruff, Biome, dprint). Mitigations: per-file content-hash cache, multi-process file processing, and the isolated printer (decision 2).
  • The Doc IR and printer API must be designed as if they crossed a language boundary: no callbacks from the printer into Python rule code.
  • JavaScript users depend on the standalone executable; the release pipeline must build and test it on all three platforms.
  • Python 3.12 as the floor enables modern typing syntax (type aliases, PEP 695 generics) and excludes older interpreters; this is acceptable for a developer tool with no legacy users.

Alternatives considered

  • Rust. Best performance and single-binary distribution; tree-sitter is native Rust/C. Rejected for the initial phases: slower design iteration, a smaller pool of plugin authors, and less maintainer familiarity. Remains the target for any future hot-path port.
  • TypeScript. Natural for the JS/Svelte audience and Prettier's plugin ecosystem. Rejected: weaker fit for the Python audience and maintainers, and no performance advantage over Python large enough to decide the question.
  • Rust core with Python and JS bindings from day one. The eventual high-performance architecture, but it doubles the build and release complexity before the design is proven. Decision 2 keeps this path open.