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,
preservesemantics, 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¶
- The core is written in Python, minimum version 3.12. Code follows
PEP 8 and is fully type-annotated, checked with
mypy --strict. - 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.
- 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.
- Distribution:
- PyPI package
rainbow-fmt(primary); - standalone executables for Linux, macOS, and Windows (tool to be chosen in the packaging task, e.g. PyInstaller or Nuitka);
- 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.
- Plugin code hooks are Python callables, discovered through the
rainbow_fmt.languagesentry-point group.
Consequences¶
- Fast iteration and a large contributor pool for the design phases;
tomllib,pathlib,concurrent.futures, andimportlib.metadatacover 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 (
typealiases, 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.