fix: exclude в check-file-length на любой глубине, BOM не прячет broad except #22

Merged
claude-secretary merged 2 commits from fix/length-excludes-broad-except-bom into main 2026-08-27 12:30:23 +07:00

Закрывает #17 — два независимых дефекта, найденных ревью #7 и #13 по соседству с их скоупом.

1. Исключения check-file-length не работали в плоской раскладке

case "$file" in
    */alembic/versions/*) continue ;;
    */__init__.py) continue ;;
    */proto/generated/*) continue ;;
    proto/generated/*) continue ;;   # ← для proto обе формы были
esac

*/x/* требует непустой сегмент перед слэшем, поэтому путь alembic/versions/a1b2c3_add_user.py (миграции в корне репозитория — типовая раскладка) под исключение не попадал и проверялся на длину. То же с __init__.py пакета верхнего уровня. Для proto обе формы были предусмотрены с самого начала — теперь у всех трёх.

2. BOM прятал файл целиком от no-broad-except

check_no_broad_except.py читал файл голым open(). BOM приезжает в исходник символом U+FEFF, ast.parse падает invalid non-printable character U+FEFF, а except SyntaxError: return [] ниже превращает это в «нарушений нет». То есть одного BOM'а хватало, чтобы весь файл прошёл мимо хука молча — худший из возможных режимов отказа для запрещающего хука.

Лечение то же, что у соседей в v0.12.0 и v0.15.0: encoding="utf-8-sig" и except (OSError, UnicodeDecodeError).

Проверка

  • file_length_toplevel_alembic_skipped / file_length_toplevel_init_skipped гоняют хук на относительном пути без ведущих каталогов (иначе сработала бы старая форма исключения). Каталог лежит вне git, поэтому без исключения хук дал бы FAIL — базовой версии нет, old = 0.
  • no_broad_except_bom на фикстуре с BOM, no_broad_except_not_utf8_skipped на бинарном файле.
  • Мутации, каждая роняет smoke: снять новые формы исключений (краснеют оба toplevel-кейса), убрать utf-8-sig, убрать UnicodeDecodeError.

Заодно

Второй mktemp -d в середине fixtures/run_smoke.sh перезаписывал SMOKE_TMP и trap из шапки — первый временный каталог оставался на диске после прогона (trap перезаписывается, а не добавляется). Снят.

Что оставлено отдельной задачей

Сведение чтения файла в один хелпер: это уже шестой хук с той же строкой, и issue про это пишет прямо. Сейчас ровно эту строку правит #21, поэтому рефакторинг после его мержа, отдельным PR.

Версия

0.17.00.18.0. Порядок в очереди: #20 (0.16.0) → #21 (0.17.0) → этот. Если порядок мержа поменяется, номер поправлю.

Закрывает [#17](https://git.homedevlab.ru/senokosov/pre-commit-hooks/issues/17) — два независимых дефекта, найденных ревью [#7](https://git.homedevlab.ru/senokosov/pre-commit-hooks/pulls/7) и [#13](https://git.homedevlab.ru/senokosov/pre-commit-hooks/pulls/13) по соседству с их скоупом. ## 1. Исключения `check-file-length` не работали в плоской раскладке ```sh case "$file" in */alembic/versions/*) continue ;; */__init__.py) continue ;; */proto/generated/*) continue ;; proto/generated/*) continue ;; # ← для proto обе формы были esac ``` `*/x/*` требует непустой сегмент перед слэшем, поэтому путь `alembic/versions/a1b2c3_add_user.py` (миграции в корне репозитория — типовая раскладка) под исключение не попадал и проверялся на длину. То же с `__init__.py` пакета верхнего уровня. Для `proto` обе формы были предусмотрены с самого начала — теперь у всех трёх. ## 2. BOM прятал файл целиком от `no-broad-except` `check_no_broad_except.py` читал файл голым `open()`. BOM приезжает в исходник символом `U+FEFF`, `ast.parse` падает `invalid non-printable character U+FEFF`, а `except SyntaxError: return []` ниже превращает это в «нарушений нет». То есть одного BOM'а хватало, чтобы весь файл прошёл мимо хука **молча** — худший из возможных режимов отказа для запрещающего хука. Лечение то же, что у соседей в `v0.12.0` и `v0.15.0`: `encoding="utf-8-sig"` и `except (OSError, UnicodeDecodeError)`. ## Проверка - `file_length_toplevel_alembic_skipped` / `file_length_toplevel_init_skipped` гоняют хук на **относительном** пути без ведущих каталогов (иначе сработала бы старая форма исключения). Каталог лежит вне git, поэтому без исключения хук дал бы FAIL — базовой версии нет, `old` = 0. - `no_broad_except_bom` на фикстуре с BOM, `no_broad_except_not_utf8_skipped` на бинарном файле. - Мутации, каждая роняет smoke: снять новые формы исключений (краснеют оба toplevel-кейса), убрать `utf-8-sig`, убрать `UnicodeDecodeError`. ## Заодно Второй `mktemp -d` в середине `fixtures/run_smoke.sh` перезаписывал `SMOKE_TMP` и `trap` из шапки — первый временный каталог оставался на диске после прогона (`trap` перезаписывается, а не добавляется). Снят. ## Что оставлено отдельной задачей Сведение чтения файла в один хелпер: это уже шестой хук с той же строкой, и issue про это пишет прямо. Сейчас ровно эту строку правит [#21](https://git.homedevlab.ru/senokosov/pre-commit-hooks/pulls/21), поэтому рефакторинг после его мержа, отдельным PR. ## Версия `0.17.0` → **`0.18.0`**. Порядок в очереди: [#20](https://git.homedevlab.ru/senokosov/pre-commit-hooks/pulls/20) (`0.16.0`) → [#21](https://git.homedevlab.ru/senokosov/pre-commit-hooks/pulls/21) (`0.17.0`) → этот. Если порядок мержа поменяется, номер поправлю.
fix: exclude в check-file-length на любой глубине, BOM не прячет broad except
All checks were successful
ci / smoke (push) Successful in 15s
ci / smoke (pull_request) Successful in 15s
73ad7540af
Оба дефекта — hooks#17, найдены ревью по соседству и там не чинились.

1. `*/alembic/versions/*` и `*/__init__.py` требовали хотя бы один
   ведущий каталог, поэтому миграции в корневом `alembic/versions/` и
   `__init__.py` пакета верхнего уровня проверялись на длину наравне с
   обычным кодом. Для proto обе формы были с самого начала — теперь у
   всех трёх.

2. `check_no_broad_except.py` читал файл голым `open()`. BOM приезжает
   символом U+FEFF, `ast.parse` падает `invalid non-printable
   character`, а `except SyntaxError` превращает это в «нарушений нет»:
   одного BOM'а хватало, чтобы весь файл прошёл мимо хука молча. Та же
   дыра, что закрыта у соседних хуков в v0.12.0 и v0.15.0.

Кейсы: относительный путь без ведущих каталогов (каталог вне git —
без исключения хук дал бы FAIL), фикстура с BOM, не-UTF-8 файл.
Мутации, каждая роняет smoke: снять новые формы исключений, убрать
utf-8-sig, убрать UnicodeDecodeError.

Заодно снят второй `mktemp -d` в smoke: он перезаписывал SMOKE_TMP и
trap из шапки, и первый временный каталог оставался на диске.

Сведение чтения файла в один хелпер (шестой хук с той же строкой)
оставлено отдельной задачей — сейчас эту же строку правит #21.

Версия 0.17.0 → 0.18.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: ниты ревью — одна форма паттерна, негативный контроль, секция в README
All checks were successful
ci / smoke (push) Successful in 15s
ci / smoke (pull_request) Successful in 15s
4641ec08eb
- Исключения сравниваются с "/$file", а не дублируются двумя паттернами
  на каждое: следующий, кто добавит исключение, иначе наступил бы на тот
  же баг. Мутация «вернуть case "$file"» роняет оба toplevel-кейса.
- Негативный контроль `alembic/env.py` той же длины рядом: exit 0 у
  toplevel-кейсов — дефолтный ответ хука на что угодно нетриггерящее, и
  опечатка в пути, короткая фикстура или пустой $SMOKE_TMP оставили бы
  их зелёными. Заодно ловит переширение исключения до всего alembic/.
- Шапка скрипта описывала исключения как `**/alembic/versions/*.py` —
  ровно та форма, которой они и не соответствовали.
- У no-broad-except не было своей секции в README, хотя этот PR делает
  изменение видимым наружу: файл с BOM, который раньше проходил молча,
  после бампа начнёт падать.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-secretary force-pushed fix/length-excludes-broad-except-bom from 4641ec08eb
All checks were successful
ci / smoke (push) Successful in 15s
ci / smoke (pull_request) Successful in 15s
to 1d557e4a23
All checks were successful
ci / smoke (push) Successful in 17s
ci / smoke (pull_request) Successful in 16s
2026-08-27 12:29:58 +07:00
Compare
claude-secretary deleted branch fix/length-excludes-broad-except-bom 2026-08-27 12:30:23 +07:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
senokosov/pre-commit-hooks!22
No description provided.