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
, ornone
- Code style for TS/JS
- indentation is normalized to spaces using
format.indent
- quotes are converted to
format.quotes
(single
ordouble
) - predictable code spacing around braces, commas, and
=
/comparison operators - optional semicolon normalization via
format.semi
- indentation is normalized to spaces using
- 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
:
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:
# 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:
function demo() {
console.log('hi')
}
After (maxConsecutiveBlankLines: 1
):
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:
"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
, whileformat.indent
controls visual width for spacing calculations and diagnostics
Before:
if (ok) {
\tconsole.log('x')
if (y) {
doThing()
}
}
After (indentStyle: 'spaces'
, indent: 2
):
if (ok) {
console.log('x')
if (y) {
doThing()
}
}
Using tabs (indentStyle: 'tabs'
):
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:
const a = 'hello'; const b = 'bye'
After (quotes: 'single'
):
const a = 'hello'; const b = 'bye'
After (quotes: 'double'
):
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:
foo()
// stray empty statement
for (let i = 0; i < 10; i++); // keep header semicolons
After:
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:
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
):
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
to1
to avoid noisy diffs - Use
quotes: 'single'
orquotes: '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.