Гейт роняет проверку, судящую ответ по живой карте заголовков
- forbidigo с analyze-types запрещает в файлах проверок обращение к httptest.ResponseRecorder.Header и .HeaderMap: правило судит по типу приёмника, поэтому ловит и цепочку, и переменную, и индекс, и обход - класс стоил трёх зелёных гейтов при неработающем коде; правило записано строкой в перечне механизированного, прозой не дублируется - поправлены два утверждения «механизировано: ничего», разошедшиеся с включённым линтером и со сверкой шага схемы
This commit is contained in:
@@ -4,7 +4,41 @@ linters:
|
|||||||
default: standard
|
default: standard
|
||||||
enable:
|
enable:
|
||||||
- errorlint
|
- errorlint
|
||||||
|
- forbidigo
|
||||||
settings:
|
settings:
|
||||||
|
forbidigo:
|
||||||
|
# Проверка ответа судит по **готовому ответу**, а не по изменяемому
|
||||||
|
# состоянию обработчика. `httptest` устроен зеркально настоящему серверу:
|
||||||
|
# `Header()` отдаёт живую карту, доступную и после записи ответа, а снимок,
|
||||||
|
# который получит клиент, лежит отдельно и читается через `Result()`.
|
||||||
|
# Проверка, читающая живую карту, зелена при неработающем коде — класс
|
||||||
|
# всплывал трижды (docs/review.md, записи 2026-08-10, 2026-08-11 и
|
||||||
|
# 2026-08-12) и трижды стоил зелёного гейта.
|
||||||
|
#
|
||||||
|
# Правило действует только в `*_test.go` — исключение ниже: в самом
|
||||||
|
# обработчике `Header()` и есть способ отдать заголовок.
|
||||||
|
#
|
||||||
|
# Судит оно **по типу приёмника**, а не по печатному тексту вызова
|
||||||
|
# (`analyze-types`), и в этом весь смысл: запрет на цепочку
|
||||||
|
# `w.Header().Get` обходится одной лишней строкой — `h := w.Header()`, —
|
||||||
|
# а также чтением по индексу карты и обходом `range`. По типу под правило
|
||||||
|
# попадают все эти формы разом. Записать его текстом нельзя ещё и потому,
|
||||||
|
# что `.Header` носят и запрос (`req.Header.Set` в проверках законен), и
|
||||||
|
# снимок ответа (`w.Result().Header` — как раз то, к чему правило ведёт).
|
||||||
|
#
|
||||||
|
# Приёмник назван поимённо: подставной сервер в проверках отдаёт заголовок
|
||||||
|
# через `w.Header().Set`, но у него приёмник — `http.ResponseWriter`, и под
|
||||||
|
# правило он не попадает.
|
||||||
|
#
|
||||||
|
# `HeaderMap` — тот же живой снимок прежним именем поля; правило второе,
|
||||||
|
# потому что о его устарелости говорит `staticcheck` (SA1019), а о том,
|
||||||
|
# почему по нему не судят ответ, — только это сообщение.
|
||||||
|
analyze-types: true
|
||||||
|
forbid:
|
||||||
|
- pattern: '^httptest\.ResponseRecorder\.Header$'
|
||||||
|
msg: 'проверка судит ответ по живой карте заголовков: читай w.Result().Header'
|
||||||
|
- pattern: '^httptest\.ResponseRecorder\.HeaderMap$'
|
||||||
|
msg: 'проверка судит ответ по живой карте заголовков: читай w.Result().Header'
|
||||||
errcheck:
|
errcheck:
|
||||||
# Без этого `_ = x.Close()` снимает замечание, и критерий «отказ не
|
# Без этого `_ = x.Close()` снимает замечание, и критерий «отказ не
|
||||||
# теряется молча» принимается реализацией, которая его теряет. Отказ,
|
# теряется молча» принимается реализацией, которая его теряет. Отказ,
|
||||||
@@ -20,6 +54,14 @@ linters:
|
|||||||
# Метод сам логирует ошибку отправки, вызывающему она не нужна
|
# Метод сам логирует ошибку отправки, вызывающему она не нужна
|
||||||
- (*git.vakhrushev.me/av/transcriber/internal/controller/tg.TelegramController).send
|
- (*git.vakhrushev.me/av/transcriber/internal/controller/tg.TelegramController).send
|
||||||
|
|
||||||
|
exclusions:
|
||||||
|
rules:
|
||||||
|
# `path-except` оставляет находки только в файлах проверок: в рабочем коде
|
||||||
|
# чтение своей карты заголовков законно.
|
||||||
|
- linters:
|
||||||
|
- forbidigo
|
||||||
|
path-except: '_test\.go$'
|
||||||
|
|
||||||
formatters:
|
formatters:
|
||||||
enable:
|
enable:
|
||||||
- gofmt
|
- gofmt
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ htmx, а здесь решено делать SPA — и перенесённы
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Сравнение ошибок через `errors.Is` и `errors.As`, не `==` и не приведением типа | `.golangci.yml` → `errorlint` |
|
| Сравнение ошибок через `errors.Is` и `errors.As`, не `==` и не приведением типа | `.golangci.yml` → `errorlint` |
|
||||||
| Непроверенное возвращаемое значение ошибки | `.golangci.yml` → `errcheck`, включая присваивание в `_` (`check-blank`). Отказ, который решено не проверять, объявляют в `exclude-functions` поимённо — там сегодня `defer Close` и `send` |
|
| Непроверенное возвращаемое значение ошибки | `.golangci.yml` → `errcheck`, включая присваивание в `_` (`check-blank`). Отказ, который решено не проверять, объявляют в `exclude-functions` поимённо — там сегодня `defer Close` и `send` |
|
||||||
|
| Проверка судит ответ по готовому ответу (`Result()`), а не по живой карте заголовков обработчика | `.golangci.yml` → `forbidigo` с `analyze-types`, находки только в `*_test.go`. Судит по типу приёмника (`httptest.ResponseRecorder`), поэтому ловит любую форму: цепочкой, через переменную, по индексу карты, обходом, полем `HeaderMap`. Остаётся ревью проверка, идущая мимо recorder — через свой `http.ResponseWriter` |
|
||||||
| Форматирование исходников | `.golangci.yml` → `gofmt` |
|
| Форматирование исходников | `.golangci.yml` → `gofmt` |
|
||||||
| Подозрительные конструкции языка | `.golangci.yml` → `govet`, `staticcheck`, `ineffassign`, `unused` |
|
| Подозрительные конструкции языка | `.golangci.yml` → `govet`, `staticcheck`, `ineffassign`, `unused` |
|
||||||
| Секреты в коммите | `lefthook.yml` → `gitleaks git --staged` |
|
| Секреты в коммите | `lefthook.yml` → `gitleaks git --staged` |
|
||||||
@@ -68,6 +69,7 @@ htmx, а здесь решено делать SPA — и перенесённы
|
|||||||
**Из перечисленного в записях правилом выражено одно** — сравнение ошибок через
|
**Из перечисленного в записях правилом выражено одно** — сравнение ошибок через
|
||||||
`errors.Is` и `errors.As` (`errorlint`, строка таблицы выше). Прозой остаётся всё
|
`errors.Is` и `errors.As` (`errorlint`, строка таблицы выше). Прозой остаётся всё
|
||||||
прочее: ни константный `msg` лога (`sloglint`), ни запрет `fmt.Print*` и
|
прочее: ни константный `msg` лога (`sloglint`), ни запрет `fmt.Print*` и
|
||||||
`os.Getenv` (`forbidigo`), ни запрет сторонних пакетов ошибок (`depguard`), ни
|
`os.Getenv` (`forbidigo` заведён, но одним правилом — о том, чем судят ответ в
|
||||||
архитектурные тесты-сканеры. Это следующий шаг переноса в правило: свойство,
|
проверках; этих двух запретов в нём нет), ни запрет сторонних пакетов ошибок
|
||||||
|
(`depguard`), ни архитектурные тесты-сканеры. Это следующий шаг переноса в правило: свойство,
|
||||||
оставшееся прозой, проверяет человек на каждом ревью заново.
|
оставшееся прозой, проверяет человек на каждом ревью заново.
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ OpenSpec.
|
|||||||
категория; шаг конвейера логирует и себя, и свой исход, и при этом возвращает
|
категория; шаг конвейера логирует и себя, и свой исход, и при этом возвращает
|
||||||
ошибку выше, где её логируют снова.
|
ошибку выше, где её логируют снова.
|
||||||
|
|
||||||
**Механизировано:** ничего. Ни `sloglint`, ни `forbidigo` в `.golangci.yml` не
|
**Механизировано:** ничего из перечисленного ниже. `forbidigo` в `.golangci.yml`
|
||||||
включены, поэтому правилами не выражено ни одно из перечисленного ниже.
|
включён, но правило у него одно и о другом — чем судят ответ в проверках
|
||||||
|
([README.md](README.md), «Механизировано»); `sloglint` не заведён, и ни один
|
||||||
|
пункт этой записи правилом не выражен.
|
||||||
|
|
||||||
## Принципы
|
## Принципы
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -281,8 +281,16 @@ API и имя не откатываются обратной правкой по
|
|||||||
- **Что меняем:** уборка перенесена до записи ответа; все проверки этого файла
|
- **Что меняем:** уборка перенесена до записи ответа; все проверки этого файла
|
||||||
судят по `Result()`. Класс всплывает **третий раз** (2026-08-10 «тесты
|
судят по `Result()`. Класс всплывает **третий раз** (2026-08-10 «тесты
|
||||||
http-обработчика ни разу не были зелёными», 2026-08-11 «проверка приёма не
|
http-обработчика ни разу не были зелёными», 2026-08-11 «проверка приёма не
|
||||||
могла упасть»), поэтому он же уходит кандидатом в конвенции: проверка ответа
|
могла упасть»), поэтому он же ушёл в конвенции правилом: проверка ответа
|
||||||
судит по готовому ответу, а не по изменяемому состоянию обработчика
|
судит по готовому ответу, а не по изменяемому состоянию обработчика.
|
||||||
|
Механизировано 2026-08-12 задачей `response-assertions-judge-result` —
|
||||||
|
`forbidigo` в `.golangci.yml` роняет гейт на чтении живой карты заголовков в
|
||||||
|
файле проверок. Правило судит по **типу приёмника**, а не по тексту вызова, и
|
||||||
|
потому ловит любую форму чтения живой карты — цепочкой, через переменную, по
|
||||||
|
индексу, обходом, полем `HeaderMap`. Текстовый запрет ловил только прямую
|
||||||
|
цепочку и обходился одной лишней строкой — это назвал прогон ревью этой же
|
||||||
|
задачи. Проходу ревью остаётся проверка, идущая мимо recorder, через свой
|
||||||
|
`http.ResponseWriter`
|
||||||
|
|
||||||
## 2026-08-12 — каждый анонимный запрос навсегда замедлял запись в хранилище [пойман ревью]
|
## 2026-08-12 — каждый анонимный запрос навсегда замедлял запись в хранилище [пойман ревью]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user