Metadata-Version: 2.4
Name: bslfmt
Version: 0.27.0
Summary: Детерминированный форматтер кода 1С (BSL): отступы и пробелы по стандартам 1С
Author: AzeevAN
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/AzeevAN/bslfmt
Project-URL: Repository, https://github.com/AzeevAN/bslfmt
Project-URL: Changelog, https://github.com/AzeevAN/bslfmt/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/AzeevAN/bslfmt/issues
Keywords: 1c,1c-enterprise,bsl,formatter,1с,форматтер
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Natural Language :: Russian
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Code Generators
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Dynamic: license-file

# bslfmt

Детерминированный форматтер структурных отступов BSL. Проект работает как
Python-библиотека и CLI; интеграция с MCP 1C будет отдельным потребителем пакета.

## Границы

Форматтер приводит отступы и форматирование поддерживаемых операторов, а
регистр ключевых слов — к каноническому. Он сохраняет значимые токены (без
учёта регистра) и порядок кода, строк, комментариев и директив. Он не
проверяет синтаксис BSL и не компилирует модули. Если структурное форматирование
небезопасно, команда завершает работу с диагностикой, не перезаписывая источник.

## Стиль форматирования

Стиль один, настроек нет. Он следует стандартам 1С «Тексты модулей» (std456) и
«Перенос выражений» (std444) и практике типовых конфигураций.

- **Отступы** — табуляцией, по вложенности блоков: процедуры и функции,
  `Если`/`ИначеЕсли`/`Иначе`, циклы, `Попытка`/`Исключение`. Английские и
  смешанные ключевые слова распознаются.
- **С колонки 0** — `Процедура`/`Функция` и их концы, аннотации
  (`&НаСервере`…), директивы препроцессора (`#Область`, `#Если`…, в том числе
  внутри процедур), переменные и код модуля вне процедур.
- **Строки продолжения** многострочной инструкции — +1 отступ к её первой
  строке. Строка с ведущим `И`/`Или`/`+`/`.` (а также `[`, `,`, `)` или
  литералом) — продолжение инструкции, как и строка после `Новый`,
  `Процедура`/`Функция` или `И`/`Или`/`Не` в конце строки; `Экспорт` на своей
  строке — часть объявления. `)` стоит на уровне строки с открывающей скобкой. Текст
  запроса сразу после `=` и после одиночного `Возврат` — как раньше.
- **Многострочные строки**: строки `|` сдвигаются вместе со строкой, где
  начался литерал; пробелы перед `|` не входят в значение строки.
- **Комментарии** `//` встают на отступ следующей строки кода; перед строкой,
  которая закрывает блок (`КонецЕсли`, `Иначе`…) или начинается с `)`, — на
  уровень глубже. Комментарий в колонке 0 (маркеры доработок `//!`, `//++`,
  закомментированный код) остаётся в колонке 0. Текст комментариев не меняется.
- **Перенос строк**: каждая инструкция — на своей строке (`А = 1; Б = 2;` →
  две строки); после `Тогда`, `Цикл`, `Попытка`, `Иначе`, `Исключение` тело
  начинается со следующей строки, а `КонецЕсли`, `КонецЦикла`,
  `КонецПопытки`, `Иначе`, `ИначеЕсли`, `Исключение` стоят отдельной строкой.
  Пустая инструкция (`Цикл;`, `Иначе;`) остаётся при своём слове, комментарий
  в конце строки — у последней части.
- **Раскладка по ширине 120**: инструкция (в том числе заголовок и
  объявление), которая с отступом (таб — 4 знака) помещается в 120 знаков,
  — одна строка; переносы автора внутри неё убираются. Иначе она режется по
  порядку: `Или` → `И` → `+` (каждая часть на своей строке, оператор в
  начале) → последняя пара скобок (параметры одной строкой, иначе по
  одному на строку, `)` отдельной строкой; пропущенные параметры — на
  строке предыдущего); части длиннее 120 режутся так же, рекурсивно. Если
  резать некуда, инструкция остаётся как была. Комментарий в конце строки в
  ширину не входит. Не трогаются инструкции с комментарием внутри,
  многострочным литералом или подряд идущими литералами, директивой,
  областью правки; литералы не режутся никогда. Внутри строки автора
  пробелы сохраняются как написаны (`Не(А)` остаётся `Не(А)`); на месте
  бывших переносов действует общее правило (`Ф` / `(А)` → `Ф(А)`).
  Идентификаторы могут содержать буквы любого алфавита (`і`, казахские
  `Қ`, `Ү`…): они раскладываются так же.
- **Пробелы**: вокруг бинарных операторов (`=`, `<>`, `+`, `%`…), после
  запятой; без пробелов перед `,` `;` `)` и после `(`. Лишние пробелы внутри
  строки схлопываются, хвостовые удаляются; выравнивание табами внутри
  строки заменяется одним пробелом.
- **Пустые строки**: не больше одной подряд. Строки из одних пробелов и табов
  (так их заполняет конфигуратор) не очищаются.
- **Регистр ключевых слов** — как в синтакс-помощнике 1С: `или` → `Или`,
  `конецЕсЛи` → `КонецЕсли`, `НЕ` → `Не`, `NULL` → `Null`. Так же директивы
  препроцессора и их символы (`#если сервер тогда` → `#Если Сервер Тогда`,
  `#область` → `#Область`) и аннотации (`&наклиенте` → `&НаКлиенте`).
  Английские слова остаются английскими (`endif` → `EndIf`). Не меняются
  имена переменных, методов и свойств (`Запрос.выполнить()`), имена меток и
  областей, неизвестные аннотации и символы препроцессора. Имя метода,
  совпадающее с ключевым словом, в объявлении получает его регистр
  (`Процедура выполнить()` → `Процедура Выполнить()`), а в вызове через
  точку (`Об.выполнить()`) — нет; на работу кода это не влияет.
- **Методы расширений** с `&ИзменениеИКонтроль` форматируются как обычный
  код: платформа не сверяет пробелы и табы в контролируемых строках.
- **Не трогаются**: строки и даты, текст комментариев, области
  `#Вставка`/`#Удаление`, регистр букв вне ключевых слов, порядок кода.

## Установка

Нужен Python 3.10+ или [uv](https://docs.astral.sh/uv/) — он сам скачает
Python. Пакет без зависимостей, одинаков для Windows, macOS и Linux.

```sh
uv tool install bslfmt        # команда bslfmt в терминале (рекомендуется)
pipx install bslfmt           # то же через pipx
uvx bslfmt Модуль.bsl         # разовый запуск без установки
```

Установить `uv`: Windows — `winget install astral-sh.uv` (или
`powershell -c "irm https://astral.sh/uv/install.ps1 | iex"`); macOS —
`brew install uv`; macOS и Linux — `curl -LsSf https://astral.sh/uv/install.sh | sh`.
Обновление: `uv tool upgrade bslfmt` (или `pipx upgrade bslfmt`).

### Для агентов и хуков

Форматировать файл на месте — одна команда, модуль через контекст агента не
передаётся:

```sh
uvx bslfmt -i Модуль.bsl            # отформатировать файл (uv сам поставит пакет)
uvx bslfmt --check Модуль.bsl       # код выхода 1 — файл нужно отформатировать
uvx bslfmt -i -sbc Модуль.bsl       # и удалить комментарии внутри методов
```

Если пакет установлен (`uv tool install bslfmt`), то же без `uvx`. Вывод —
UTF-8; сводка на файл: `ФАЙЛ: изменён (строк: N)` или `ФАЙЛ: без изменений`.

## Установка для разработки

```sh
python -m venv .venv
. .venv/bin/activate
python -m pip install -e .
python -m unittest discover -s tests -v
```

Команда `bslfmt` появляется в активном окружении.

CI (GitHub Actions) прогоняет тесты на Windows, macOS и Linux и проверяет
установленную команду: `python tests/cli_smoke.py` после `pip install .`.

## CLI

```sh
bslfmt Модуль.bsl                        # результат на экран, файл не меняется
bslfmt -i Модуль.bsl Форма.bsl           # отформатировать файлы на месте
bslfmt --check Модуль.bsl Форма.bsl      # только проверить (для CI и хуков)
bslfmt --check --diff Модуль.bsl         # проверить и показать различия
bslfmt Модуль.bsl --output Новый.bsl     # записать результат в новый файл
bslfmt - < Модуль.bsl                    # стандартный ввод
bslfmt -i -sbc Модуль.bsl                # и удалить комментарии внутри методов
bslfmt --help                            # справка по всем режимам
```

`-i` переписывает только файлы, которым нужна правка: запись атомарная
(временный файл рядом и замена), при отказе форматирования файл не меняется.
Замена создаёт новый файл: права копируются, а владелец, ACL, расширенные
атрибуты и жёсткие ссылки — нет. Символическая ссылка остаётся ссылкой,
переписывается её цель, даже вне текущего каталога: в CI и хуках по чужому
репозиторию передавайте `-i` только свои файлы.
Сводка на каждый файл: `ФАЙЛ: изменён (строк: N)` или `ФАЙЛ: без изменений`.
Файл только для чтения не переписывается (ошибка, код 2). `--` завершает
флаги: `bslfmt --check -- -файл.bsl`. Короткие флаги — только целиком
(`-s` не сокращение `-sbc`).
`-sbc`, `--strip-body-comments` (с любым режимом) удаляет строки-комментарии внутри
процедур и функций — закомментированный код, маркеры доработок, пояснения.
Это же касается строк-комментариев между строками многострочного литерала
(`//|  Поле,` в тексте запроса): в значение строки они не входят. Комментарии
в конце строки кода, над методами, внутри самого текста строки (`|// …` —
часть запроса: маркеры для `СтрЗаменить`, разделитель пакета) и в областях
`#Вставка`/`#Удаление` остаются. Значения строк не меняются — это проверяется
при каждом запуске. Без ключа комментарии не удаляются.
`--check` ничего не записывает и перечисляет файлы, которые нужно
отформатировать. Несколько файлов — только с `-i`, `--check` или `--diff`;
каждый обрабатывается отдельно. `--output` создаёт новый файл и отказывается
перезаписывать существующий.

Ввод и вывод (текст, различия, сводка, ошибки) — UTF-8 на любой ОС; переводы
строк и BOM сохраняются как в исходнике. Читаются обычные файлы и каналы
(`bslfmt <(git show HEAD:Модуль.bsl)`) не больше 80 000 000 байт; устройства —
нет.

Коды выхода (худший по всем файлам): `0` — успех (при `--check` — всё
отформатировано); `1` — `--check` нашёл файлы для форматирования; `2` —
ошибка аргументов, чтения или записи, неверная кодировка или отказ
форматирования (сообщение в stderr с именем файла и номером строки, если он
известен); `3` — внутренняя ошибка форматтера.

## Python API

```python
from bslfmt import format_code

formatted = format_code(source)
```

Исключения: `FormatError` — форматировать небезопасно (отказ, превышен
лимит); `LexerError` — исходник не разбирается (незакрытая строка или дата).
Оба — подклассы `ValueError` и импортируются из `bslfmt`; номер строки —
в атрибуте `line`.

Для недоверенного ввода есть лимиты: `max_chars` — длина исходника
(по умолчанию 20 000 000 символов), `max_depth` — вложенность блоков
(по умолчанию 100). При превышении — `FormatError`; `None` отключает лимит.

```python
formatted = format_code(source, max_chars=1_000_000, max_depth=50)
cleaned = format_code(source, strip_body_comments=True)
```

Сервису, который принимает код извне, стоит также запускать форматирование в
отдельном процессе с ограничением памяти и времени.

Поддерживаемые функции и ограничения уточняются по синтетическим тестам в
`tests/`; локальные корпуса и конфигурации в репозиторий не входят.

## Лицензия

Apache License 2.0 — см. `LICENSE` и `NOTICE`. Проект не аффилирован с ООО «1С».
