Skip to content

Formatting

Pickier’s formatter focuses on speed, stability, and predictable diffs. It does not attempt full AST reprinting; instead it applies a set of targeted, deterministic transforms that cover the 80% of day-to-day cleanup needs.

What the formatter does:

  • Whitespace normalization
    • trims trailing spaces on each line
    • collapses consecutive blank lines to a configured maximum
    • enforces a final newline policy: one, two, or none
  • Code style for TS/JS
    • indentation is normalized to spaces using format.indent
    • quotes are converted to format.quotes (single or double)
    • predictable code spacing around braces, commas, and =/comparison operators
    • optional semicolon normalization via format.semi
  • Import management for TS/JS files (see Features » Import Management)
  • Known JSON/config files get stable key ordering (see Features » JSON & Config Sorting)

It intentionally avoids touching:

  • template strings/backticks
  • content within string literals
  • non-TS/JS code style beyond whitespace and final newline

Configuration

Set options in pickier.config.* under format:

ts
import type { PickierConfig } from 'pickier'

const config: PickierConfig = {
  format: {
    extensions: ['ts', 'js', 'html', 'css', 'json', 'jsonc', 'md', 'yaml', 'yml', 'stx'],
    trimTrailingWhitespace: true,
    maxConsecutiveBlankLines: 1,
    finalNewline: 'one', // 'one' | 'two' | 'none'
    indent: 2,
    quotes: 'single', // 'single' | 'double'
    semi: false,
  },
}
export default config

CLI usage:

bash
# check without writing
pickier format . --check

# write changes
pickier format src --write

# restrict to TS/JS
pickier format . --ext .ts,.tsx,.js,.jsx --write

Whitespace normalization

Trailing spaces are removed and blank lines are collapsed per maxConsecutiveBlankLines.

Before:

ts
function demo() {
  console.log('hi')
}

After (maxConsecutiveBlankLines: 1):

ts
function demo() {
  console.log('hi')
}

Final newline policy

Pick one of:

  • one: ensure exactly one trailing newline (default). If the file contains imports, the formatter may ensure one additional separator newline after the import block as needed while still ending with a single final newline.
  • two: end with a blank line and a final newline (i.e., two newlines total at EOF)
  • none: strip all trailing newlines

Examples:

text
"abc"            -> one  -> "abc\n"
"abc\n\n\n"       -> one  -> "abc\n"
"abc"            -> two  -> "abc\n\n"
"abc\n"          -> none -> "abc"

Indentation (TS/JS)

Pickier supports two indentation styles:

  • spaces (default): indentation normalized to multiples of format.indent
  • tabs: indentation normalized to tabs; each level corresponds to one \t, while format.indent controls visual width for spacing calculations and diagnostics

Before:

ts
if (ok) {
\tconsole.log('x')
    if (y) {
       doThing()
    }
}

After (indentStyle: 'spaces', indent: 2):

ts
if (ok) {
  console.log('x')
  if (y) {
    doThing()
  }
}

Using tabs (indentStyle: 'tabs'):

ts
if (ok) {
  console.log('x')
  if (y) {
    doThing()
  }
}

Quote style (TS/JS)

Quotes in string literals are converted to your preference. Template strings are not modified.

Before:

ts
const a = 'hello'; const b = 'bye'

After (quotes: 'single'):

ts
const a = 'hello'; const b = 'bye'

After (quotes: 'double'):

ts
const a = 'hello'; const b = 'bye'

Escapes are adjusted so the literal content remains the same. For example, converting "He said \"hi\"" becomes 'He said "hi"'.

Code spacing (TS/JS)

The formatter inserts minimal spaces to improve readability:

  • before { in blocks/objects: if (x){ -> if (x) {
  • after commas: a,b -> a, b
  • around single = not part of ==, ===, =>, <=, >=: a=b -> a = b
  • around < and > in common contexts: a<b -> a < b, a>b -> a > b

Lines are also cleaned to avoid runs of multiple spaces (leading indentation is preserved).

Semicolons (TS/JS)

When format.semi is true, Pickier performs semicolon normalization that is safe with respect to JavaScript ASI and common patterns:

  • removes empty statement lines composed solely of semicolons
  • collapses duplicate trailing semicolons to a single one
  • keeps semicolons inside for (...) headers
  • keeps a single trailing semicolon at the end of a statement (to preserve current semantics and avoid surprising changes)

Before:

ts
foo()
// stray empty statement
for (let i = 0; i < 10; i++); // keep header semicolons

After:

ts
foo()
// (empty statement removed)
for (let i = 0; i < 10; i++); // unchanged header semicolons

If you want fully semicolon-free style, set semi: true and combine with a linter rule or an additional codemod to strip the remaining statement-terminating semicolons.

Import management

For TS/JS files, imports are parsed and rewritten to a canonical form (see Features » Import Management for details). This happens before indentation and spacing so the import block is clean and separated from code by a blank line when appropriate.

Pickier also removes any leading blank lines at the very top of the file to keep headers tidy.

JSON and config files

For known JSON files (package.json, tsconfig*.json), keys are ordered deterministically (see Features » JSON & Config Sorting). Other JSON files are left as-is except for whitespace normalization and final newline policy.

Large example

Before:

ts
import type { AA, TT } from 'z'
import type { C } from './t'
import { A as Alias, B } from 'z'
import { a, b, c } from './x'
import 'side-effect'
const s = 'str'
if (ready) {
  debugger
  for (let i = 0; i < 10; i++) { console.log(s) }
}

After (indent: 2, quotes: 'single', semi: true):

ts
import type { AA, TT } from 'z'
import { a, b, c } from './x'
import 'side-effect'

const s = 'str'
if (ready) {
  debugger
  for (let i = 0; i < 10; i++) { console.log(s) }
}

Notes:

  • type imports are grouped first, side-effects preserved, and named imports sorted
  • quotes converted to single quotes
  • spacing and indentation normalized
  • duplicate semicolons/empty statements removed; single statement semicolons kept

Best practices

  • Keep maxConsecutiveBlankLines to 1 to avoid noisy diffs
  • Use quotes: 'single' or quotes: 'double' consistently across the codebase
  • Prefer indent: 2 (or tabs) for compact diffs, unless your codebase standardizes on 4
  • Use --check in CI to fail builds when formatting is needed
  • Combine with the Linter to catch stylistic drift (e.g., noDebugger)

Troubleshooting

  • Imports not moving? Ensure the import block is at the top of the file; only the leading contiguous import block is managed
  • Template string not reformatted? This is by design; use a follow-up codemod if needed
  • YAML/MD spacing “changed”? Only trailing spaces and final newline policy are applied; content is untouched

FAQ

Q: Can I change indentation to tabs?

A: Currently the formatter converts tabs to spaces. Use a downstream tool if tabs are required throughout.

Q: Does it reorder object keys?

A: Only JSON keys in known files (package.json, tsconfig*.json) are ordered. Source object literals are left as-is; consider plugin rules such as sort-objects or sort-keys for lint-only checks.

Released under the MIT License.