refactor: чтение исходника — один слой read_source; skip не молчит #24
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/read-source-helper"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Закрывает #23.
Седьмая копия одной строки
open(path, encoding="utf-8-sig")+except (OSError, UnicodeDecodeError)жила в семи скриптах. Каждый раз, когда её забывали, хук получал одну и ту же дыру: BOM приезжает символомU+FEFF, парсер падает, перехват ошибки превращает это в «нарушений нет» — файл проходит мимо хука молча. Чинилось это четырьмя отдельными релизами —v0.12.0,v0.15.0,v0.17.0,v0.18.0— потому что дыру находили по одному хуку за раз.Теперь чтение —
scripts/read_source.py. Проверяемая разница: мутация «убратьutf-8-sig» роняет четыре BOM-кейса разных хуков сразу, а не один.Пропуск перестал быть невидимым
Недекодируемый файл пропускается со строкой в stderr — с именем хука и файла:
Exit-код при этом не меняется: один бинарник, попавший под слишком широкий
files:, не должен ронять вердикт по остальным файлам (кейсread_source_skip_keeps_verdictэто фиксирует). Файл, не читающийся с диска, по-прежнему пропускается тихо — он мог быть удалён тем же коммитом, а pre-commit всё равно передаёт его имя.У
no-broad-exceptчтение расплетено сast.parse: раньше оба стояли под однимtry, иexcept SyntaxErrorформально накрывал бы и ошибки чтения.migrations/versions/в check-file-lengthalembic init migrations— не менее типовая раскладка, чемalembic/, и до сих пор такие миграции проверялись на длину. Django-форма<app>/migrations/0001_x.pyнамеренно не исключена: уровняversions/там нет, а исключать любойmigrations/слишком широко — под ним держат и рукописный код. Оба решения закреплены кейсами (file_length_migrations_versions_skipped/file_length_django_migrations_checked) и записаны в README и шапке скрипта.Смоук
Появился
run_case_err— проверка stderr: до сих пор пиннился только stdout, а сообщение о пропуске по замыслу идёт в stderr.Отдельно стоит отметить: первая редакция этого хелпера стояла в файле ниже места вызова, bash молча отдавал 127, и три новых кейса не выполнялись вовсе — при том что весь прогон был зелёный. Поймано на прогоне и исправлено; ирония в том, что это ровно тот класс молчаливого зелёного, против которого написан весь PR.
Мутации, каждая роняет smoke: убрать
utf-8-sigв хелпере (4 кейса), убратьprintв веткеUnicodeDecodeError(3 кейса), снять исключениеmigrations/versions.Версия
0.18.0→0.19.0. Тегиv0.10.0–v0.18.0проставлены и запушены (#16 закрыт), так что пин из README снова резолвится.9fe784cef72ec71721a4Ревью (субагент, свежий контекст): 2 блокера, оба подтвердил и исправил.
B1 — восьмой читатель, написанный с той самой ошибкой.
check_model_comments.pyчитал голымread_text(encoding="utf-8"): BOM давал ложный FAIL сSyntaxErrorна валидном файле, не-UTF-8 и файл без прав — сырой трейсбек. То есть README с фразой «все скрипты читают одним слоем» был ложным инвариантом ровно того сорта, против которого PR. Переведён на хелпер, добавлены кейсыmodel_not_utf8_skippedиmodel_bom_ok; мутация «вернутьread_text» роняет оба.B2 — строка в stderr невидима в зелёном прогоне. Воспроизвёл на
pre-commit 4.6.1:Passed,rc=0, ни слова; с--verboseстрока появляется. Формально приёмка выполнялась, по смыслу — нет. Исправлено честно, а не переформулировкой: в README добавлен абзац «Где эту строку видно», а инстансу с широким скоупом в примере конфига проставленverbose: true. Там же названо, что тем же ограничением давно страдаютWARN/WARN(grandfather)вcheck-file-length— но как оговорка, а не как оправдание.Ниты:
*_not_utf8_skippedпереведены наrun_case_errс пином id хука (раньше неверная метка в сообщении проходила незамеченной у четырёх), плюс заведены недостающие кейсы дляno-plan-stage-refsиcheck-model-comments.run_case_silent: несуществующий путь обязан давать exit 0 и пустой stderr. Мутация «печатать и в веткеOSError» теперь краснеет; до этого вторая половина контракта была только декларацией.schema_migrations/versions/: якорь на слэш закреплён.migrations/versionsдоехал в exclude-примерmax-filename-wordsв README и вexamples/.alembic/versions/**иmigrations/versions/**: под каталогом пропускается всё содержимое, не только.py.SyntaxError), остаётся тихим намеренно — его не пропустят ruff/mypy, и дублировать их вердикт хук не должен.read_source_reports_skip_broad_except.read_source, на местах остался однострочник.N5 (stderr против stdout) — оставляю как есть, осознанно: сообщение о пропуске это не вердикт, а сопутствующее предупреждение, и оно консистентно с
WARN-строкамиcheck-file-length, которые тоже идут в stderr при exit 0. Порядок строк при пайпе действительно перемешивается — цена приемлемая; переносить вердикты в stderr ради этого не стоит.Отдельно про находку, которой не было в списке: дважды за эту работу я поставил вызов нового хелпера выше его объявления. bash печатал «команда не найдена», возвращал 127 и шёл дальше — кейсы молча не исполнялись, а сюита оставалась зелёной. Второй раз это поймал прогон, но полагаться на внимательность здесь нельзя, поэтому в
run_smoke.shдобавленcommand_not_found_handle, валящий прогон на любой неизвестной команде. Мутация на это тоже есть.