Documentation
¶
Overview ¶
Package treehygiene несёт ОБЩУЮ часть двух запретов корпуса, у которой до сих пор не было дома: словарь форм отложенной работы (ban #11) и разбор нелатинских узлов-идентификаторов Go (ban #17).
Предмет: у общей части не было владельца ¶
Держатели обоих запретов заведены дважды — в платформе (`PRO-Robotech/kacho:internal/repohygiene`) и в службе доступа (`PRO-Robotech/kaname:internal/check`). Предмет у каждого СВОЙ: каждый судит своё дерево, и снять ни один нельзя. Но РАЗБОР у них был общий, и держателя у его одинаковости не было ни одного.
Цена названа числом, а не словом. Замер по стволам обоих репозиториев 2026-09-13 (норма: непустые строки кода без комментариев, `LC_ALL=C sort -u`, сверка `comm -12`): пара разборов отложенной работы совпала на 99 строках из 186, пара разборов имён — на 17 из 34, а словарь форм совпадал по составу ТОЧНО — двенадцать записей с обеих сторон.
Совпадение содержимого нарушением не смягчается, а ДОКАЗЫВАЕТ его: одинаковы копии были сегодня, а держателя у одинаковости не было ни одного. Расхождение наступило бы молча — заведут в одном дереве тринадцатую форму, в другом её не будет, и написанное в ней окажется не находкой и не чистотой, а НЕВИДИМОСТЬЮ. Ни сборка, ни гейт, ни обзор диффа этого не видят: каждое дерево по отдельности исправно.
Расхождение при этом уже НАЧАЛОСЬ, и это замер, а не опасение: на день заведения пакета двенадцать форм совпадали затравкой и образцом дословно, но пример двенадцатой формы разошёлся («на время релиза» против «на время выпуска»), а объяснение одного вида вычитания — на два слова. Решающая половина (что именно ловится) ещё сходилась; заметить это было нечем.
Что здесь, а что остаётся у ПОТРЕБИТЕЛЯ ¶
Здесь — всё, что об одном предмете говорит одинаково в любом дереве: словарь форм, образец, отсев по затравке, граница употребления и упоминания, вычет имени функции стандартной библиотеки, обход и перепись, разбор узлов-имён.
У потребителя остаются ровно две вещи, и обе суть утверждения о ЕГО дереве:
- КОРЕНЬ ОБХОДА. Судить чужое дерево — краснеть на чужом, а в самостоятельном клоне соседа нет вовсе;
- ВИДЫ ВЫЧИТАНИЯ (DeferralSkip). Перечень вычитаемого есть утверждение о составе конкретного дерева, и проверяется он там же: вид, которому больше нечего вычитать, — находка потребителя, а не этого пакета. Готовых видов здесь нет НАМЕРЕННО: раздавая их, фундамент утверждал бы о чужом дереве то, чего не измерял, и предикат «вид без предмета» обессмыслился бы — под общим именем можно положить что угодно, и счёт не сдвинется.
Почему два предмета в одном пакете ¶
Их роднит не тема, а ЧИТАТЕЛЬ: оба разбора зовёт гейт гигиены дерева, оба берут состав у github.com/PRO-Robotech/corelib/treecorpus, и оба обязаны печатать объём осмотренного, чтобы «ноль находок» было отличимо от «ноль прочитанного». Разнеся их по двум пакетам, мы получили бы два дома для одной провязки и два места, где следующий будет искать.
Чего этот пакет НЕ делает ¶
Он не гейт и вердикта не выносит. Он отдаёт находки и перепись; решение — падать ли, и с каким текстом — принимает проба потребителя, потому что текст отказа обязан называть исходы, принятые в ЕГО дереве.
Он не судит ПРОЗУ: имя маркера внутри объяснения запрета — не отложенная работа, а её имя. Граница проходит между УПОТРЕБЛЕНИЕМ и УПОМИНАНИЕМ и описана у DeferralForms.
Он не держит ЭТО дерево. Фундамент своего гейта гигиены сегодня не несёт, и пакет его не заводит: гейт есть утверждение о дереве, и заводить его надо своим изменением, с инъекцией и переписью по этому дереву.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AuditDeferredWork ¶
func AuditDeferredWork(root string, skips []DeferralSkip) ( findings []DeferralFinding, census DeferralCensus, err error)
AuditDeferredWork обходит дерево под root и ищет маркеры отложенной работы.
Область — ВЕСЬ индекс дерева: корни не выписываются, а выводятся из него, поэтому каталог, заведённый завтра, попадает под разбор в день появления, а не тогда, когда кто-нибудь снова пройдёт по дереву руками.
skips — виды вычитания вызывающего (см. DeferralSkip). Пустой перечень — законный вход: он означает «не вычитать ничего» и делает разбор СТРОЖЕ, а не мягче, поэтому отказом не является. Счёт вычтенного по каждому виду уезжает в перепись, и вердикт «виду больше нечего вычитать» выносит вызывающий.
Возвращает находки и перепись осмотренного: «ноль находок» обязано быть отличимо и от «ноль прочитанного», и от «прочитал не там».
func DeferralSeeds ¶
func DeferralSeeds() []string
DeferralSeeds — затравки отсева. Нужны пробе сквозного пути: форма, чья затравка не встречается в её же примере, отсекается ДО образца и перестаёт ловиться, оставаясь на вид объявленной.
func HasDeferralSeed ¶
HasDeferralSeed — дешёвый отсев перед разбором.
Types ¶
type DeferralCensus ¶
type DeferralCensus struct {
Tracked int // элементов в индексе дерева
Read int // прочитано
// Roots — верхний уровень индекса, ВЫВЕДЕННЫЙ из него же.
Roots []string
// ByRoot — корень, затем сколько прочитано под ним.
ByRoot map[string]int
// SkipNames — виды вычитания в том порядке, в каком их объявил вызывающий.
// Держатся отдельно от счёта, потому что у карты порядка нет, а перепись
// обязана быть детерминированной.
SkipNames []string
// Skipped — вид вычитания, затем сколько вычтено.
Skipped map[string]int
// Mentions — совпадений, отсечённых как цитата.
Mentions int
}
DeferralCensus — объём осмотренного. Печатается всегда: «ноль находок» обязано быть отличимо от «ноль прочитанного», а «область покрыта» — от «область выписана».
func (DeferralCensus) String ¶
func (c DeferralCensus) String() string
String — перепись одной строкой, включая раскладку по корням.
type DeferralFinding ¶
DeferralFinding — одна находка с координатой.
func (DeferralFinding) String ¶
func (f DeferralFinding) String() string
type DeferralForm ¶
type DeferralForm struct {
// Seed — подстрока, обязательно присутствующая в любом совпадении.
Seed string
// Pattern — образец, решающий по существу.
Pattern string
// Example — строка, которую эта форма ОБЯЗАНА ловить. Проверяется пробой
// СКВОЗНЫМ путём (через отсев), а не образцом в отрыве от сита.
Example string
}
DeferralForm — одна форма отсрочки: затравка для дешёвого отсева и точный образец для решения.
Затравка и образец лежат в ОДНОЙ записи намеренно. Отсев по подстроке («есть ли в файле хоть одна затравка») экономит порядок величины: образец с альтернацией по всему дереву стоил в монорепо 155 секунд из 438 у пакета проверок, то есть треть его бюджета и, в конечном счёте, предел времени конвейера. Но отсев, живущий ОТДЕЛЬНЫМ списком, — второе место об одном предмете: добавят форму в образец, забудут в затравки, и разбор молча перестанет её видеть. Здесь такое расхождение невозможно by construction — и предикат, и отсев выводятся из одного перечня.
func DeferralForms ¶
func DeferralForms() []DeferralForm
DeferralForms — объявленные формы. Отдаётся КОПИЯ: перечень есть предмет пробы сквозного пути, и проба не вправе его двигать.
Копия при этом поверхностная, и этого достаточно: все поля формы — строки, то есть неизменяемы, и записать через неё в перечень нечего.
type DeferralSkip ¶
type DeferralSkip struct {
// Name — имя вида; оно же ключ в переписи.
Name string
// Why — зачем вид заведён; печатается в отказе, когда предмет у вида
// кончился, чтобы читатель не гадал, что именно снимать.
Why string
// Match — предикат узнавания по пути.
Match func(slashed string) bool
}
DeferralSkip — вид, вычитаемый из области, и предикат его узнавания.
Вычитается ровно то, что судить нельзя ПО СУЩЕСТВУ, а не то, что неудобно: фикстура гейта обязана уметь написать форму дефекта, иначе гейт не проверить инъекцией, а маркер в сгенерированном принадлежит генератору.
Перечень видов задаёт ПОТРЕБИТЕЛЬ и проверяет его сам: вид, которому больше нечего вычитать, — слепая зона, которую унаследует следующий (под его именем можно положить что угодно, и счёт не сдвинется). Поэтому обход считает вычтенное поштучно и отдаёт счёт переписью, а вердикт по нему выносит проба потребителя — она же знает состав своего дерева.
Match получает путь, отсчитанный от корня обхода, с прямыми разделителями.
type NonASCIIIdent ¶
type NonASCIIIdent struct {
// Position — файл:строка:колонка объявления либо употребления имени.
Position string
// Name — само имя, как оно записано.
Name string
// Rune — первый нелатинский знак имени. Печатается отдельно, потому что на
// глаз он от латинского двойника неотличим — в этом и предмет разбора.
Rune rune
}
NonASCIIIdent — одно нелатинское имя с координатой.
Координата обязательна: находка без неё — не действие. Правится такое имя ПО ПОЗИЦИИ РАЗБОРА, а не текстовой заменой: замена попадает внутрь строковых литералов и комментариев, где кириллица законна, и видно это только в готовом тексте.
func ScanNonASCIIIdents ¶
func ScanNonASCIIIdents(name string, src []byte) (seen int, findings []NonASCIIIdent, err error)
ScanNonASCIIIdents разбирает ОДИН исходник и возвращает: сколько имён осмотрено и какие из них нелатинские.
Отдельная функция, а не обход дерева, ради двух вещей сразу. Первая — доказательство: разбор обязан показать, что различает ИМЯ и текст, а показать это можно только позвав его синтетикой, где обе формы стоят рядом. Вторая — перепись: «имён осмотрено N» делает «ноль находок» отличимым от «разбор ничего не увидел».
Обход дерева остаётся у вызывающего вместе с корнем: судить чужое дерево значило бы краснеть на чужом (см. шапку пакета).
Неразбираемый файл возвращает признак, а не пустой успех: такой файл и собраться не может, об этом скажет сборка, — но молчать о нём нельзя, иначе перепись завысит объём осмотренного.
func (NonASCIIIdent) String ¶
func (f NonASCIIIdent) String() string