check

package
v0.48.1 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: MIT Imports: 43 Imported by: 0

Documentation

Overview

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 (the post lints) 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.

Index

Constants

View Source
const StatusFailed = "FAILED"

StatusFailed is the Outcome of a directive that did not resolve.

View Source
const StatusOK = "OK"

StatusOK is the Outcome of a directive that resolved.

Variables

View Source
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.

View Source
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.

View Source
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.

Functions

func CheckJSONSyntax

func CheckJSONSyntax(text string) *jsonSyntaxError

CheckJSONSyntax reports whether text is a JSON document CPython would accept, and answers with CPython's own message and line when it is not.

func CheckPosts

func CheckPosts(
	projectConfig map[string]any, dirPath string, handle *effects.Handle,
) ([]lints.LintResult, error)

CheckPosts checks a project's blog posts for validation errors (the post lints), 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 posts directory, 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.

func CheckResultExitCode

func CheckResultExitCode(result *CheckResult, config map[string]any) int

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

func CountsAsStatistic

func CountsAsStatistic(word string) bool

CountsAsStatistic reports whether a prose token is a concrete numeric data point.

low-numeric-data-density 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.

func CoverageBelowThreshold

func CoverageBelowThreshold(result *CheckResult, config map[string]any) bool

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

func LintPostBuffer

func LintPostBuffer(
	dirPath, sourcePath, content string,
	projectConfig map[string]any,
	handle *effects.Handle,
) ([]lints.LintResult, error)

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, broken-page-link, 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.

func PostErrorLint

func PostErrorLint(err *posts.PostError, postsDirRelative string) lints.LintResult

PostErrorLint turns one post refusal into its lint diagnostic.

The mapping from a refusal to its post lint 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 lint name.

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 lint name comes off the error too for every refusal the frontmatter schema decided, which is where a post's required fields and its date's spelling are declared. The refusals this repository decides on its own -- the slug rules and the directive-marker scan -- carry no kind of their own and are still matched by message.

func PrintResults

func PrintResults(out io.Writer, result *CheckResult)

PrintResults writes a check result to out in the human-readable form. It is plain text: the report goes through strictcli's ctx.Out, whose destination may be the --json envelope's output member, so it carries no ANSI escapes.

func RecordBaselines added in v0.47.0

func RecordBaselines(
	dirPath string, projectConfig map[string]any, handle *effects.Handle,
) error

RecordBaselines writes the staleness and drift baseline `selfdoc check` compares against: every page's content, description, source-docstring and schema hashes, measured as CheckDocs measures them, advanced for every page not stale or drifted and held for every page that is.

`selfdoc check` is a read-only verdict and never writes the store, so the commands that generate content record the baseline through this, which leaves the store reporting what the next check reports. The caller commits the store.

projectConfig may be nil, in which case it is loaded from selfdoc.json.

func SerializeCheckResult

func SerializeCheckResult(result *CheckResult, exitCode int) map[string]any

SerializeCheckResult 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 carry the repository's lint options (lints.Settings.Apply).

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

func ThemeCSS

func ThemeCSS(themeName string) (string, bool)

ThemeCSS returns the stylesheet insufficient-theme-color-contrast 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.

Types

type AcceptError

type AcceptError struct {
	// Message is the whole refusal, already naming every offending page
	// and why it was refused.
	Message string
}

AcceptError reports that `selfdoc baseline accept` cannot accept a named page.

func (*AcceptError) Error

func (e *AcceptError) Error() string

Error returns the refusal.

type AcceptedBaseline

type AcceptedBaseline struct {
	// Page is the page identifier, exactly as `selfdoc check` shows it.
	Page string
	// Lint is the name of the lint whose error was cleared:
	// "stale-page-description" or "description-drifted-from-source".
	Lint string
}

AcceptedBaseline is one page whose baseline was advanced, with the error the advance cleared.

func AcceptBaselines

func AcceptBaselines(
	pages []string, dirPath string, projectConfig map[string]any, handle *effects.Handle,
) ([]AcceptedBaseline, error)

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 stale-page-description/description-drifted-from-source 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.

type CheckResult

type CheckResult struct {
	// DirectiveResults is one entry per directive the run validated, in
	// page order.
	DirectiveResults []DirectiveResult
	// Coverage is the symbol coverage measurement, nil for a project that
	// declares no source.
	Coverage *CoverageStats
	// Lints are the diagnostics the run produced, in rule order.
	Lints []lints.LintResult
}

CheckResult is the whole verdict of one CheckDocs run.

func CheckDocs

func CheckDocs(
	dirPath string,
	projectConfig map[string]any,
	dryRun bool,
	versionFilter string,
	versionOverride string,
	handle *effects.Handle,
) (*CheckResult, error)

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 writes nothing: staleness is reported without writing the hash store, and an archived version is read from a temporary extraction removed before the call returns instead of from selfdoc's version cache. `selfdoc check` passes true, since it is a read-only verdict; the build's lint pass passes false, writing the store and filling the cache. versionFilter, when non-empty, skips the multi-version validation pass (version-tag-not-extractable), 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 (version-mismatch-in-generated-root-file), 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`.

type CoverageStats

type CoverageStats struct {
	// Total is how many public symbols the project's sources export.
	Total int
	// ReferencedCount is how many of them any directive names.
	ReferencedCount int
	// DocumentedCount is how many of them a non-skeleton page names.
	DocumentedCount int
	// ReferencedSymbols are the "<file>:<symbol>" identifiers any
	// directive names.
	ReferencedSymbols []string
	// DocumentedSymbols are the identifiers a non-skeleton page names.
	DocumentedSymbols []string
	// UnreferencedSymbols are the identifiers no directive names.
	UnreferencedSymbols []string
	// SkeletonPagesBySymbol maps a referenced identifier to the generated,
	// still-seeded page that named it. It is what lets the report say
	// WHICH page's description is the reason a symbol counts as referenced
	// and not documented, because that is a property of the page and not
	// of the symbol's own documentation.
	SkeletonPagesBySymbol map[string]string
}

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

func (*CoverageStats) Documented

func (c *CoverageStats) Documented() int

Documented reports how many public symbols are documented, satisfying lints.Coverage.

func (*CoverageStats) TotalPublic

func (c *CoverageStats) TotalPublic() int

TotalPublic reports how many public symbols there are, satisfying lints.Coverage.

type DirectiveResult

type DirectiveResult struct {
	// File is the directive's page, relative to the docs directory, with
	// the version or project prefix a multi-version or unified run adds.
	File string
	// Line is the directive's 1-based line in its source file,
	// frontmatter included.
	Line int
	// Directive is the directive as the report shows it: its name
	// followed by its attributes.
	Directive string
	// Outcome is "OK" or "FAILED". [DirectiveResult.Status] reports it,
	// which is what lints.DirectiveOutcome asks of this type -- a field of
	// that name and a method of that name cannot coexist.
	Outcome string
	// Error is the resolution failure's message, empty when the directive
	// resolved.
	Error string
}

DirectiveResult is the result of validating a single directive.

func (DirectiveResult) Status

func (d DirectiveResult) Status() string

Status reports the directive's outcome, satisfying lints.DirectiveOutcome.

type ResolvedDirective

type ResolvedDirective struct {
	// Name is the directive name -- ref, table-schema, code-test and the
	// rest.
	Name string
	// Attrs are the directive's attributes.
	Attrs map[string]string
	// Content is the Markdown the directive resolved to.
	Content string
	// File is the page the directive sits on, relative to the docs
	// directory.
	File string
	// SourceEntry is the declared source path that answered the directive,
	// nil when none did.
	SourceEntry *extractors.SourceEntry
}

ResolvedDirective is a successfully resolved directive with its output.

func (ResolvedDirective) StalenessDirective

func (r ResolvedDirective) StalenessDirective() staleness.PageDirective

StalenessDirective narrows the record to what the drift measurement reads.

type StalenessState

type StalenessState struct {
	// Current maps each page identifier -- locale-prefixed when locales
	// are configured, matching the hash store's keys -- to its full
	// current hash entry.
	Current staleness.Store
	// Stored is the loaded baseline, the hash store's own contents.
	Stored staleness.Store
	// ErrorPages maps a page identifier to the name of the lint of its
	// outstanding error: "stale-page-description" or "description-drifted-from-source".
	ErrorPages map[string]string
}

StalenessState is what ComputeStalenessState measured.

func ComputeStalenessState

func ComputeStalenessState(
	dirPath string, projectConfig map[string]any, handle *effects.Handle,
) (StalenessState, error)

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 stale-page-description/description-drifted-from-source detection, but never writes the hash store.

projectConfig may be nil, in which case it is loaded from selfdoc.json.

Jump to

Keyboard shortcuts

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