Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Code Style and Linting

Cadmus enforces a consistent code style across all languages using treefmt for formatting and several linters for static analysis. The pre-commit hooks run all checks automatically, and CI enforces the same rules on every pull request.

Formatters and Linters

ToolLanguages / FilesKey Configuration
rustfmtRust (*.rs)Workspace rustfmt.toml
prettierJSON, YAML, Markdown, CSS, JS.prettierrc.json
shfmtShell (*.sh, *.bash)-i 2 -ci (2-space, case-indent)
shellcheckShell (*.sh, *.bash).editorconfig
yamllintYAMLextends: default, several rules disabled
rumdlMarkdown (*.md)Default rules
actionlintGitHub Actions workflows-ignore "rust-toolchain"
clippyRust-D warnings

Running treefmt

All formatters run through treefmt. Inside the devenv shell:

# Format all files tracked by treefmt
treefmt

# Check without writing (dry run)
treefmt --fail-on-change

The pre-commit hook (git-hooks.hooks.treefmt) runs treefmt --fail-on-change automatically on every commit, so format issues are caught before they reach CI.

Rust Style

Formatting

rustfmt with the workspace configuration handles formatting automatically. Run it via treefmt or directly:

cargo fmt

Linting (Clippy)

Clippy runs with -D warnings — all warnings are errors:

cargo xtask clippy

Clippy runs across every feature flag combination in CI. When adding a new feature flag, update .github/workflows/cargo.yml to include the new matrix entries.

Key Conventions

  • Prefer ? over unwrap() / expect() in library and app code.
  • Use iterators over index-based loops.
  • Use &str over String in function parameters when ownership is not needed.
  • Prefer borrowing over cloning.
  • Avoid premature collect() — keep iterators lazy.
  • Use newtype wrappers over raw primitives for domain concepts.

Shell Style

Shell scripts are formatted with shfmt (-i 2 -ci) and checked with shellcheck. The -ci flag indents case statement arms relative to the case keyword:

# Correct — case arms indented with -ci
case "${VAR}" in
  pattern)
    do_something
    ;;
  *)
    fallback
    ;;
esac

Scripts must declare their shell variant. For bash scripts, use:

#! /bin/bash

Structured Logging

Use the tracing crate with structured fields — never string formatting for log data:

#![allow(unused)]
fn main() {
// Correct
tracing::debug!(pr_number, count, "Found artifacts");

// Wrong
tracing::debug!("[OTA] Found {} artifacts for PR #{}", count, pr_number);
}

See Logging for log level guidance.

Comments

Comment why, not what. Most code needs no comments — good naming is preferred. The rules:

  • No inline comments — if one feels necessary, extract the code into a well-named function instead.
  • No commented-out code.
  • No changelog comments (Modified by X on date).
  • No decorative dividers (//=====).
  • TODO, FIXME, HACK, NOTE annotations are fine with context.

Public API items must have doc comments.

CI Checks

The following CI workflows enforce style:

WorkflowWhat it checks
cargo.ymlrustfmt, clippy (full feature matrix), tests
shell.ymlshellcheck, shfmt (changed lines only)
docs-lint.ymlprettier, markdownlint (docs and markdown files)

CI uses filter_mode: added for shell checks, meaning only lines changed in the PR are flagged. Running treefmt locally before pushing will catch everything.