Documentation
¶
Overview ¶
Package lint surfaces docs-hygiene issues the link graph proper doesn't. Two checks: doc references written as inline code (`docs/design.md`) rather than as [text](path) links — which never become graph edges, so a doc only ever pointed at that way reads as an orphan — split into unlinked references (resolve to a file; turning them into links enriches the graph) and broken inline references (resolve to nothing); and deep relative links, real [text](path) links that climb several directories with `../`, which a root-absolute path (`/docs/x.md`) states more stably.
Index ¶
Constants ¶
const DeepLinkClimb = 2
DeepLinkClimb is the number of parent-directory hops ('../') at which a relative link is flagged in favor of a root-absolute path.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type DeepFix ¶
type DeepFix struct {
From string `json:"from"`
OldTarget string `json:"old_target"`
NewTarget string `json:"new_target"`
Count int `json:"count"`
}
DeepFix is one applied deep-link rewrite: the destination as written, its root-absolute replacement, and how many occurrences were rewritten.
func ApplyDeepFixes ¶
ApplyDeepFixes rewrites deep relative links in one file's content to their root-absolute form, replacing each `](target)` with `](suggestion)`. refs must be the DeepRelative refs for that file, one per flagged occurrence — each is rewritten only on its own Ref.Line, never by searching the whole file for matching text, so an identical span sitting in a code fence or illustrative example elsewhere in the file (never itself flagged by Analyze) is left alone. A destination not found verbatim on that line — a link with a title, a ?query, or already-edited text — is skipped, so the rewrite never guesses; only exact `](dest)` spans are touched. Returns the new content and the fixes applied.
type Ref ¶
type Ref struct {
From string `json:"from"`
Raw string `json:"target"` // the path as written
Anchor string `json:"anchor,omitempty"` // #section fragment, if any
To string `json:"to,omitempty"` // resolved rel-path; empty = unresolved
Suggest string `json:"suggest,omitempty"` // root-absolute rewrite (deep links only)
Candidates []string `json:"candidates,omitempty"` // ambiguous refs only: every same-named file it could mean
Line int `json:"line"`
}
Ref is one flagged reference — an inline-code doc path or a deep relative link — and where it points.
func KeepFiles ¶
KeepFiles returns the refs sourced from one of files — a set of vault-relative, forward-slashed rel-paths — narrowing a whole-vault report to a chosen set (e.g. the staged files a pre-commit hook passes). A nil/empty set keeps nothing.
func (*Ref) DestWithAnchor ¶
DestWithAnchor recombines the destination as written: Raw, plus "#Anchor" when the reference carried a #section fragment.
type Report ¶
type Report struct {
Unlinked []Ref `json:"unlinked"` // resolve to an indexed doc — should be links
Ambiguous []Ref `json:"ambiguous"` // bare basename matches >1 indexed file — needs a fuller path
Broken []Ref `json:"broken"` // resolve to nothing — dead inline paths
DeepRelative []Ref `json:"deep_relative"` // real links climbing ≥DeepLinkClimb dirs — prefer root-absolute
MissingTOC []TOCFinding `json:"missing_toc"` // long markdown files with no current Contents TOC
}
Report groups the flagged references by kind.
func Analyze ¶
Analyze loads the indexed files and link records and classifies them. Every inline-code doc-path reference (LinkCode) is unlinked only if it fully resolves — the path finds a file and any #section fragment finds a heading in it; a missing file or section makes it broken. A bare basename (no directory) matching more than one indexed file of the same extension is ambiguous instead: Resolver.Resolve's first-match pick would be arbitrary, so it's reported with its full candidate list rather than silently promoted to a possibly-wrong file. Every real markdown link (LinkMarkdown) that climbs DeepLinkClimb+ directories and stays within the tree is flagged deep-relative, with a root-absolute rewrite.
linkRoot is the vault's forward-slashed path within its enclosing repository ("" when the vault is the repo root). A leading-'/' link resolves against the repo root, so the rewrite is anchored there rather than at the vault — a vault indexed below the repo root ("subproj") gets that prefix, keeping the suggestion pointed at the intended file.
type Source ¶
type Source interface {
AllFiles() ([]string, error)
AllLinks() ([]index.LinkRow, error)
FileHeadings() (map[string]map[string]bool, error)
}
Source is the read side of the index lint needs. *index.Store satisfies it.
type TOCFinding ¶
type TOCFinding struct {
File string `json:"file"`
Lines int `json:"lines"`
Reason string `json:"reason"`
}
TOCFinding is one markdown file over toc.LineThreshold lines whose committed Contents TOC is missing or stale. Reason is "missing" (no up-to-date block) or "stale" (a block exists but no longer matches the headings).
func AuditTOCs ¶
AuditTOCs flags long markdown files lacking a current Contents TOC. A file is in scope when it is markdown, exceeds toc.LineThreshold lines, has at least one heading a TOC would list, and does not opt out with a `semantic-ignore-file` directive; read supplies file content by rel-path.
MDX is out of scope. A docs site renders its own page TOC from the headings, so a `## Contents` block there is a duplicate the reader sees twice — and --fix would write one into every long page. A file whose content can't be read is skipped rather than failing the audit.
type UnlinkedFix ¶
type UnlinkedFix struct {
From string `json:"from"`
Target string `json:"target"` // the inline-code destination as written (path[#anchor])
Link string `json:"link"` // root-absolute href written into the new link
Count int `json:"count"`
}
UnlinkedFix is one applied "make it a real link" rewrite: an inline-code path promoted to a markdown link, keeping the original text as the link's code-span label and pointing at a root-absolute target.
func ApplyUnlinkedFixes ¶
func ApplyUnlinkedFixes(content string, refs []Ref, linkRoot string) (string, []UnlinkedFix)
ApplyUnlinkedFixes rewrites each Unlinked ref's inline-code span (“ `dest` “) to a real markdown link (“ [`dest`](/link) “), turning a bare path mention into a link the graph can traverse.
The label keeps the original text verbatim rather than shortening it to a basename: a shortened label reads as ambiguous the moment two refs in the same file resolve to same-named files in different directories (two `main.go`s). The href is root-absolute rather than the raw destination: an Unlinked ref's raw text resolves vault-relative — like a wikilink, see Resolver.resolveWiki — which is not generally a valid same-directory relative link from wherever the reference happens to be written; a nested file writing “ `pkg/x.go` “ verbatim would produce a link GitHub resolves relative to that file's own directory, landing somewhere the path never meant to reach. Anchoring at the repo root the way GitHub follows a leading-'/' link keeps the promoted link correct regardless of where the reference lives — the same fix DeepFix already applies to deep-relative real links. linkRoot is the vault's path within its enclosing repo, as in Analyze.
refs must be the Unlinked refs for that file, one per flagged occurrence — each is rewritten only on its own Ref.Line, never by searching the whole file for matching text, so an identical span sitting in a code fence, an ignored placeholder, or another literal example elsewhere in the file (never itself flagged by Analyze) is left alone. A destination not found verbatim as a code span on that line (a stray ?query, already-edited text) is skipped, so the rewrite never guesses; only exact “ `dest` “ spans are touched.