| | |

Prettier 21 ๐ŸŽจ The –debug-check Flag โ€” Verifying correctness preservation

โฑ๏ธ Estimated read: 12 min | |

The --debug-check flag is Prettier’s self-audit. It formats the input, then formats the output again, and compares the two results. If they differ, it reports an error. It also compares the abstract syntax tree (AST) of the input to the AST of the formatted output, and reports an error if they differ . The first check catches instability in Prettier’s own formattingโ€”a case where formatting a file once produces one result and formatting it again produces a different one. The second check catches a more serious class of bug: a case where formatting changes the meaning of the code. The flag is not a formatter. It is a verification tool, and it exists because Prettier’s core promise is that formatting does not change behavior .

The flag is used when a codebase is being formatted for the first time, or when a new Prettier version is being adopted, and the developer wants evidence that the formatting is safe before writing the changes to disk. It cannot be combined with --write; the CLI rejects the combination with an error . The output is not the formatted code. It is a pass/fail report with a diff when the check fails.

Key point: --debug-check runs two comparisons: prettier(input) === prettier(prettier(input)) for formatting stability, and ast(input) === ast(prettier(input)) for semantic preservation. It does not write to disk and cannot be combined with --write. A passing check prints the filename and exits with code 0; a failing check prints a diff to stderr and exits with a non-zero code.


Why the flag exists

The formatter’s promise. Prettier’s rationale documentation states the constraint directly: “Prettier only reformats code, it does not change the behavior of the code” . The promise is the reason developers trust the tool on a large codebase without reading every diff. The --debug-check flag is the mechanism that verifies the promise for a specific file. It does not prove the promise universally; it checks the specific input that the developer is about to format.

The AST comparison problem. Formatting a file and re-parsing it should produce the same AST. If it does not, the formatter has changed the semanticsโ€”a parenthesis was dropped, a comment moved into a string, a line break changed the meaning of a return. The flag catches these cases by parsing both versions and comparing the trees . The check is not perfect; the AST comparison includes location data that can produce false positives, and the Prettier team has had to strip the location fields to make the comparison meaningful . But when the check passes, it is evidence that the formatting is semantically safe.

The incremental adoption use case. The Prettier documentation recommends the flag when formatting an existing codebase: “If you’re worried that Prettier will change the correctness of your code, add --debug-check” . A developer who is about to run prettier --write on a thousand files can run prettier --debug-check first and see whether any file produces a semantic difference. The files that pass are safe to write. The files that fail need investigation before they are formatted.

The trade-off. The flag is slower than a normal format because it formats each file twice and parses it twice. It is a verification step, not a formatting step, and it is meant to be run on a specific set of files before a formatting operation, not on every commit. The CI check is --check, not --debug-check; the debug flag is for the one-time migration, and --check is for the ongoing enforcement.


a. Running the check and reading the output

The flag is passed on the command line alongside the file paths. It does not write to disk. The output is a filename on success and a diff on failure.

# Check a single file.
# The file is not modified.
npx prettier src/app.js --debug-check

On success, the CLI prints the filename to stdout and exits with code 0. The file is unchanged. The absence of an error is the pass signal.

# Check a directory of files.
# Each file is checked independently.
npx prettier "src/**/*.js" --debug-check

On failure, the CLI prints an error to stderr. The error format is [error] filename: prettier(input) !== prettier(prettier(input)) for an instability, or [error] filename: ast(input) !== ast(prettier(input)) for a semantic difference . Below the error, a diff shows the two outputs. The + and - lines in the diff are the locations where the two results diverge.

# Check stdin.
# The parser must be specified because there is no file extension.
echo 'const x = 1' | npx prettier --debug-check --parser babel

The stdin check is the same mechanism. The output is (stdin) on success, and the exit code is 0. The parser is required because the input has no file path to infer from.


b. The limitations and the real-world failure case

The flag is not a proof. It checks the specific input, and it can produce false positives when the AST comparison includes metadata that the formatter legitimately changes. The Prettier PHP plugin had to strip location fields from the AST before the comparison to avoid false failures . The flag also cannot catch a semantic change that produces an AST that is structurally identicalโ€”a change in whitespace that affects a template literal, for example, is not an AST change, and the flag does not detect it.

The real-world failure case is documented on Stack Overflow. A developer ran --debug-check on a file and received ast(input) !== ast(prettier(input)). The output was described as “uninterpretable” . The developer narrowed the problem by commenting out code until the failing line was isolated. The line was return (a && a.length > 0) && (b && b.length > 0);. The parentheses were unnecessary, but their presence produced a different AST from the output without them. The fix was to remove the parentheses before formatting . The case is instructive: the flag caught a difference that a human would not have noticed, and the fix was a manual edit that made the code match what Prettier would produce.

// The failing input
return (a && a.length > 0) && (b && b.length > 0);

// The fix
return a && a.length > 0 && b && b.length > 0;

The case also shows the limitation. The flag does not tell the developer why the AST differs or where in the file the difference originates. The developer has to narrow the input manually. The flag is a detector, not a debugger.


Complete Example Session

// ============================================
// PART 1: THE BASIC CHECK
// ============================================

// npx prettier src/app.js --debug-check
//
// On success:
//   stdout: src/app.js
//   stderr: (empty)
//   exit code: 0
//
// On failure:
//   stderr: [error] src/app.js: ast(input) !== ast(prettier(input))
//   stderr: (diff)
//   exit code: 1

// ============================================
// PART 2: THE STABILITY CHECK
// ============================================

// The first comparison is:
// prettier(input) === prettier(prettier(input))
//
// If the formatter is stable, formatting the output
// produces the same output again.
//
// A failure means the formatter has an instability
// that should be reported as a bug.

// ============================================
// PART 3: THE AST CHECK
// ============================================

// The second comparison is:
// ast(input) === ast(prettier(input))
//
// The input is parsed, the output is parsed,
// and the two trees are compared.
//
// A failure means the formatter changed the meaning
// of the code. This is the more serious failure.

// ============================================
// PART 4: THE --write CONFLICT
// ============================================

// โŒ This command fails:
// npx prettier src/app.js --debug-check --write
//
// The error: "Cannot use --write and --debug-check together."
//
// --debug-check is a verification step.
// --write is a modification step.
// They cannot run in the same invocation.

// ============================================
// PART 5: THE MIGRATION WORKFLOW
// ============================================

// Step 1: Run the check on the codebase.
// npx prettier "src/**/*.js" --debug-check
//
// Step 2: Investigate the failures.
// The diff shows the two outputs that differ.
// The AST error shows the semantic difference.
//
// Step 3: Fix the failing files manually.
// The fix is usually a simplification that the
// formatter would produce anyway.
//
// Step 4: Run the check again.
// The files that pass are safe to format.
//
// Step 5: Run the write.
// npx prettier "src/**/*.js" --write

// ============================================
// PART 6: THE CI COMBINATION
// ============================================

// --debug-check can be combined with --check.
// npx prettier src/app.js --debug-check --check
//
// The --check flag verifies the formatting.
// The --debug-check flag verifies the correctness.
// The combination is the full safety check.

// ============================================
// PART 7: THE STDIN CHECK
// ============================================

// echo 'const x = 1' | npx prettier --debug-check --parser babel
//
// The parser is required for stdin.
// The output is (stdin) on success.
// The exit code is 0 on success, non-zero on failure.

// ============================================
// PART 8: THE REAL FAILURE CASE
// ============================================

// Input:
// return (a && a.length > 0) && (b && b.length > 0);
//
// The AST of the input has the parentheses.
// The AST of the output does not.
// The trees differ, and the check fails.
//
// The fix is to remove the parentheses before formatting:
// return a && a.length > 0 && b && b.length > 0;

// ============================================
// PART 9: THE FALSE POSITIVE
// ============================================

// The AST comparison includes location data.
// The formatter changes the locations.
// The trees differ in the location fields,
// but not in the structure.
//
// The fix is to strip the location fields
// before the comparison. The Prettier plugins
// do this in their own test harnesses.

// ============================================
// PART 10: THE VERIFICATION CHECKLIST
// ============================================

// [ ] The check is run on a specific set of files.
// [ ] The check is not combined with --write.
// [ ] The failures are investigated manually.
// [ ] The failing files are fixed before the write.
// [ ] The check is rerun after the fixes.
// [ ] The write is run after the check passes.

The ten parts covered the basic check, the stability check, the AST check, the --write conflict, the migration workflow, the CI combination, the stdin check, the real failure case, the false positive, and the verification. The through-line is that the flag is a verification tool, not a formatting tool.


Quick Reference

The two comparisons

ComparisonError messageMeaning
prettier(input) === prettier(prettier(input))prettier(input) !== prettier(prettier(input))Formatting instability
ast(input) === ast(prettier(input))ast(input) !== ast(prettier(input))Semantic difference

The flag combinations

CombinationEffect
--debug-check aloneCheck only; no write
--debug-check --checkCheck formatting and correctness
--debug-check --writeโŒ Error: cannot be combined

The exit codes

CodeMeaning
0Check passed
1Check failed (instability or AST difference)
2Prettier error

Best Practices

โœ… Do This:

# Run the check before a migration
npx prettier "src/**/*.js" --debug-check                         # โœ…

# Fix the failures before writing
# The fix is usually a manual simplification.                     // โœ…

# Combine with --check for a full verification
npx prettier src/app.js --debug-check --check                    // โœ…

โŒ Don’t Do This:

# Combine with --write
npx prettier src/app.js --debug-check --write                    // โŒ

# Assume the check is a proof of correctness
# It checks the specific input, not the formatter universally.   // โŒ

# Ignore the AST failure
# The formatter changed the meaning of the code.                 // โŒ

Common Pitfalls

PitfallWhy It HappensFix
--write is rejectedThe flags are mutually exclusiveUse --write after the check passes
The AST error is uninterpretableThe diff does not show the causeNarrow the input manually
The check fails on valid codeThe AST includes location metadataThe plugin should strip locations
The check is slowIt formats and parses twiceUse it for migration, not every commit

Real-World Examples

1. Basic check

npx prettier src/app.js --debug-check

2. Directory check

npx prettier "src/**/*.js" --debug-check

3. Stdin check

echo 'const x = 1' | npx prettier --debug-check --parser babel

4. Combined check

npx prettier src/app.js --debug-check --check

5. The write conflict

# npx prettier src/app.js --debug-check --write # โŒ

6. The stability failure

[error] file.js: prettier(input) !== prettier(prettier(input))

7. The AST failure

[error] file.js: ast(input) !== ast(prettier(input))

8. The fix

// Before: return (a && a.length > 0) && (b && b.length > 0);
// After:  return a && a.length > 0 && b && b.length > 0;

9. The migration order

npx prettier "src/**/*.js" --debug-check && npx prettier "src/**/*.js" --write

10. The exit code

npx prettier src/app.js --debug-check; echo $?

Visual

The Two Checks

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  --debug-check RUNS TWO COMPARISONS                           โ”‚
โ”‚                                                              โ”‚
โ”‚  Input                                                       โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ”œโ”€โ”€โ–ถ Format once โ”€โ”€โ–ถ Output 1                             โ”‚
โ”‚    โ”‚                         โ”‚                               โ”‚
โ”‚    โ”‚                         โ–ผ                               โ”‚
โ”‚    โ”‚                    Format again โ”€โ”€โ–ถ Output 2            โ”‚
โ”‚    โ”‚                         โ”‚                               โ”‚
โ”‚    โ”‚                         โ–ผ                               โ”‚
โ”‚    โ”‚                    Output 1 === Output 2?               โ”‚
โ”‚    โ”‚                    No โ†’ stability error                 โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ””โ”€โ”€โ–ถ Parse input โ”€โ”€โ–ถ AST 1                                โ”‚
โ”‚                              โ”‚                               โ”‚
โ”‚                              โ–ผ                               โ”‚
โ”‚                         Parse output 1 โ”€โ”€โ–ถ AST 2             โ”‚
โ”‚                              โ”‚                               โ”‚
โ”‚                              โ–ผ                               โ”‚
โ”‚                         AST 1 === AST 2?                     โ”‚
โ”‚                         No โ†’ semantic error                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Migration Workflow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CHECK, FIX, THEN WRITE                                       โ”‚
โ”‚                                                              โ”‚
โ”‚  npx prettier "src/**/*.js" --debug-check                    โ”‚
โ”‚         โ”‚                                                    โ”‚
โ”‚         โ”œโ”€โ”€โ–ถ pass โ†’ safe to write                            โ”‚
โ”‚         โ”‚                                                    โ”‚
โ”‚         โ””โ”€โ”€โ–ถ fail โ†’ investigate                              โ”‚
โ”‚                   โ”‚                                          โ”‚
โ”‚                   โ–ผ                                          โ”‚
โ”‚              fix manually                                    โ”‚
โ”‚                   โ”‚                                          โ”‚
โ”‚                   โ–ผ                                          โ”‚
โ”‚              rerun the check                                 โ”‚
โ”‚                   โ”‚                                          โ”‚
โ”‚                   โ–ผ                                          โ”‚
โ”‚              npx prettier "src/**/*.js" --write              โ”‚
โ”‚                                                              โ”‚
โ”‚  The check is the gate. The write is the action.             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Flag--debug-check
ComparisonsStability (prettier(input) vs prettier(prettier(input))) and AST
Writes to diskNo
--write combinationโŒ Rejected
Exit code 0Check passed
Exit code 1Check failed
Use caseMigration verification

Key takeaways:

  • --debug-check verifies two things. It checks that formatting is stable (formatting the output produces the same output) and that the AST of the input matches the AST of the formatted output. The first catches formatter bugs; the second catches semantic changes .
  • The flag is a verification tool, not a formatting tool. It does not write to disk, and it cannot be combined with --write. The output is a pass/fail report, not the formatted code .
  • The flag is for migration, not for CI. The recommended workflow is to run the check on a codebase before formatting it for the first time, fix the files that fail, and then run --write. The ongoing CI check is --check, not --debug-check .
  • The AST comparison can produce false positives. The AST includes location metadata that the formatter legitimately changes. Plugins must strip the location fields before the comparison to avoid false failures .
  • The failure output is not a debugger. When the check fails, the diff shows the two outputs, but it does not identify the line that caused the difference. The developer must narrow the input manually. The Stack Overflow case shows the pattern: isolate the line, simplify it, and rerun the check .

Remember: Prettier’s core promise is that formatting does not change behavior. The --debug-check flag is the mechanism that verifies the promise for a specific file. It formats twice and parses twice, and it reports a failure when the results differ. The flag is not a proof; it checks the input it is given, and it can produce false positives. But when it passes, it is evidence that the formatting is safe. Use it before a large migration, not on every commit. The gate is the check, and the action is the write.

Was this helpful?


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!