On this page
Validating a project's documentation: every directive resolves, every public symbol is covered, every lint rule holds, and reviewed baselines are advanced.
#internal/check
#internal/check
Package check validates a project's documentation: every directive resolves, every public symbol is covered, and every lint rule holds.
The command's three answers come out of here. [CheckDocs] produces the whole verdict -- per-directive resolution results, the coverage measurement and every diagnostic -- [PrintResults] renders it for a reader and [SerializeCheckResult] renders it for a machine, and [AcceptBaselines] advances the staleness baseline of a page a person has reviewed.
#Post checks are here too
The Python this ports split post validation (POST001-POST007) out into the former blog package and reached it back through a registered hook, because the two packages shipped as separate installs and the docs generator could not import it. One binary has no such boundary: [CheckPosts] and [PostErrorLint] live beside the rules that consume them and [CheckDocs] calls them directly.
#Every rule runs over posts as well as pages
A post is a page on the site, so the lint slice [CheckDocs] judges is the docs tree plus the project's published posts, each keyed by its own source path so a diagnostic names a file a reader can open. Coverage and the staleness baselines are keyed by docs-tree page and see only the docs tree: a post is not one of those.
#StatusOK
const StatusOK = "OK"StatusOK is the Outcome of a directive that resolved.
#StatusFailed
const StatusFailed = "FAILED"StatusFailed is the Outcome of a directive that did not resolve.
#ErrShellWords
var ErrShellWords = errors.New("command template cannot be split into words")ErrShellWords is the base every refusal of a command template wraps, so one errors.Is check covers them.
#ErrUnbalancedQuote
var ErrUnbalancedQuote = fmt.Errorf("%w: no closing quotation", ErrShellWords)ErrUnbalancedQuote reports a command template that opens a quote and never closes it -- one of the two conditions shlex.split refuses.
#ErrDanglingEscape
var ErrDanglingEscape = fmt.Errorf("%w: no escaped character", ErrShellWords)ErrDanglingEscape reports a command template ending in a backslash, which escapes nothing -- the other condition shlex.split refuses.
#AcceptError
type AcceptError structAcceptError reports that selfdoc baseline accept cannot accept a named page.
#AcceptedBaseline
type AcceptedBaseline structAcceptedBaseline is one page whose baseline was advanced, with the error the advance cleared.
#StalenessState
type StalenessState structStalenessState is what [ComputeStalenessState] measured.
#DirectiveResult
type DirectiveResult structDirectiveResult is the result of validating a single directive.
#ResolvedDirective
type ResolvedDirective structResolvedDirective is a successfully resolved directive with its output.
#CoverageStats
type CoverageStats structCoverageStats is the coverage of a project's public symbols by its directives.
Two tiers are counted. A symbol is "referenced" when any directive's resolved output names it, and "documented" when it does so on a page that is not a bare generated skeleton -- a page whose description a person wrote, or customized.
The count fields are spelled Total, ReferencedCount and DocumentedCount rather than after the tiers, because [CoverageStats.TotalPublic] and [CoverageStats.Documented] are the methods lints.Coverage asks for and a field cannot share a method's name.
#CheckResult
type CheckResult structCheckResult is the whole verdict of one [CheckDocs] run.
#ComputeStalenessState
func ComputeStalenessState(ComputeStalenessState computes the current page hashes and the pages frozen in an error state.
It runs the same content, description, source-docstring and schema hashing that [CheckDocs] uses for STALE001/DRIFT001 detection, but never writes the hash store.
projectConfig may be nil, in which case it is loaded from selfdoc.json.
#AcceptBaselines
func AcceptBaselines(AcceptBaselines advances the stored baseline of each named page to its current hashes.
A deliberate, auditable human action meaning "reviewed: the page content changed but the existing frontmatter description is still accurate". Each named page must currently be frozen in a STALE001/DRIFT001 error state; accepting advances its baseline exactly as if the description had been rewritten, so the next check passes for that page.
pages are page identifiers exactly as selfdoc check shows them (for instance "en/cli-index.md"). projectConfig may be nil, in which case it is loaded from selfdoc.json.
Nothing is written when any named page is invalid: a page that is not a documentation page of this project, one with no baseline yet, and one that is not currently stale or drifted are each refused, and one refusal refuses the whole call.
#CheckDocs
func CheckDocs(CheckDocs validates every directive in a project's docs templates and reports its coverage and its diagnostics.
It walks the docs directory, parses the directives on every page, resolves each one, measures how much of the project's public surface the pages cover, and runs every lint rule over the pages and the project's published posts.
projectConfig may be nil, in which case it is loaded from selfdoc.json. dryRun reports staleness without writing the hash store -- under a previewing effects handle the write is recorded rather than performed, which gives the same reporting and an honest preview, so the command layer leaves this false and lets the handle decide. versionFilter, when non-empty, skips the multi-version validation pass (VER001), which is what build --version wants: it is checking one version and needs no cross-version answer. versionOverride is the version that version-bearing generated content is expected to embed (VER004), overriding the version detected from the project manifest; a release orchestrator passes the about-to-be-released version here, matching what it passes to selfdoc gen --version-override.
#ThemeCSS
func ThemeCSS(themeName string) (string, bool)ThemeCSS returns the stylesheet SEO012 measures for a theme.
The Python resolved a path on disk because the themes shipped as files in an installed package; here they are embedded, so the bytes come out of the theme registry instead. It is the theme's OWN sheet -- what themes.Overlay answers -- because that is the file that declares the custom properties a page is painted with; a framework theme's framework sheets carry the framework's own palette and are not selfdoc's to correct.
The second result is false for a theme this build does not ship, which is the counterpart of the Python's absent file: there is nothing to measure and no diagnostic to make.
#CheckJSONSyntax
func CheckJSONSyntax(text string) *jsonSyntaxErrorCheckJSONSyntax reports whether text is a JSON document CPython would accept, and answers with CPython's own message and line when it is not.
#PostErrorLint
func PostErrorLint(err *posts.PostError, postsDirRelative string) lints.LintResultPostErrorLint turns one post refusal into its lint diagnostic.
The mapping from a refusal to its POST code lives here and nowhere else, so every surface that reports post validation -- the check, and the editor judging an unsaved buffer -- says the same thing under the same code.
The coordinates come off the error, not out of the message: the detection site knew the post's path (and, for a stray marker, its line), and everything downstream that positions a diagnostic reads the structured fields rather than parsing prose. The CODE, though, is chosen by matching the message -- the refusals carry no kind of their own, and inventing one would have to be declared at every raise site in the post parser.
#CheckPosts
func CheckPosts(CheckPosts checks a project's blog posts for validation errors (POST001-POST007), returning one diagnostic for the first invalid post.
The result is empty when the posts directory is not on disk and when every post is valid. A project that declares no "posts" block is read at the conventional .selfdoc/posts/, which is where the post lints read it. Each diagnostic is positioned at the offending post -- its path relative to the project, and its line where the defect has one -- taken from the refusal the detection site raised.
#LintPostBuffer
func LintPostBuffer(LintPostBuffer runs the post-applicable lint rules over one in-memory post buffer.
The editor's diagnostics come from here, and they are the check's own diagnostics: the same slice conversion over the same rules, with the buffer overlaid on the saved post set exactly as the renderer overlays it. Nothing about a rule is restated for the editor, so a finding on screen is a finding selfdoc check will report, worded identically.
Two differences from the whole-project run, each deliberate:
- drafts are judged, because a draft is what is being written (and, as a consequence, a link to a draft post resolves here where the check would call it unknown -- the draft is on disk either way); - only the buffer's own diagnostics are returned, because the rest of the tree is not what the author is looking at.
The rules run over the post slice alone, not the whole docs tree. The one cross-page rule, XREF001, resolves a link against the page's own directory, and a post's own directory is the posts directory -- so the docs pages could never have matched a post's link anyway, and the slice is the universe the whole-project run offers a post.
sourcePath is the post's path relative to the posts directory and content is the buffer, frontmatter included. projectConfig may be nil, in which case it is loaded from selfdoc.json.
#SerializeCheckResult
func SerializeCheckResult(result *CheckResult, exitCode int) map[string]anySerializeCheckResult builds the machine payload selfdoc check --json carries.
This is the single definition of the machine-readable check contract: the command layer and its tests both call it, so the declared payload schema has one producer to stay in step with -- and the framework validates this document against that declaration where it writes the envelope.
exitCode is the code the run will terminate with, from [CheckResultExitCode]. The diagnostics must already be filtered through the project's suppression list.
#FilterLints
func FilterLints(diagnostics []lints.LintResult, ignoreCodes map[string]struct{}) []lints.LintResultFilterLints returns the diagnostics whose code is not suppressed.
It is the adapter over lints.FilterLints for a caller holding a code set as a map, which is what the suppression parser produces.
#PrintResults
func PrintResults(out io.Writer, result *CheckResult, color bool)PrintResults writes a check result to out in the human-readable form.
color decides whether the report carries ANSI escapes. It is a parameter rather than a decision made here, because whether the destination is a terminal is the caller's knowledge -- the Python decided it once at import time from sys.stdout, which made the report untestable and wrong for any writer that was not that stream.
#CheckResultExitCode
func CheckResultExitCode(result *CheckResult, config map[string]any) intCheckResultExitCode computes the process exit code for a whole CheckResult.
It is the adapter over lints.CheckExitCode -- the one definition of the verdict rules -- for a caller holding a full result. A reduced entry point (the post-build lint pass, the posts-only check) calls that function directly with just its diagnostics.
config is read for "coverage_threshold"; pass nil when no configuration is in play.
#CoverageBelowThreshold
func CoverageBelowThreshold(result *CheckResult, config map[string]any) boolCoverageBelowThreshold reports whether a run's documented coverage is under the project's configured threshold.
It is the adapter over lints.CoverageBelowThreshold for a caller holding a full result, which is what the command layer needs to decide whether to print the below-threshold note beside the report.
#SplitShellWords
func SplitShellWords(line string) ([]string, error)SplitShellWords splits a command line into words the way Python's shlex.split does in POSIX mode, which is what every configured example validator was written against.
The rules, all of them reachable from a real "examples" entry:
- Words are separated by runs of whitespace, which contribute nothing. - A single-quoted run is literal: nothing inside it is special, not even a backslash. - A double-quoted run keeps everything literal except a backslash before a double quote or another backslash. - Outside quotes a backslash escapes the next character, whatever it is; a backslash at the very end escapes nothing and is [ErrDanglingEscape]. - An empty quoted run still produces a word, so go vet "" is three words and the third is empty.
An unclosed quote is [ErrUnbalancedQuote]: the template says something the splitter cannot honestly read, and guessing where the word ends would run a command nobody wrote.
#CountsAsStatistic
func CountsAsStatistic(word string) boolCountsAsStatistic reports whether a prose token is a concrete numeric data point.
SEO008 measures how many quantities a page offers a citing model. A digit alone does not make a quantity: release versions and calendar years appear in almost every documentation page and say nothing about magnitude, count or proportion. Both are refused here, so a page whose only digits are "0.36.0" and "2026" reads as having no statistics -- which is the truth.
word is a whitespace-delimited token from prose content, with any Markdown decoration still attached. Genuine quantities ("42", "3.5", "87%", "12ms") answer true; a token carrying no digit, a version-shaped token and a bare year answer false.
#AcceptError.Error
func (e *AcceptError) Error() string { return e.Message }Error returns the refusal.
#jsonSyntaxError.Error
func (e *jsonSyntaxError) Error() stringError renders the refusal with the position it was raised at. The message alone is what EXAMPLE001 reports; this spelling exists for a caller that prints the error whole.
#jsonSyntaxError.Line
func (e *jsonSyntaxError) Line(document []rune) intLine is the 1-based line Pos sits on within document.
#DirectiveResult.Status
func (d DirectiveResult) Status() string { return d.Outcome }Status reports the directive's outcome, satisfying lints.DirectiveOutcome.
#ResolvedDirective.StalenessDirective
func (r ResolvedDirective) StalenessDirective() staleness.PageDirectiveStalenessDirective narrows the record to what the drift measurement reads.
#CoverageStats.TotalPublic
func (c *CoverageStats) TotalPublic() int { return c.Total }TotalPublic reports how many public symbols there are, satisfying lints.Coverage.
#CoverageStats.Documented
func (c *CoverageStats) Documented() int { return c.DocumentedCount }Documented reports how many public symbols are documented, satisfying lints.Coverage.