This file provides guidance to AI coding assistants working with this codebase.
djot-fmt is a command-line tool for automatically formatting djot markup files. The primary focus is on correcting common list formatting issues:
- Missing newlines between list items
- Incorrect indentation for nested lists
- Proper blank line spacing before nested content
├── main.go # CLI entry point
├── internal/
│ ├── formatter/ # Core formatting logic
│ │ ├── formatter.go # Node conversion functions
│ │ ├── writer.go # Output writer with state tracking
│ │ └── formatter_test.go
│ └── iohelper/ # File I/O and CLI argument handling
│ ├── args.go # Command-line argument parsing
│ ├── process.go # File reading/writing logic
│ └── args_test.go
└── testdata/ # Test fixtures
Conversion System - Uses godjot's generic conversion pattern:
- Registry maps
DjotNodetypes to conversion functions - Each conversion function receives state and a callback for processing children
- Writer tracks formatting state (indentation, block types, context)
State Tracking - The Writer tracks:
- Current indentation level
- Last block type (for spacing decisions)
- Whether currently inside a list item (for nested list handling)
- Add new node type support: Update
defaultRegistryinformatter.gowith a new conversion function - Modify spacing: Adjust
NeedsBlankLine()logic or block type tracking inwriter.go - Handle edge cases: Add test cases in
formatter_test.gobefore implementing
# Run all tests
go test ./...
# Test specific package
go test ./internal/formatter -v
# Test with coverage
go test -cover ./...Follow Go best practices from GO_BEST_PRACTICES.md:
- Table-driven tests
- Small, single-responsibility functions
- Explicit error handling with context wrapping
- No dot imports
The project enforces strict linting rules. Ensure code passes all linters before committing:
revive:
- Never use built-in function names as parameter names (
new,make,len, etc.) - Use
switchstatements instead of if-else chains with 3+ branches
gocritic:
- Convert if-else chains to switch statements when appropriate
- Prefer switch for cleaner, more maintainable branching logic
testifylint:
- Use
require.Error(t, err)andrequire.NoError(t, err)for error assertions in tests - Never use
assert.Errororassert.NoError- tests should stop on error assertion failures assert.*is acceptable for non-error value comparisons
cyclop:
- Maximum cyclomatic complexity is 10 per function
- Extract helper functions to reduce complexity when needed
- Break down complex conditionals and loops into smaller functions
gosec:
- File permissions for
os.WriteFilemust be 0600 or less (not 0644) - Follow security best practices for file operations
testpackage:
- Test files should use
package <name>_testinstead ofpackage <name> - This enforces testing the public API and prevents accessing private internals
- Import the package explicitly (e.g.,
import "github.com/KyleKing/djot-fmt/internal/formatter") - Note: Module path uses capital K in
KyleKing(check go.mod for correct capitalization)
-
Add conversion function in
formatter.go:func formatNewNode(state ConversionState[*Writer], next func(Children)) { // Handle node formatting next(nil) // Process children }
-
Register in
defaultRegistry:NewNodeType: formatNewNode,
-
Add test case in
formatter_test.go
Use the AST inspection pattern:
ast := djot_parser.BuildDjotAst(input)
// Print AST structure to understand node hierarchyThe project uses a replace directive in go.mod to use the local godjot:
replace github.com/sivukhin/godjot/v2 => ../godjot
Build with:
GOWORK=off go build -o djot-fmt