Help us shape the future of Go! Take the 2026 Go Developer Survey

treehygiene

package
v1.16.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

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

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

func HasDeferralSeed(body string) bool

HasDeferralSeed — дешёвый отсев перед разбором.

func InImportSet added in v1.10.0

func InImportSet(path string, set []string) bool

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

type DeferralFinding struct {
	Where string
	Line  string
}

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL