fix(private): разбор импортов и вызовов через ast, а не регуляркой #13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/ast-private-hooks"
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?
Найдено при заведении волн
no-private-importsиno-private-method-callsв jamzap/backend (эпик backend#465, задачи #471 и #472).Третий хук подряд оказался построчной регуляркой с ошибками в обе стороны
Это уже закономерность, и её стоит назвать прямо: хуки писались regex-first и не проверялись на реальном дереве.
no-issue-refs-in-commentsв #12 был первым, здесь — ещё два. Симптом один и тот же: молчаливый пропуск плюс ложное срабатывание, и обнаруживаются они только когда хук впервые включают на живом коде.no-private-importsfrom ... import, а имена стоят нижеfrom app.core.i18n import gettext_lazy as _lazyпадало на публичном имениВторое особенно неприятно: подчёркивание в алиасе — обычная разметка «не реэкспортируем из этого модуля», и «починка» волной означала бы ломать идиому ради дефекта инструмента.
Теперь проверяется именно импортируемое имя:
Голое
_— правка по ревью: ast-версия первой редакции ловила его как приватный символ, хотя старая регулярка требовала букву после подчёркивания и такое пропускала. Ирония в том, что PR мотивирован соседней идиомой из того же i18n-модуля.no-private-method-callsРегулярка искала
._name(в «коде», отрезая всё после первой решётки:Class._method(...)в docstring'е считался вызовом. На jamzap/backend так падали 4 файла, у которых весь «вызов» — заголовок теста вида «ТестыGeoConfig._parse_trusted_proxies»;astснимает оба случая по построению: строки и комментарии узламиCallне являются. Заодно ловится вызов, разложенный по строкам, и исключён dunder —obj.__init__()это протокол, а не приватный интерфейс.Дельта по потребителям
no-private-importsapp+tests+scripts)no-private-importsno-private-method-callstests)Все «снятые» просмотрены поштучно — это алиасы и упоминания в docstring'ах. Все «найденные» — настоящие многострочные импорты приватных символов.
Что изменилось по ревью
_больше не нарушение (см. выше) — единственный новый ложняк, который ast-версия успела ввести.from mypkg.helpers import (: уast.aliasесть собственныйlineno, у вызова берётсяend_linenoатрибута. Это не косметика — на pre-push вердикт фильтруется по изменённым строкам, и чужой номер там равен молчаливому пропуску. Оба номера теперь пиннятся в smoke черезrun_case_out.encoding="utf-8-sig"ast.parseпадает наU+FEFF,except SyntaxErrorглотает это, и весь файл проходит молча. Мутация «убратьencoding=» первую редакцию переживала.obj._compute_internal(стоял целиком на одной строке, то есть ловился и старой регуляркой. Переписана так, чтобы атрибут был не на строке объекта.if TYPE_CHECKING),from x import *.open()ронял их на не-UTF-8 стектрейсом» — для этих двух хуков нет, старый код оборачивал чтение вexcept Exceptionи возвращал[]. Реальные приобретения другие: (а) BOM больше не прячет файл целиком, (б)except Exceptionсузился доexcept (OSError, UnicodeDecodeError)— то есть старый код нарушал собственныйno-broad-exceptэтого же репозитория.self._helper()внутри тест-класса — тоже FAIL;obj.__mangled()и псевдоприватный stdlib-API (nt._replace()) регулярка пропускала,astловит; приватный модуль в пути импорта (from ._private import x) не ловится — записано как известное ограничение и заведено отдельной задачей.Проверка
Мутации, каждая роняет smoke: алиас вместо импортируемого имени; выпадение ветки
ast.Import; снятие исключения dunder (в каждом из двух хуков); проверка только первого имени в списке вместо всех; убратьencoding="utf-8-sig"; вернуть улику наnode.lineno(в обоих хуках); снова считать_приватным.Мутация «только первое имя» сначала переживала smoke — фикстура многострочного импорта держала приватное имя первым. Заведена вторая, где оно последнее: приватное имя дописывают в конец списка чаще, чем ставят в начало.
По дороге снят собственный неверный комментарий: я написал, что номер строки берётся у
Attribute, а не уCall, «чтобы улика указывала на сам._method». Проверил — уCallи у егоAttributeномер строки один и тот же, начало выражения; разница была придумана. Точную строку даётend_lineno, что и сделано теперь.Версия
0.14.0→0.15.0:no-private-importsначинает ловить существенно больше (многострочные импорты), это поведенческое изменение. Потребителям после бампа стоит ждать новых срабатываний ровно там.Первая редакция пиновала
v0.13.0— этот номер уже занят grandfather-фиксом (#15), аv0.14.0— хукомmax-filename-words(#7). Отдельно: на remote нет ни одного тега послеv0.9.1, из-за чего пин из README у консьюмера не резолвится — заведено #16.fd39f7de65d9ccf283acd9ccf283ac6d8398b9e1from ... import (...)#5