Code Formatting
Quick Reference
- When to use: Understanding and working with automatic code formatting and linting
- Formatter: dprint (TypeScript plugin)
- Linter: ESLint (flat config)
- Config files:
dprint.json,eslint.config.mjs - Enforcement: Automatic -
pnpm run formatandpnpm run lint
The Rule
Let dprint handle formatting and ESLint handle code quality, with targeted overrides when readability suffers.
This project uses a two-tool approach:
- dprint - Fast, opinionated code formatter (replaces Prettier)
- ESLint - Code quality linting with project-specific rules (no formatting rules)
ESLint formatting rules are intentionally disabled. The comment in eslint.config.mjs states: "Formatting rules (quotes, semi, comma-dangle, max-len) removed. dprint handles all formatting via dprint.json."
Running the Tools
# Format all files
pnpm run format
# Check formatting without changing (CI)
pnpm run format:check
# Format a specific file
npx dprint fmt path/to/file.ts
# Lint all files
pnpm run lint
# Lint and auto-fix
pnpm run lint:fixdprint Configuration
Key settings from dprint.json:
| Setting | Value | Effect |
|---|---|---|
lineWidth | 157 | Maximum line length before wrapping |
indentWidth | 2 | Two-space indentation |
semiColons | "asi" | No semicolons (automatic semicolon insertion) |
quoteStyle | "preferSingle" | Single quotes for strings |
trailingCommas | "never" | No trailing commas |
objectExpression.preferSingleLine | true | Objects on one line when possible |
arrayExpression.preferSingleLine | false | Arrays expand to multiline |
arguments.preferHanging | "always" | First argument on same line as function |
arrowFunction.useParentheses | "force" | Always use parens in arrow functions |
What dprint Formats
- All TypeScript/JavaScript files in
packages/*/src/andpackages/*/test/ - Template files in
templates/ - JSON files and root
.mjsfiles
What dprint Ignores
node_modules,dist,build,coverage- VitePress build output (
docs/.vitepress/)
ESLint Configuration
ESLint handles code quality rules that dprint does not cover.
Key Rules
| Rule | Level | Purpose |
|---|---|---|
@typescript-eslint/no-explicit-any | warn | Discourage any usage |
@typescript-eslint/no-unused-vars | error | Catch unused variables (allows _ prefix) |
jsdoc/require-jsdoc | warn | Require JSDoc on exported functions |
jsdoc/no-types | error | Forbid types in JSDoc (TypeScript provides them) |
jsdoc/require-hyphen-before-param-description | warn | Enforce @param name - desc format |
tsdoc/syntax | warn | Validate TSDoc syntax |
drizzle/enforce-delete-with-where | error | Prevent accidental bulk deletes |
drizzle/enforce-update-with-where | error | Prevent accidental bulk updates |
Local Rules (Project-Specific)
| Rule | Level | Purpose |
|---|---|---|
local-rules/env-validation | error | Enforce getRequiredEnv() over process.env |
local-rules/strict-env-vars | error | Strict env var access patterns |
local-rules/enforce-powertools | error | Require Powertools observability wrappers |
local-rules/import-order | warn | Enforce import ordering convention |
local-rules/spacing-conventions | warn | Function spacing rules |
local-rules/response-helpers | warn | Use buildResponse() helper |
local-rules/cascade-delete-order | warn | Correct cascade delete ordering |
local-rules/migrations-safety | error | Safe migration patterns |
Test File Relaxation
ESLint relaxes certain rules for test files (**/test/**/*.ts, **/*.test.ts):
jsdoc/require-jsdocis turned off@typescript-eslint/no-unused-varsbecomes a warning instead of error
Multiline Formatting Hint
The Problem
When dprint wraps arrays/objects that exceed line width, it can create mixed inline/multiline formatting:
// dprint's default - ugly mixed wrapping
const items = [{id: 1, name: 'first'}, {id: 2, name: 'second'}, {
id: 3,
name: 'third'
}]The Solution: // fmt: multiline
Add // fmt: multiline after the first element to force consistent multiline formatting:
// Clean, consistent multiline
const items = [
{id: 1, name: 'first'}, // fmt: multiline
{id: 2, name: 'second'},
{id: 3, name: 'third'}
]When to Use
- An array/object wraps with some elements inline and others multiline
- Elements are similar and should be visually aligned
- Test fixtures with consistent structure
When NOT to Use
- The expression fits on one line (let dprint keep it compact)
- The multiline format is already clean
Complete Formatting Bypass
// dprint-ignore
Skip formatting for a single statement:
// dprint-ignore
const matrix = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
];// dprint-ignore-file
Skip formatting for an entire file (use sparingly).
When to Use Ignore
- Matrix/grid data - Visual alignment matters
- Complex regex - Manual formatting aids readability
- Generated code - Preserve generator's formatting
Function Call Formatting
With arguments.preferHanging: "always", when function arguments must wrap, the first argument stays on the same line:
// First argument on same line as function name (good context)
vi.mock('@mantleframework/env',
() => ({getRequiredEnv: vi.fn(), getOptionalEnv: vi.fn()}))
// NOT like this (loses context)
vi.mock(
'@mantleframework/env',
() => ({getRequiredEnv: vi.fn(), getOptionalEnv: vi.fn()})
)Type Aliases for Line Width
When function signatures exceed 157 characters, extract type aliases:
// Before: 175 characters, wraps awkwardly
export async function getResourceDetails(id: string): Promise<{userId: string; fileId: string; metadata: Record<string, unknown>}> {
// After: Clean single line
type ResourceDetails = {userId: string; fileId: string; metadata: Record<string, unknown>}
export async function getResourceDetails(id: string): Promise<ResourceDetails> {Rationale
- Consistency over preference - Automated formatting eliminates style debates
- Two-tool separation - dprint for formatting, ESLint for quality (no overlap)
- Targeted overrides - Only intervene when readability suffers
- Fast formatting - dprint is significantly faster than Prettier
- Searchable hints -
// fmt: multilineand// dprint-ignoreare easy to grep
Related Patterns
- Code Comments - When and how to use comments
- Naming Conventions - Variable and file naming
- Import Organization - Import ordering (enforced by both dprint and ESLint)
Trust dprint for formatting and ESLint for code quality. Use // fmt: multiline sparingly for arrays/objects where automatic formatting creates inconsistent visual structure.