treehygiene

package
v1.5.0 Latest Latest
Warning

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

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

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

func HasDeferralSeed(body string) bool

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

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 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