Documentation
¶
Overview ¶
Package treehygiene несёт ОБЩУЮ часть запретов корпуса, у которой до сих пор не было дома: словарь форм отложенной работы (ban #11), разбор нелатинских узлов-идентификаторов Go (ban #17) и разбор заключения импортов — стек, принесённый внесённым поддеревом, не импортируется вне его (приёмка F1, `F1-51`; см. imports.go).
Предмет: у общей части не было владельца ¶
Держатели обоих запретов заведены дважды — в платформе (`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.
Он не держит ЭТО дерево: гейт есть утверждение о дереве, и заводится он своим изменением, с инъекцией и переписью по этому дереву. Единственный гейт фундамента по собственному дереву — заключение стека движка в поддерево — живёт в `internal/engineconfinement`, а не здесь.
Index ¶
- func AuditDeferredWork(root string, skips []DeferralSkip) (findings []DeferralFinding, census DeferralCensus, err error)
- func AuditImportConfinement(root string, rule ImportConfinement) (findings []ImportFinding, census ImportCensus, err error)
- func DeferralSeeds() []string
- func HasDeferralSeed(body string) bool
- func InImportSet(path string, set []string) bool
- type DeferralCensus
- type DeferralFinding
- type DeferralForm
- type DeferralSkip
- type ImportCensus
- type ImportConfinement
- type ImportFinding
- type NonASCIIIdent
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 AuditImportConfinement ¶ added in v1.10.0
func AuditImportConfinement(root string, rule ImportConfinement) ( findings []ImportFinding, census ImportCensus, err error)
AuditImportConfinement обходит отслеживаемые файлы `.go` дерева и судит каждое объявление импорта по правилу.
Состав берётся из индекса git (treecorpus), а не с диска: игнорируемый каталог с чужой копией дерева не предмет. Файл, который не разбирается, — отказ обхода с именем файла: без его объявлений перепись завысила бы объём осмотренного, а находка в нём осталась бы невидимой.
func DeferralSeeds ¶
func DeferralSeeds() []string
DeferralSeeds — затравки отсева. Нужны пробе сквозного пути: форма, чья затравка не встречается в её же примере, отсекается ДО образца и перестаёт ловиться, оставаясь на вид объявленной.
func HasDeferralSeed ¶
HasDeferralSeed — дешёвый отсев перед разбором.
func InImportSet ¶ added in v1.10.0
InImportSet отвечает, принадлежит ли путь импорта набору (правило — у поля ImportConfinement.Set, в том числе почему без учёта регистра). Экспортирована ради пробы границы правила: форма, о которой разбор не знает, — не находка и не чистота, а невидимость.
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 ImportCensus ¶ added in v1.10.0
type ImportCensus struct {
// Tracked — файлов в индексе дерева.
Tracked int
// GoFiles — прочитано отслеживаемых файлов `.go` (включая пробы и файлы
// под ограничениями сборки).
GoFiles int
// Imports — осмотрено объявлений импорта.
Imports int
// HomeImporters — файлов под Homes, импортирующих набор. Ноль при
// непустых Homes — предмет правила пропал: либо поддерево потеряло стек,
// либо обход слеп. Вердикт по нему выносит вызывающий.
HomeImporters int
}
ImportCensus — объём осмотренного. Печатается всегда: «ноль находок» обязано быть отличимо от «ноль прочитанного», а «набор внутри дома есть» — от «обход дома не увидел».
func (ImportCensus) String ¶ added in v1.10.0
func (c ImportCensus) String() string
String — перепись одной строкой.
type ImportConfinement ¶ added in v1.10.0
type ImportConfinement struct {
// Name — имя набора; печатается в находке.
Name string
// Set — пути модулей набора. Путь импорта принадлежит набору, если он
// РАВЕН элементу либо продолжает его через «/» (пакет модуля, мажорная
// версия: `github.com/go-jose/go-jose/v3`) или через «.v» (форма
// `gopkg.in`: `gopkg.in/square/go-jose.v2`). Иное продолжение — не набор:
// `github.com/go-jose/go-josex` чужой модуль, и назвать его находкой
// значило бы судить имя, а не импорт.
//
// Сравнение — БЕЗ УЧЁТА РЕГИСТРА. Хостинг кода не различает регистра
// организации и репозитория, и прокси модулей отдаёт тот же модуль под
// `github.com/Square/go-jose` и `gopkg.in/Go-Jose/go-jose.v2` (опрошено
// 2026-09-23): такой импорт собирается, и правило, сравнивающее с учётом
// регистра, его бы не видело. Чужой модуль в другом регистре остаётся
// чужим — продолжение судится тем же правилом.
Set []string
// Homes — каталоги от корня обхода, через «/», под которыми импорт набора
// законен. Пусто — законен НИГДЕ: так судит дерево, в котором поддерева
// нет. Каталог-дом, которого нет в индексе, — отказ обхода, а не пустое
// место: правило говорило бы о каталоге, которого нет, и «ноль импортёров
// дома» было бы неотличимо от «дом переехал».
Homes []string
// ForbiddenAtHome — пути модулей, которые под Homes НЕ импортируются;
// принадлежность — по тому же правилу, что у Set. Это обратная сторона
// заключения: поддерево не берёт нашего стека, и стеки не смешиваются.
ForbiddenAtHome []string
}
ImportConfinement — правило заключения набора путей импорта.
type ImportFinding ¶ added in v1.10.0
type ImportFinding struct {
// Position — файл:строка:колонка объявления импорта, от корня обхода.
Position string
// Path — путь импорта, как он записан.
Path string
// Why — какая половина правила нарушена.
Why string
}
ImportFinding — один импорт вне правила, с координатой.
func (ImportFinding) String ¶ added in v1.10.0
func (f ImportFinding) String() string
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