Self-Documenting Code
Self-Documenting Code
Section titled “Self-Documenting Code”Learn how to write documentation that the compiler understands.
Time: 25 minutes Level: Beginner Prerequisites: Tutorial 1 (Hello World to Production) What you’ll learn: Doc comments, structured tags, doctests, documentation coverage, and query predicates
Why Documentation in Janus Is Different
Section titled “Why Documentation in Janus Is Different”Most languages treat documentation as text blobs stapled to functions. Grep for a pattern, hope the comments are up to date, move on.
Janus treats documentation as structured data in the ASTDB — the same semantic database the compiler uses for type checking and code generation. Your doc comments are parsed, tagged, and stored as columnar rows. They survive renames. They are queryable. They are enforceable in CI.
“Documentation is not decoration. It is structured evidence.”
Step 1: Your First Doc Comment (5 min)
Section titled “Step 1: Your First Doc Comment (5 min)”The /// syntax
Section titled “The /// syntax”Documentation comments use triple slashes. Place them immediately before a declaration:
/// Add two numbers and return the result.func add(a: i64, b: i64) -> i64 do return a + bendThe first sentence becomes the summary — the one-liner that appears in module overviews and search results. Everything after the first blank /// line becomes the description.
Multi-line descriptions
Section titled “Multi-line descriptions”/// Calculate the factorial of n.////// Uses iterative multiplication to avoid stack overflow/// on large inputs. Returns 1 for n <= 1.func factorial(n: i64) -> i64 do var result = 1 var i = 2 while i <= n do result = result * i i = i + 1 end return resultendSee it in action
Section titled “See it in action”Save the file as math.jan and run:
janus doc math.janOutput:
## Functions
### `factorial(n: i64) -> i64`
Calculate the factorial of n.
Uses iterative multiplication to avoid stack overflowon large inputs. Returns 1 for n <= 1.
---
### `add(a: i64, b: i64) -> i64`
Add two numbers and return the result.What you learned:
///creates a doc comment- The first sentence is the summary
janus docgenerates Markdown from your source
Step 2: Structured Tags (5 min)
Section titled “Step 2: Structured Tags (5 min)”The essential three: @param, @returns, @error
Section titled “The essential three: @param, @returns, @error”Raw prose is good. Structured tags are better. They tell the compiler (and your teammates) exactly what each parameter does, what comes back, and what can go wrong.
/// Opens a file at the given path and returns a handle.////// This function validates the path and checks permissions/// before attempting to open the file.////// @param path The filesystem path to open/// @param mode Read or write mode/// @returns File handle on success/// @error FsError.NotFound Path does not exist/// @error FsError.PermissionDenied Insufficient permissions/// @capability CapFsRead Required for read mode/// @since 0.3.0/// @complexity O(1) amortized////// ```janus/// let f = open("data.txt", "r")/// defer f.close()/// ```func open(path: str, mode: str) !File do println("opening file")endThe full tag reference
Section titled “The full tag reference”| Tag | Syntax | Purpose |
|---|---|---|
@param | @param name Description | Document a parameter |
@returns | @returns Description | Document the return value |
@error | @error ErrorType Description | Document an error variant |
@capability | @capability CapName Description | Required capability token |
@since | @since version | Version the item was introduced |
@deprecated | @deprecated Reason | Mark as deprecated with guidance |
@safety | @safety Explanation | Safety invariants the caller must uphold |
@complexity | @complexity O(...) | Algorithmic complexity |
@see | @see identifier | Cross-reference another declaration |
Structured output
Section titled “Structured output”Run janus doc on the file above:
janus doc file_ops.janOutput:
### `open(path: str, mode: str) !File`
Opens a file at the given path and returns a handle.
This function validates the path and checks permissionsbefore attempting to open the file.
**Parameters:**
| Name | Description ||------|-------------|| `path` | The filesystem path to open || `mode` | Read or write mode |
**Returns:** File handle on success
**Errors:**
| Error | Description ||-------|-------------|| `FsError.NotFound` | Path does not exist || `FsError.PermissionDenied` | Insufficient permissions |
**Capabilities:** `CapFsRead` -- Required for read mode
**Since:** 0.3.0
**Complexity:** O(1) amortizedTags are not freeform text. They are parsed into structured fields that tooling can consume, validate, and query.
What you learned:
@param,@returns,@errordocument the function contract- Tags like
@capability,@since,@deprecatedadd metadata janus docrenders structured sections (Parameters table, Errors table, etc.)
Step 3: Embedded Examples (5 min)
Section titled “Step 3: Embedded Examples (5 min)”Fenced code blocks become doctests
Section titled “Fenced code blocks become doctests”Code examples inside doc comments are not just for humans. They are first-class AST nodes that the compiler parses, type-checks, and can execute.
/// Format a greeting message.////// ```janus/// let msg = greet("Markus")/// assert(msg == "Hello, Markus!")/// ```func greet(name: str) -> str do return "Hello, " ++ name ++ "!"endThe fenced ```janus block inside the doc comment is extracted during the doc extraction pass and treated as a doctest. Unlike regex-extracted doctests in other languages, these are parsed into AST nodes and participate in type checking.
Adjacent test blocks
Section titled “Adjacent test blocks”Test blocks placed immediately after a function are also linked as that function’s doctest:
/// Clamp a value to a range.func clamp(val: i64, lo: i64, hi: i64) -> i64 do if val < lo do return lo end if val > hi do return hi end return valend
test "clamp basics" do assert(clamp(5, 0, 10) == 5) assert(clamp(-1, 0, 10) == 0) assert(clamp(99, 0, 10) == 10)endThe compiler sees the test block as a child node of clamp in the ASTDB. Renaming clamp updates the link automatically via CID resolution.
Running doctests
Section titled “Running doctests”janus test --doc # Run all doctestsjanus test --doc --check # Verify they compile without executingWhat you learned:
- Fenced
```janusblocks inside doc comments become doctests - Adjacent
testblocks are linked to the preceding function janus test --docexecutes doctests
Step 4: Documentation Coverage (5 min)
Section titled “Step 4: Documentation Coverage (5 min)”The doc linter
Section titled “The doc linter”Writing documentation is half the job. Enforcing it is the other half. Janus ships a built-in documentation linter:
janus doc math.jan --checkExample output:
Documentation Coverage Report=============================File: math.janCoverage: 75.0% (3/4 declarations documented)
Issues: [MISSING_DOC] func helper() at line 47 -- no doc comment [MISSING_PARAM] func factorial(n: i64) at line 39 -- missing @param for 'n' [MISSING_RETURNS] func add(a: i64, b: i64) at line 5 -- missing @returns tag
Summary: 3 issues foundWhat it checks
Section titled “What it checks”| Check | Description |
|---|---|
| Missing docs | Public declarations without /// comments |
| Missing @param | Parameters not documented |
| Missing @returns | Return values not documented |
| Deprecated without reason | @deprecated tags missing migration guidance |
| Stale @see | Cross-references pointing to renamed or removed items |
Gate it in CI
Section titled “Gate it in CI”The --check flag returns exit code 1 if any issues are found. Add it to your CI pipeline:
# In your CI scriptjanus doc src/ --check || exit 1This enforces documentation quality the same way you enforce test coverage — automatically, on every commit.
What you learned:
janus doc --checkreports documentation coverage- It catches missing docs, missing tags, and stale references
- Gate it in CI to enforce documentation standards
Step 5: Query Predicates — The Superpower (5 min)
Section titled “Step 5: Query Predicates — The Superpower (5 min)”Documentation as a queryable database
Section titled “Documentation as a queryable database”Here is where Janus documentation diverges from every other language. Because doc comments are stored as structured ASTDB rows, you can query them with predicates.
janus query --doc "<predicate>" <file_or_directory>Available atoms
Section titled “Available atoms”| Atom | Matches |
|---|---|
func | Function declarations |
struct | Struct declarations |
enum | Enum declarations |
const | Constant declarations |
trait | Trait declarations |
has_doc | Declarations with doc comments |
is_deprecated | Items marked @deprecated |
has_doctest | Items with embedded code examples |
has_param_doc | Functions with @param tags |
has_return_doc | Functions with @returns tags |
has_error_doc | Functions with @error tags |
Combinators
Section titled “Combinators”Combine atoms with and, or, not, and parentheses:
# Find undocumented functionsjanus query --doc "func and not has_doc" src/
# Find deprecated items anywhere in the projectjanus query --doc "is_deprecated" src/
# Find functions missing parameter docsjanus query --doc "func and has_doc and not has_param_doc" src/
# Find documented functions that include examplesjanus query --doc "func and has_doctest" src/
# Find undocumented structs or enumsjanus query --doc "(struct or enum) and not has_doc" src/Practical example
Section titled “Practical example”Suppose you are preparing a release and need to find every deprecated function in your codebase:
janus query --doc "is_deprecated" src/Output:
src/math.jan:39 func factorial(n: i64) !i64 @deprecated Use math.gamma() instead for better precision
Found 1 deprecated item(s).This is documentation as data. You do not grep for missing docs — you query a semantic database.
What you learned:
janus query --docsearches declarations by documentation properties- Atoms like
has_doc,is_deprecated,has_doctestfilter by metadata - Combinators (
and,or,not) compose into precise queries - This replaces ad-hoc grep scripts with structured semantic queries
Step 6: Why This Matters
Section titled “Step 6: Why This Matters”Documentation is structured data
Section titled “Documentation is structured data”In most languages, documentation is a string blob attached to an AST node. You can render it, but you cannot reason about it programmatically. If a function is renamed, the docs might reference the old name. If a parameter is added, nothing reminds you to document it.
Janus takes a different approach:
- ASTDB-native — Doc comments are columnar rows in the same database the compiler uses. They are not an afterthought bolted onto the AST.
- CID-linked — Each doc entry is bound to its declaration by content ID. Docs survive renames, file moves, and refactors because the link is semantic, not textual.
- Queryable — Tooling and CI can enforce documentation standards through predicates, not regex.
- Machine-readable — The UTCP output format makes docs consumable by AI agents and language servers without custom parsing.
Compared to other approaches
Section titled “Compared to other approaches”Elixir’s ExDoc built an excellent documentation culture. But ExDoc treats docs as string blobs on module attributes — rich for humans, opaque for machines. Rustdoc’s missing_docs lint catches undocumented items, but cannot query for “functions with examples” or “deprecated items missing migration guidance.”
Janus combines the cultural emphasis on documentation with architectural enforcement. The compiler does not just store your docs — it understands them.
What You Accomplished
Section titled “What You Accomplished”- Wrote doc comments with
///and multi-line descriptions - Used structured tags (
@param,@returns,@error,@capability,@since,@deprecated) - Created embedded doctests with fenced code blocks
- Linked adjacent
testblocks to functions - Ran
janus docto generate structured Markdown output - Used
janus doc --checkto lint documentation coverage - Queried the ASTDB with
janus query --docto find undocumented or deprecated items
Next Steps
Section titled “Next Steps”- Read the janus doc reference for the full tag reference, output formats (HTML, JSON, UTCP), and symbolic markers
- Explore
janus doc --format=htmlfor browsable documentation sites - Add
janus doc --checkto your CI pipeline
Your documentation is now as honest as your code.
“Syntactic Honesty applies to prose as much as to programs. If the docs can lie, the system is already compromised.”