fix(no-issue-refs): per-line учёт строк ломает скан docstring'ов — пропуски и ложные срабатывания #6

Closed
opened 2026-06-19 21:53:26 +07:00 by claude-secretary · 1 comment

Проблема

scripts/check_no_issue_refs_in_comments.py извлекает комментарий через extract_comment(), который отслеживает состояние строкового литерала (in_string) по одной строке и не знает про многострочные строки (docstring'и). Из-за этого #NNN внутри docstring'ов обрабатывается некорректно — причём в обе стороны.

В отличие от него, check_no_session_marks_in_code.py сканирует COMMENT/STRING-токены через tokenize и работает корректно. Issue-refs-хук стоит привести к той же модели.

Репро

"""Docstring со ссылкой (#235) и внутри.

Продолжение #999 на отдельной строке без кавычек.
"""
x = 1  # обычный комментарий #777
$ python3 scripts/check_no_issue_refs_in_comments.py repro.py
FAIL [no-issue-refs-in-comments]: ссылки #NNN в комментариях запрещены.
  repro.py:3: Продолжение #999 на отдельной строке без кавычек.
  repro.py:5: x = 1  # обычный комментарий #777
exit=1

Две дефектные ветки:

  1. False negative (строка 1). #235 на строке-открытии docstring'а пропущен: после """ in_string становится True (нечётное число кавычек на строке), и #235 считается «внутри строки». Реальный кейс: в senokosov/baton issue-ref'ы в докстрингах массово прятались именно на """-строках и проходили хук молча (всплыло на baton#1007).
  2. False positive (строка 3). #999 на строке-продолжении docstring'а без кавычек флагается как комментарий: extract_comment стартует каждую строку с in_string=False, видит # вне строки и считает остаток комментарием. То есть результат зависит от того, есть ли на конкретной строке docstring'а кавычка — непредсказуемо.

Строка 5 (настоящий #-комментарий) ловится верно — регрессий по основному кейсу быть не должно.

Предлагаемое решение

Сканировать .py через tokenize, как в check_no_session_marks_in_code.py:

  • COMMENT-токены — проверять всегда (основной контракт хука);
  • STRING-токены (docstring'и) — решить, входят ли в скоуп. В senokosov/baton действует правило «не цитировать issue-ref'ы и в докстрингах кода тоже», так что покрыть STRING-токены логично — тогда хук станет симметричен session-marks-хуку. Если оставлять только комментарии — хотя бы убрать недетерминированный per-line парсер (false positive на строке 3).
  • TODO(#NNN)-исключение переиспользовать как есть.
  • На неразбираемом файле (оборванная строка/скобка) — fallback на построчный скан, как в session-marks-хуке.

Acceptance

  • #NNN в docstring'ах детектируется детерминированно (или сознательно исключён — но без зависимости от наличия кавычки на строке).
  • #NNN в обычных #-комментариях по-прежнему ловится; TODO(#N) разрешён.
  • Фикстуры fixtures/issue_refs/ дополнены docstring-кейсами (открытие + продолжение); bash fixtures/run_smoke.sh зелёный.
  • Тег (bump rev), consumer baton бампит при касании.

Заметки

  • У репо нет Actions-раннера → CI висит в waiting; верификация локально через fixtures/run_smoke.sh.
  • Источник: senokosov/baton#1007 (volody: «раз хук их не ловит — это баг, заведи задачу»).
  • Родственное: #4 (покрытие .ts/.tsx) — другой пробел, но обе задачи про общий парсер комментариев; стоит делать согласованно.
## Проблема `scripts/check_no_issue_refs_in_comments.py` извлекает комментарий через `extract_comment()`, который отслеживает состояние строкового литерала (`in_string`) **по одной строке** и не знает про многострочные строки (docstring'и). Из-за этого `#NNN` внутри docstring'ов обрабатывается некорректно — причём в обе стороны. В отличие от него, `check_no_session_marks_in_code.py` сканирует COMMENT/STRING-токены через `tokenize` и работает корректно. Issue-refs-хук стоит привести к той же модели. ## Репро ```python """Docstring со ссылкой (#235) и внутри. Продолжение #999 на отдельной строке без кавычек. """ x = 1 # обычный комментарий #777 ``` ``` $ python3 scripts/check_no_issue_refs_in_comments.py repro.py FAIL [no-issue-refs-in-comments]: ссылки #NNN в комментариях запрещены. repro.py:3: Продолжение #999 на отдельной строке без кавычек. repro.py:5: x = 1 # обычный комментарий #777 exit=1 ``` Две дефектные ветки: 1. **False negative (строка 1).** `#235` на строке-открытии docstring'а пропущен: после `"""` `in_string` становится True (нечётное число кавычек на строке), и `#235` считается «внутри строки». Реальный кейс: в `senokosov/baton` issue-ref'ы в докстрингах массово прятались именно на `"""`-строках и проходили хук молча (всплыло на baton#1007). 2. **False positive (строка 3).** `#999` на строке-продолжении docstring'а **без кавычек** флагается как комментарий: `extract_comment` стартует каждую строку с `in_string=False`, видит `#` вне строки и считает остаток комментарием. То есть результат зависит от того, есть ли на конкретной строке docstring'а кавычка — непредсказуемо. Строка 5 (настоящий `#`-комментарий) ловится верно — регрессий по основному кейсу быть не должно. ## Предлагаемое решение Сканировать `.py` через `tokenize`, как в `check_no_session_marks_in_code.py`: - COMMENT-токены — проверять всегда (основной контракт хука); - STRING-токены (docstring'и) — решить, входят ли в скоуп. В `senokosov/baton` действует правило «не цитировать issue-ref'ы и в докстрингах кода тоже», так что покрыть STRING-токены логично — тогда хук станет симметричен session-marks-хуку. Если оставлять только комментарии — хотя бы убрать недетерминированный per-line парсер (false positive на строке 3). - `TODO(#NNN)`-исключение переиспользовать как есть. - На неразбираемом файле (оборванная строка/скобка) — fallback на построчный скан, как в session-marks-хуке. ## Acceptance - [ ] `#NNN` в docstring'ах детектируется детерминированно (или сознательно исключён — но без зависимости от наличия кавычки на строке). - [ ] `#NNN` в обычных `#`-комментариях по-прежнему ловится; `TODO(#N)` разрешён. - [ ] Фикстуры `fixtures/issue_refs/` дополнены docstring-кейсами (открытие + продолжение); `bash fixtures/run_smoke.sh` зелёный. - [ ] Тег (bump rev), consumer baton бампит при касании. ## Заметки - У репо нет Actions-раннера → CI висит в `waiting`; верификация локально через `fixtures/run_smoke.sh`. - Источник: `senokosov/baton#1007` (volody: «раз хук их не ловит — это баг, заведи задачу»). - Родственное: #4 (покрытие `.ts/.tsx`) — другой пробел, но обе задачи про общий парсер комментариев; стоит делать согласованно.
Author
Owner

Закрываю: сделано в #12main с v0.12.0).

Разбор .py в check_no_issue_refs_in_comments.py идёт через tokenize — COMMENT-токены плюс docstring'и модуля/класса/функции (классификация через ast), общим слоем scripts/comment_scan.py с no-broken-repo-paths. Обе описанные ветки закрыты по построению: #235 на строке-открытии docstring'а больше не прячется за нечётной кавычкой, а #999 на строке-продолжении не считается #-комментарием — состояние держится пофайлово, а не пересобирается на каждой строке.

По пунктам приёмки: docstring-фикстуры — fixtures/issue_refs/docstring_ref.py, docstring_class.py, docstring_async.py, bom_docstring.py, todo_in_docstring.py; TODO(#N) остался исключением (и сузился до строгого формата вместе с todo-needs-issue); неразбираемый и не-UTF-8 файл не роняет хук стектрейсом.

Родственное #4 закрыто лишь наполовину и остаётся открытым: no-issue-refs-in-comments фронт уже видит (жёсткий types: [python] снят, C-подобный режим с пофайловой машиной состояний есть в comment_scan), а no-session-marks-in-code в не-.py файлах по-прежнему сканирует строку целиком, то есть на .ts/.tsx пройдётся и по коду. Остаток по разметочным расширениям (.vue/.html/.svelte, .css) ведётся в #11.

Закрываю: сделано в [#12](https://git.homedevlab.ru/senokosov/pre-commit-hooks/pulls/12) (в `main` с `v0.12.0`). Разбор `.py` в `check_no_issue_refs_in_comments.py` идёт через `tokenize` — COMMENT-токены плюс docstring'и модуля/класса/функции (классификация через `ast`), общим слоем `scripts/comment_scan.py` с `no-broken-repo-paths`. Обе описанные ветки закрыты по построению: `#235` на строке-открытии docstring'а больше не прячется за нечётной кавычкой, а `#999` на строке-продолжении не считается `#`-комментарием — состояние держится пофайлово, а не пересобирается на каждой строке. По пунктам приёмки: docstring-фикстуры — `fixtures/issue_refs/docstring_ref.py`, `docstring_class.py`, `docstring_async.py`, `bom_docstring.py`, `todo_in_docstring.py`; `TODO(#N)` остался исключением (и сузился до строгого формата вместе с `todo-needs-issue`); неразбираемый и не-UTF-8 файл не роняет хук стектрейсом. Родственное [#4](https://git.homedevlab.ru/senokosov/pre-commit-hooks/issues/4) закрыто лишь наполовину и остаётся открытым: `no-issue-refs-in-comments` фронт уже видит (жёсткий `types: [python]` снят, C-подобный режим с пофайловой машиной состояний есть в `comment_scan`), а `no-session-marks-in-code` в не-`.py` файлах по-прежнему сканирует строку целиком, то есть на `.ts/.tsx` пройдётся и по коду. Остаток по разметочным расширениям (`.vue`/`.html`/`.svelte`, `.css`) ведётся в [#11](https://git.homedevlab.ru/senokosov/pre-commit-hooks/issues/11).
Sign in to join this conversation.
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#6
No description provided.