https://github.com/jassielof/docent
A documentation linter for Zig that enforces doc comments, doctests, and container documentation with configurable severity levels.
https://github.com/jassielof/docent
build-options build-tool cli doc lib library linter zig
Last synced: about 2 months ago
JSON representation
A documentation linter for Zig that enforces doc comments, doctests, and container documentation with configurable severity levels.
- Host: GitHub
- URL: https://github.com/jassielof/docent
- Owner: jassielof
- License: mpl-2.0
- Created: 2026-03-12T22:00:27.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-14T19:42:55.000Z (2 months ago)
- Last Synced: 2026-04-14T21:27:28.798Z (2 months ago)
- Topics: build-options, build-tool, cli, doc, lib, library, linter, zig
- Language: Zig
- Homepage: https://jassielof.github.io/docent/
- Size: 119 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# Docent: Zig Documentation Linter
Docent is a documentation linter for Zig.
Available as a CLI, library, and build integration step (#1).
## Behavior & Rules
### Scanning
It expects a manifest file to be present in your current working directory, then scans the configured `.paths` entries.
Directory behavior:
- If a directory contains `root.zig`, Docent treats it as an entrypoint and lints files that are publicly reachable from that root via `pub const ... = @import("...")` chains.
- If a directory has no `root.zig`, Docent treats each top-level `.zig` file as a module entrypoint and lints the union of their publicly reachable files.
- If no top-level `.zig` files exist, Docent falls back to linting all `.zig` files in that directory tree.
Reachability notes:
- Traversal is recursive across imported files, so multi-hop public chains are included.
- Imports reachable only through non-public declarations are excluded.
- Package imports (for example `@import("std")`) are not treated as local lint targets.
Build script defaults:
- `build.zig` and files under `build/` are ignored by default.
- Instead of files within the build directory, it's mostly all of those files that are used/imported by the main build script (`build.zig`).
- This avoids false-positive API checks from build tooling paths that are commonly present in `build.zig.zon` `.paths`.
- CLI users can opt in with `--include-build-scripts`.
- Library users can opt in via `docent.targeting.Options{ .include_build_scripts = true }`.
- Build integration users can opt in via `docent.addLintStep(..., .{ .include_build_scripts = true, ... })`.
### Severities
All rules accept one of these levels:
- `allow`: disable the rule.
- `warn`: emit a diagnostic but do not fail the run.
- `deny`: emit a diagnostic and count it as an error.
- `forbid`: same behavior as `deny` for now. Either deny or forbid will have to be gone, I fell forbid is better to stay.
### Rule: missing_doc_comment
Checks public declarations for missing `///` documentation comments.
What it checks:
- Public functions.
- Public constants and variables.
- Fields inside container declarations.
- Nested members inside container literals (for example `pub const Foo = struct { ... }`).
Re-export behavior:
- For `pub const Foo = @import("other.zig").Bar`, the rule follows the import and checks docs on `Bar` in `other.zig`.
- If `Bar` is documented, no diagnostic is emitted.
- If `Bar` is undocumented, one diagnostic is emitted and points to `other.zig`.
- If resolution fails (missing file, package import, parse failure), the re-export is skipped to avoid false positives.
Current limit:
- Re-export resolution is currently one-hop and root-declaration based. It does not perform full project/API reachability traversal.
### Rule: empty_doc_comment
Checks for doc comments that are present but blank.
What it checks:
- `///` comments with only whitespace after the prefix.
- `//!` comments with only whitespace after the prefix.
### Rule: missing_doctest
Checks public function doctest coverage.
What it checks:
- Collects top-level `pub fn name(...)` declarations.
- Collects identifier-style tests `test name { ... }`.
- Emits a diagnostic when a public function has no matching identifier-style doctest.
Notes:
- String-literal test names (for example `test "name"`) are not counted as doctests for this rule.
### Rule: private_doctest
Checks that identifier-style doctests reference public symbols.
What it checks:
- Collects top-level public function names and public variable/constant names.
- For each `test name { ... }`, emits a diagnostic if `name` is not public.
### Rule: doctest_naming_mismatch
Checks for style mismatch when a doctest name matches a public function but is written as a string literal.
What it checks:
- If `pub fn foo(...)` exists and the file uses `test "foo"`, it suggests using `test foo`.
### Rule: missing_container_doc_comment
Checks `//!` container doc comments.
What it checks:
- File-level module container doc comment (`//!`) near the beginning of the file.
- Public container declarations assigned to `pub const` (for example `pub const Config = struct { ... }`) and recursively nested public containers.
Note:
- This rule exists for compatibility with current parser behavior around top-level/container doc comments and may evolve with Zig 0.16 changes.
### Re-export resolution
When a public declaration re-exports a symbol from another file using the
`pub const Foo = @import("other.zig").Bar` pattern, the linter follows the
import and evaluates the doc comment on the _original_ declaration rather than
on the re-export line:
- If `Bar` in `other.zig` has a `///` doc comment → no diagnostic.
- If `Bar` has no doc comment → one diagnostic pointing to `other.zig`, not to
the re-export site.
- If the import path cannot be resolved (package imports such as `"std"`,
missing files, parse errors) → the re-export is silently skipped; no false positive is emitted.
Current implementation detail:
- Re-export checks are performed while linting the current file and resolving directly imported files. The linter does not currently compute full transitive API reachability for the entire package graph.
## References
### Rust
The user already has the two core Rust links. A third Clippy-specific lint is worth adding for private item coverage, since `rustc`'s `missing_docs` only covers public items: [github](https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/missing_doc.rs)
- — full list of `rustdoc` lints (`missing_docs`, `missing_doc_code_examples`, `broken_intra_doc_links`, etc.) [doc.rust-lang](https://doc.rust-lang.org/rustdoc/lints.html)
- Zig docs seem to support intra links
- — `rustc`-level `missing_docs` lint details (allowed by default, enable with `#![warn/deny(missing_docs)]`) [bsdwatch](https://bsdwatch.net/docs/sharedocs/rust/html/rustdoc/lints.html)
- — Clippy's `MISSING_DOCS_IN_PRIVATE_ITEMS` (restriction lint), which extends coverage to private items that `rustc` ignores [github](https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/missing_doc.rs)
### Go
Go has no compiler-level equivalent to `#![deny(missing_docs)]`. The canonical approach is through external linters, backed by the official doc comment spec: [go](https://go.dev/doc/comment)
- — official Go doc comment syntax specification (paragraphs, headings, links, lists, code blocks, and formatting rules enforced by `gofmt`) [go](https://go.dev/doc/comment)
- — `go/doc/comment` standard library package for parsing and reformatting doc comments programmatically [pkg.go](https://pkg.go.dev/go/doc/comment)
- — dedicated godoc lint checker with rules like `require-doc`, `start-with-name`, `deprecated`, `max-len`, `no-unused-link`, and `require-stdlib-doclink` [github](https://github.com/godoc-lint/godoc-lint)
- — `golangci-lint` integrates `godoclint` as a configurable linter runner with YAML-based rule configuration [golangci-lint](https://golangci-lint.run/docs/linters/)
## Credits
Mainly Rust/Cargo's documentation (and probably Clippy too) linter checks, while also taking inspiration from Go's linting.