Prettier 22 ๐จ Range Formatting โ Formatting only a segment of a file
Range formatting is the mechanism that lets Prettier format a slice of a file instead of the whole thing. The two options are --range-start and --range-end, and they accept character offsets. The start is inclusive, the end is exclusive, and the range is expanded to the nearest statement boundary before formatting begins . The expansion is the critical detail: Prettier does not format the exact characters you selected. It formats the first statement that contains the start offset and the last statement that contains the end offset. A selection that begins in the middle of a function call expands backward to the start of the call and forward to the end of the call.
The feature exists because editors need it. When a developer selects a block of code and presses the format shortcut, the editor sends the selection offsets to Prettier, and Prettier returns the formatted segment. The alternativeโformatting the entire file on every selectionโwould be slower and would produce diffs the developer did not ask for. The range options are the primitive that makes the selection-based formatting possible.
Key point: --range-start and --range-end are character offsets. The range is expanded to statement boundaries before formatting, and it cannot be combined with --cursor-offset. The feature is designed for editor integrations, not for the CLI’s normal formatting workflow.
Why range formatting expands to statement boundaries
The syntax tree problem. Prettier formats code by parsing it into an abstract syntax tree and printing the tree back out. A range that cuts a statement in half cannot be printed, because the printer expects complete nodes. The expansion to statement boundaries is the mechanism that ensures the range contains complete nodes. The selection starts at the first statement that contains the start offset and ends at the last statement that contains the end offset . The expanded range is the smallest set of complete statements that covers the selection.
The editor selection problem. An editor selection is a pair of cursor positions. The positions are not aligned to statement boundaries, and the user does not expect them to be. The expansion is what makes the selection useful: the user selects a line, and Prettier formats the statement that contains the line, not just the characters. The --range-start and --range-end options are the editor’s way of asking Prettier to format the selection without the editor having to compute the statement boundaries itself.
The --cursor-offset conflict. The range options cannot be used with --cursor-offset. The cursor offset is the position of the cursor after formatting, and it is meaningful only for a full-file format. A range format does not have a well-defined cursor position, because the output is a segment, not a whole file. The CLI rejects the combination . The editor integration must choose one or the other: format the selection with the range options, or format the file with the cursor option.
The trade-off. Range formatting is less predictable than full-file formatting. A selection that spans two statements formats both; a selection that spans one statement formats one. The output depends on where the statement boundaries fall, and the user may not anticipate the expansion. The // prettier-ignore comment is the escape hatch for the cases where the expansion produces an unwanted result .
a. The CLI usage and the offset computation
The CLI usage is straightforward: pass the offsets and the file path. The offsets are character positions, not line numbers. The editor computes them from the selection.
# Format the characters from offset 100 to 200.
# The range is expanded to statement boundaries.
npx prettier src/app.js --range-start 100 --range-end 200
The output is the whole file with the in-range content reformatted. The hjkl-mangler documentation describes the behavior precisely: “Prettier returns the whole file with only in-range content reformatted, so the output is safe to install directly” . The CLI does not write the output to disk by default; the --write flag is required for that. The editor integration captures the stdout and replaces the buffer content.
# Write the range-formatted output to disk.
npx prettier src/app.js --range-start 100 --range-end 200 --write
The --write flag with the range options is the combination for a batch range format. The file is modified in place, and only the in-range statements are reformatted. The rest of the file is unchanged.
The offset computation is the editor’s job. A line and column position must be converted to a character offset. The conversion depends on the file’s line endings and encoding. The hjkl-mangler library does this conversion and passes the byte offsets to Prettier . A custom integration must do the same, or the range will be off by a few characters and the expansion will select the wrong statements.
b. The editor integration and the statement boundary trap
The editor integration is the primary use case. The none-ls.nvim plugin uses the --range-start and --range-end flags for its range formatting . The VS Code Prettier extension uses the same options. The editor computes the offsets from the selection, calls Prettier, and replaces the selection with the output.
The statement boundary trap is the source of most range formatting surprises. A Stack Overflow answer describes the problem: if the selection spans across a closing brace, the entire block inside the brace is formatted, not just the selected lines . The expansion to statement boundaries means the selection grows to include the complete statement. The user who selects a single line inside a function may find that the entire function is reformatted.
// The user selects only the line with `let b`.
function test() {
let a = [1, 2, 3, 4];
let b = [1, 2, 3, 4]; // selected line
}
// The range expands to the statement that contains the selection.
// If the selection crosses the closing brace of the function,
// the whole function is formatted.
The // prettier-ignore comment is the workaround. Placing the comment above the node that should not be formatted excludes that node from the formatting . The comment is the per-node opt-out, and it is the only mechanism for protecting a specific statement from a range format that expands to include it.
Complete Example Session
// ============================================
// PART 1: THE BASIC RANGE FORMAT
// ============================================
// A file with two functions.
// The user selects the first function.
// The editor computes the offsets: 0 to 50.
// npx prettier src/app.js --range-start 0 --range-end 50
// The output is the whole file.
// The first function is formatted.
// The second function is unchanged.
// ============================================
// PART 2: THE EXPANSION
// ============================================
// The selection starts at offset 10 and ends at offset 20.
// Offset 10 is inside the first function.
// Offset 20 is inside the first function.
// The range expands to the first function's boundaries.
// The first function is formatted.
// The second function is unchanged.
// ============================================
// PART 3: THE CROSS-STATEMENT SELECTION
// ============================================
// The selection starts at offset 10 and ends at offset 100.
// Offset 10 is inside the first function.
// Offset 100 is inside the second function.
// The range expands to cover both functions.
// Both functions are formatted.
// ============================================
// PART 4: THE CLOSING BRACE TRAP
// ============================================
// The selection starts inside a function
// and ends after the closing brace.
// The expansion includes the entire function.
// The whole function is formatted, not just the selection.
// The fix is to adjust the selection to stay inside the function,
// or to use // prettier-ignore.
// ============================================
// PART 5: THE PRETTIER-IGNORE WORKAROUND
// ============================================
// function test() {
// let a = [1, 2, 3, 4];
// // prettier-ignore
// let b = [1, 2, 3, 4]; // this line is protected
// }
// The // prettier-ignore comment excludes the next node
// from the formatting, even if the range expands to include it.
// ============================================
// PART 6: THE CURSOR OFFSET CONFLICT
// ============================================
// โ This command fails:
// npx prettier src/app.js --range-start 10 --range-end 20 --cursor-offset 15
//
// The error: "Cannot use --cursor-offset with --range-start or --range-end."
//
// The cursor offset is for full-file formatting.
// The range options are for segment formatting.
// They cannot be combined.
// ============================================
// PART 7: THE WRITE FLAG
// ============================================
// npx prettier src/app.js --range-start 10 --range-end 20 --write
//
// The range-formatted output is written to disk.
// The rest of the file is unchanged.
// ============================================
// PART 8: THE EDITOR INTEGRATION
// ============================================
// The editor computes the offsets from the selection.
// It calls Prettier with the range options.
// It replaces the buffer with the output.
// The output is the whole file, not just the range.
// The editor must diff or replace the whole buffer.
// ============================================
// PART 9: THE OFFSET COMPUTATION
// ============================================
// The editor converts the line/column position to a character offset.
// The conversion depends on the line endings and the encoding.
// A UTF-8 file with multi-byte characters has offsets
// that do not match the character count.
// The editor must use the byte offset, not the character count.
// ============================================
// PART 10: THE VERIFICATION CHECKLIST
// ============================================
// [ ] The range options are used with character offsets.
// [ ] The range is expanded to statement boundaries.
// [ ] The --cursor-offset is not used with the range options.
// [ ] The --write flag is used for a batch range format.
// [ ] The // prettier-ignore comment protects the nodes that should not be formatted.
// [ ] The editor computes the offsets correctly.
The ten parts covered the basic range format, the expansion, the cross-statement selection, the closing brace trap, the prettier-ignore workaround, the cursor offset conflict, the write flag, the editor integration, the offset computation, and the verification. The through-line is that the range is expanded to statement boundaries, and the expansion is the source of both the feature’s utility and its surprises.
Quick Reference
The range options
| Option | Type | Default | Meaning |
|---|---|---|---|
--range-start | int | 0 | The inclusive start offset |
--range-end | int | Infinity | The exclusive end offset |
The expansion rules
| Selection | Expansion |
|---|---|
| Inside one statement | That statement |
| Across two statements | Both statements |
| Across a closing brace | The entire block |
| Empty | The statement at the cursor |
The conflicts
| Combination | Effect |
|---|---|
--range-start + --cursor-offset | โ Error |
--range-end + --cursor-offset | โ Error |
--range-start + --write | โ Writes the range-formatted file |
Best Practices
โ Do This:
# Use the range options for editor selections
npx prettier src/app.js --range-start 100 --range-end 200 // โ
# Use --write for a batch range format
npx prettier src/app.js --range-start 100 --range-end 200 --write // โ
# Use // prettier-ignore to protect a node
// prettier-ignore // โ
โ Don’t Do This:
# Combine the range options with --cursor-offset
npx prettier src/app.js --range-start 10 --cursor-offset 15 // โ
# Expect the exact selection to be formatted
# The range expands to statement boundaries. // โ
# Assume the output is only the selection
# The output is the whole file with the range reformatted. // โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| The whole function is formatted | The selection crossed the closing brace | Adjust the selection or use prettier-ignore |
| The cursor offset error | The options are combined | Use one or the other |
| The range is off by a few characters | The offset computation used character count, not bytes | Use byte offsets |
| The output is the whole file | The range format returns the whole file | The editor must replace the whole buffer |
Real-World Examples
1. Basic range format
npx prettier src/app.js --range-start 100 --range-end 200
2. Range format with write
npx prettier src/app.js --range-start 100 --range-end 200 --write
3. The expansion
// Selection inside a statement โ the statement is formatted
4. The closing brace trap
// Selection across the closing brace โ the whole block is formatted
5. The prettier-ignore
// prettier-ignore
const x = [1, 2, 3];
6. The cursor offset conflict
# npx prettier src/app.js --range-start 10 --cursor-offset 15 # โ
7. The editor integration
// The editor computes the offsets and calls Prettier
8. The byte offset
// The offset is a byte position, not a character position
9. The whole file output
// The CLI returns the whole file with the range reformatted
10. The default range
# --range-start 0 --range-end Infinity formats the whole file
Visual
The Range Expansion
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE SELECTION EXPANDS TO STATEMENT BOUNDARIES โ
โ โ
โ User selection โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ const x = 1; โ โ
โ โ const y = 2; โ โ selected โ
โ โ const z = 3; โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ Expanded range โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ const y = 2; โ โ the statement is formatted โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The selection is expanded to the complete statement. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Closing Brace Trap
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SELECTION ACROSS THE CLOSING BRACE โ
โ โ
โ function test() { โ
โ let a = [1, 2, 3, 4]; โ
โ let b = [1, 2, 3, 4]; โ selection starts here โ
โ } โ selection ends here โ
โ โ
โ The expansion includes the entire function. โ
โ The whole function is formatted. โ
โ โ
โ Fix: keep the selection inside the function, โ
โ or use // prettier-ignore. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Output
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE CLI RETURNS THE WHOLE FILE โ
โ โ
โ Input: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ function a() {} โ โ in range โ
โ โ function b() {} โ โ not in range โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ Output: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ function a() {} โ โ formatted โ
โ โ function b() {} โ โ unchanged โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The editor replaces the whole buffer with the output. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Start option | --range-start <int> (inclusive) |
| End option | --range-end <int> (exclusive) |
| Expansion | To statement boundaries |
| Output | The whole file with the range reformatted |
--cursor-offset | โ Cannot be combined |
--write | โ Writes the range-formatted file |
| Use case | Editor selection formatting |
Key takeaways:
- The range options are character offsets, not line numbers. The start is inclusive, and the end is exclusive. The range is expanded to the nearest statement boundaries before formatting begins .
- The expansion is the source of the feature’s utility and its surprises. A selection inside a statement formats the statement. A selection across a closing brace formats the entire block. The user must anticipate the expansion or use
prettier-ignore. - The CLI returns the whole file. The range format does not return only the selection. The output is the entire file with the in-range statements reformatted. The editor integration replaces the whole buffer with the output .
- The range options cannot be combined with
--cursor-offset. The cursor offset is for full-file formatting. The range options are for segment formatting. The CLI rejects the combination . - The byte offset is the correct unit. The offset is a byte position in the file, not a character count. A UTF-8 file with multi-byte characters has offsets that do not match the character count. The editor must compute the byte offset correctly .
Remember: Range formatting is the primitive that makes editor selection formatting possible. The --range-start and --range-end options are character offsets, and the range is expanded to statement boundaries before formatting. The output is the whole file with the in-range statements reformatted. The expansion is the mechanism and the surprise: a selection inside a statement formats the statement, and a selection across a closing brace formats the block. The prettier-ignore comment is the escape hatch. The range options are for editors, not for the CLI’s normal workflow. The cursor offset is the other option, and the two cannot be combined.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!