Skip to content

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 format and pnpm 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

bash
# 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:fix

dprint Configuration

Key settings from dprint.json:

SettingValueEffect
lineWidth157Maximum line length before wrapping
indentWidth2Two-space indentation
semiColons"asi"No semicolons (automatic semicolon insertion)
quoteStyle"preferSingle"Single quotes for strings
trailingCommas"never"No trailing commas
objectExpression.preferSingleLinetrueObjects on one line when possible
arrayExpression.preferSingleLinefalseArrays 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/ and packages/*/test/
  • Template files in templates/
  • JSON files and root .mjs files

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

RuleLevelPurpose
@typescript-eslint/no-explicit-anywarnDiscourage any usage
@typescript-eslint/no-unused-varserrorCatch unused variables (allows _ prefix)
jsdoc/require-jsdocwarnRequire JSDoc on exported functions
jsdoc/no-typeserrorForbid types in JSDoc (TypeScript provides them)
jsdoc/require-hyphen-before-param-descriptionwarnEnforce @param name - desc format
tsdoc/syntaxwarnValidate TSDoc syntax
drizzle/enforce-delete-with-whereerrorPrevent accidental bulk deletes
drizzle/enforce-update-with-whereerrorPrevent accidental bulk updates

Local Rules (Project-Specific)

RuleLevelPurpose
local-rules/env-validationerrorEnforce getRequiredEnv() over process.env
local-rules/strict-env-varserrorStrict env var access patterns
local-rules/enforce-powertoolserrorRequire Powertools observability wrappers
local-rules/import-orderwarnEnforce import ordering convention
local-rules/spacing-conventionswarnFunction spacing rules
local-rules/response-helperswarnUse buildResponse() helper
local-rules/cascade-delete-orderwarnCorrect cascade delete ordering
local-rules/migrations-safetyerrorSafe migration patterns

Test File Relaxation

ESLint relaxes certain rules for test files (**/test/**/*.ts, **/*.test.ts):

  • jsdoc/require-jsdoc is turned off
  • @typescript-eslint/no-unused-vars becomes 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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

  1. Consistency over preference - Automated formatting eliminates style debates
  2. Two-tool separation - dprint for formatting, ESLint for quality (no overlap)
  3. Targeted overrides - Only intervene when readability suffers
  4. Fast formatting - dprint is significantly faster than Prettier
  5. Searchable hints - // fmt: multiline and // dprint-ignore are easy to grep

Trust dprint for formatting and ESLint for code quality. Use // fmt: multiline sparingly for arrays/objects where automatic formatting creates inconsistent visual structure.