# Вклад в 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
./bench.sh "parse|render" # any criterion regex
```

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: «будь другом конфига, а не его экзаменатором». Перед тем
как предлагать новую фичу, спросите:

- Добавляет ли это новое правило, которое читателю придётся держать
  в голове?
- Можно ли всё ещё понять строку без её соседей?

Новые правила всегда дорого стоят. Отклоняйте всё, что не проходит
эти две проверки.
