# Вклад в Ktav
**Languages:** [English](CONTRIBUTING.md) · **Русский** · [简体中文](CONTRIBUTING.zh.md)
## Основные правила
### 1. Каждый bug-fix сопровождается регрессионным тестом
Когда вы нашли баг, **перед тем как чинить**, напишите тест, который
его воспроизводит — тест **должен падать на `main`** и проходить
после фикса. Включите оба изменения в один PR.
Тест должен жить рядом со связанными тестами (например, в
`tests/edge_cases/<topic>.rs` или `tests/ser/<topic>.rs`). Короткий
комментарий к тесту объясняет характер падения, чтобы читатель через
десять месяцев понимал, *почему* этот случай важен.
Почему: молчаливые регрессии — самое смертельное, что может
случиться с библиотекой, отгружаемой множеству пользователей.
Документированная растяжка не стоит ничего в долгосрочной
перспективе.
### 2. Чувствительные к производительности изменения сопровождаются числами before/after
Если PR трогает что-либо из:
- `src/parser/` / `src/thin/parser.rs` — горячий путь парсинга
- `src/ser/text_serializer.rs` / `src/render/` — горячий путь
сериализации
- `src/thin/deserializer.rs` / `src/de/` — горячий путь
десериализации
- `src/value/` — динамический тип значения
… в описании PR должен быть блок с **before/after** числами criterion
по затронутым бенчам:
```
parse_to_struct/100_upstreams_typed
before: 275 µs
after: 198 µs
change: -28%
```
Цель — не быть самым быстрым любой ценой, а сделать изменения
*подотчётными*. Регрессия 5 % допустима, если она покупает ясность
или корректность, но должна быть видна и обоснована.
Используйте удобный скрипт:
```
./bench.sh # quick run (warmup 1s, measurement 2s, 20 samples)
./bench.sh full # criterion defaults — longer, more accurate
./bench.sh parse # filter: only parse benches
./bench.sh render # filter: only render benches
Criterion хранит числа прошлого прогона в `target/criterion/` и
автоматически сравнивает их при следующем прогоне, так что в выводе
будет `change: +/- X %`.
### 3. Изменения публичного API отмечают совместимость
Если вы трогаете что-либо под `pub` в `lib.rs`, в описании PR
укажите — это:
- **semver-совместимо** (добавления, ослабленные bounds, правки
документации); или
- **ломает semver** (переименования / удаления, изменённые сигнатуры,
ужесточённые bounds) — в этом случае bump версии идёт в следующий
`MINOR`, пока мы pre-1.0.
Обновите `CHANGELOG.md` в том же PR под `## [Unreleased]`.
### 4. Одна концепция — один коммит
Коммиты должны быть атомарными: bug-fix и его тест вместе, новая
фича и её тесты вместе. Переименование — отдельный коммит. Рефакторинг,
который попутно чинит баг, — вероятно, должен быть двумя коммитами.
`git log --oneline` должен читаться как changelog. Пишите именно так.
## Получение кода
Набор тестов соответствия спецификации находится в git-субмодуле `spec/`
([`ktav-lang/spec`](https://github.com/ktav-lang/spec)). Клонируйте
вместе с субмодулем, чтобы `cargo test` мог его запустить:
```
git clone --recurse-submodules https://github.com/ktav-lang/rust
```
Если репозиторий уже склонирован без `--recurse-submodules`:
```
git submodule update --init
```
## Запуск тестов
```
cargo test # все тесты (включая conformance)
cargo test --test spec_conformance # только языково-нейтральный набор
cargo test --test edge_cases # одна категория
cargo test multiline # по фильтру имени
cargo test --doc # doc-тесты
```
Категории тестов:
- `src/**/tests.rs` — приватные unit-тесты на модуль.
- `tests/de/*` — десериализация по фичам.
- `tests/ser/*` — сериализация по фичам.
- `tests/roundtrip/*` — round-trip (`T → text → T`).
- `tests/edge_cases/*` — комбинаторные edge case-ы (литералы со
скобками, ключевые слова в map-ах, глубокая вложенность, особые
строки…).
- `tests/fixtures.rs` — end-to-end по реальным `.conf` файлам.
- `tests/spec_conformance.rs` — языково-нейтральный набор из
`ktav-lang/spec` (valid-фикстуры совпадают с JSON-оракулом;
invalid-фикстуры отклоняются; valid-фикстуры переживают round-trip).
## Benchmarks
Источник: `benches/parse.rs` (criterion). Сценарии охватывают:
- `parse_to_value` — сырой parse в `Value` (владеющее, публичное
дерево).
- `parse_to_struct` — parse по thin-пути в типизированную структуру.
- `render` — сериализация типизированной структуры в текст.
- `roundtrip` — parse + render.
- `multiline_dedent` — разбор многострочных строк при разном
количестве строк.
## Руководство по раскладке кода
Правило декомпозиции: **один экспортируемый элемент на файл**.
Приватные хелперы живут рядом с типом, который ими пользуется.
Папки группируют тесно связанные элементы (весь `src/thin/` — это
zero-copy путь десериализации).
```
src/
├── lib.rs public entry points
├── value/ owned Value enum (public)
├── parser/ Value-building parser
├── render/ Value → text
├── thin/ zero-copy de path (ThinValue → T)
├── ser/ T → Value (public) + T → text (direct)
├── de/ Value → T (via ValueDeserializer)
└── error/ Error + serde impls
```
## Философия (чего не делать)
Девиз Ktav: «будь другом конфига, а не его экзаменатором». Перед тем
как предлагать новую фичу, спросите:
- Добавляет ли это новое правило, которое читателю придётся держать
в голове?
- Можно ли всё ещё понять строку без её соседей?
Новые правила всегда дорого стоят. Отклоняйте всё, что не проходит
эти две проверки.