index.md

July 19, 2026 · View on GitHub

Build and test reference for mdsmith contributors. See also:

Build & Test Commands

Requires Go 1.25+. Dev tools (golangci-lint and gobco) build from tools/go.mod, which needs Go 1.25.8+; go.mod itself stays tool-free so go install consumers never inherit a dev tool's go floor.

  • go build ./... — build all packages
  • go test ./... — run all tests
  • go test -run TestName ./... — run a specific test
  • go run ./cmd/mdsmith check . — lint markdown
  • go run ./cmd/mdsmith fix . — auto-fix markdown
  • go tool -modfile=tools/go.mod golangci-lint run — run linter
  • go vet ./... — run go vet

Project Layout

Follows the standard Go project layout:

  • cmd/mdsmith/ — main entry point.
  • internal/ — private packages.
  • internal/rules/<rule-name>/ — rule code (e.g. paragraphstructure/).
  • internal/rules/MDS###-<rule-name>/ — rule README and good/bad fixtures (e.g. MDS024-paragraph-structure/).
  • testdata/ — shared markdown fixtures.
  • pkg/goldmark/ — vendored goldmark fork.

Code Style

  • Follow standard Go conventions (gofmt, goimports).
  • Use golangci-lint for linting.
  • Keep functions small and focused.
  • Error messages: lowercase, no trailing punctuation.
  • Prefer returning errors over panicking.

Defensive Code

Add a defensive branch only when you can drive it red/green. Write the failing test first. Then add the code that takes the branch.

Allocation Budget

A rule's Check allocates ≤ 10 times per call on representative input. Enforced by internal/integration/alloc_budget_test.go; most rules allocate 0–6.

  • Walk f.Lines / f.AST directly.
  • Prefer bytes.IndexByte / bytes.Contains over regexp for fixed searches.
  • Compile every regexp.Regexp at package scope.
  • Pre-size slices with make([]X, 0, n).
  • Reuse loop-local buffers via buf = buf[:0].
  • Return nil, not an empty slice, on no diagnostics.

Test Fixtures

Rule test fixtures live in internal/rules/MDS###-<rule-name>/ (e.g. MDS024-paragraph-structure/). Each rule has good/ and bad/ examples (or good.md / bad.md).

Good fixtures must pass all default-enabled rules plus the rule under test. Opt-in rules are skipped: a good MDS001 fixture need not also satisfy MDS043. When a good fixture uses non-default settings, override them in .mdsmith.yml so mdsmith check . also passes. Bad fixtures are excluded via the ignore: section.

When adding or changing a rule, add both:

  1. Unit tests in rule_test.go (inline markdown, fast red/green). Use require for preconditions and assert for checks; Same/NotSame for pointer identity.
  2. Fixture tests under internal/rules/MDS###-<rule-name>/ with YAML frontmatter specifying expected diagnostics. Discovered automatically by internal/integration/rules_test.go.

Config Merge Semantics

Layered config (defaults → kinds → overrides) is deep-merged rule by rule:

  • Maps merge key by key; siblings set in earlier layers survive partial overrides.
  • Scalar leaves are replaced wholesale.
  • List settings replace by default. Opt into append by implementing rule.ListMerger.SettingMergeMode(key). The placeholder vocabulary is the canonical example.
  • A bool-only layer (rule-name: false) toggles enabled without erasing inherited settings.

New list-typed settings must document the choice next to their ApplySettings handler.

Generated Sections

Content between <?directive ... ?> and <?/directive?> markers is auto-generated. Edit directive parameters or the source file, then run mdsmith fix <file> — never the body by hand. Run mdsmith merge-driver install [files...] once per clone so generated-section conflicts resolve automatically.