Prettier-erInternals & Documentation
A technical walkthrough of how Prettier-er extends formatting behavior and evaluates readability, with implementation notes, analysis metrics, and usage examples.
Table of Contents
Formatting Additions
Prettier-er's primary contribution is a set of new formatting behaviors that replicate popular code styles not supported by default. The following section details how these options were implemented at the code level, and how users can configure them directly in their VS Code workspace settings.
How Formatting Works
1. Repositories & Their Roles
- VS Code Extension:A fork of the Prettier extension repo, which integrates the core into VS Code, allowing it to be called by the user. It loads user settings, resolves configuration, invokes the core formatter, and returns the formatted result to the editor.
- Custom Types Definitions:TypeScript definitions that describe all Prettier-er options, keeping both the extension and the core in sync.
- Prettier Core:A fork of Prettier that contains the formatting engine. The core implements all formatting logic; from parsing the AST to printing the formatted code.
Prettier-er Core: The Formatting Engine
The Prettier-er core is the engine that actually formats code. It is directly forked from Prettier's architecture, but includes several optional formatting features. Its responsibilities are to parse the source text sent by the extension and reformat using the passed through settings.
Prettier-er doesn't change how Prettier parses the text. Rather, it adds new logic that changes how the text is reprinted after being converted into an AST. These will be discussed in the next section.
The Core's formatting logic checks against several settings sent by VS Code when invoked, which are used to customize the output. For example, if options.allmanStyle
is true (meaning the user has enabled their Allman Style setting), then when printing compatible language blocks, like JavaScript and CSS, Prettier uses that true value to add a new line character before opening braces.
Types Definitions: Option Contracts & Synchronization
This repository contains the TypeScript type definitions for Prettier-er. Since the core formatter is written in JavaScript, a separate .d.ts file provides static types for its API and options. These definitions ensure that all parts of the system agree on option names and types.
This was the least changed of the three repositories, only serving as a place we added any new setting that the formatter needed to care about. It exports interfaces/types for the Prettier-er API, including the new fields, like forceObjectBreak, allmanStyle, matrixArray, and the others, all with the appropriate types to match the allowed values.
The extension also consumes these definitions by referencing @types/prettier. In fact, in the extension's package.json, the dependency for @types/prettier is pointed to the repository. This means that when the extension's TS code imports prettier, it takes in the custom .d.ts, so the new options are recognized, keeping the extension in sync with the core.
For example: if the extension calls the format command, and passes in{allmanStyle: true}
, the types ensure that allmanStyle is accepted with no type errors, and is the correct type (boolean). If the type definitions weren't updated, the extension either wouldn't compile, or would be forced to cast all those fields to any.
In summary, types-prettier makes the extension and the core communicate smoothly, working like a contract between the two.
VS Code Extension: User Interface & Integration
This is the repository that stores all the code for the extension itself, meaning technically the source code for the other two repositories is also present here, tucked away in node_modules
and compiled index.js
files. This component acts as the glue that connects VS Code to the rest of the system, allowing the user to invoke Prettier-er formatting, change style settings, and run Readability Analysis (explained in its own section later).
The responsibilities of this component are:
- Settings UI:Declare and document configuration options in VS Code's workspace settings UI.
- Configuration Resolution:Read and merge user configuration from both VS Code settings and Prettier config files.
- Formatter Selection:Decide which Prettier module to run.
- Formatter Invocation:Invoke the Prettier-er formatter with the resolved options.
- Editor Integration:Apply the formatted result to the active editor window.
- Command Registration:Implement editor commands like
Format Document
andAnalyze Document (Metrics)
.
In short, this component handles acquiring all the relevant data from the editor and workspace settings, packages it up, and sends it to the core formatter. Once the formatted string is returned, it updates the open document in the editor with the new text. For more details on how Readability Analysis works in this component, see the dedicated section below.
2. Formatting Pipeline
Now that we've covered the structure of the system, let's look at how a document actually moves through it, from unformatted source to clean, styled output.
Below is a general overview of the full data pipeline of Prettier-er, from when the user calls for a format, to when the user sees the formatted document.
The process begins with the user's settings. For Prettier-er's custom formatting logic, the important source for these are VS Code's workspace settings. These settings are defined in the extension's package manifest.
The extension, running within VS Code, reads in the config. It prioritizes local settings over global config. When finished, it has a complete set of option values, including any defaults for values not set explicitly by the user in any of the sources.
The extension then determines the appropriate Prettier module, and ensures that any needed plugins are also loaded.
The extension can finally call the Core using prettier.format
with the source code and the options object. Thanks to the custom types, the extension passes exactly the fields Prettier-er expects, including base Prettier and custom Prettier-er options, in the correct types. From here, control moves to the Prettier-er Core.
The Prettier-er Core parses the document text from the extension into an AST, then unfolds the tree back into a string. This unfolding, or "printing" as it's called in the code, is where the formatting occurs. For example, if options.allmanStyle
is enabled, then Prettier-er will insert a newline character before any { in the code. But, this is only done in the printing process; nothing is done to the document when being converted into the AST.
After printing is complete, the Core sends back a formatted string to the extension, as the return value of prettier.format
. The extension compares this string to the original text. If it's different, it creates a text edit. It then applies the edit to the editor buffer, and the user's document is updated with the formatted code.
The cycle ends here. The user has their newly formatted code, and can choose to save the changes, or with an undo action, can revert to the code prior to formatting.
Formatting Features
Prettier-er's primary goal is to give developers a simple way to make their code clean and uniquely their own. Below you'll find each formatting enhancement introduced by Prettier-er, with real examples of how they affect your code.
Allman Style
This option places opening braces on their own line rather than immediately after the preceding statement. This is known as Allman style. It's a classic approach for making code feel more spacious without relying on extra blank lines.
Prettier-er introduces an Allman Style toggle for JavaScript, TypeScript, and even a “pseudo Allman” for CSS. When enabled, you'll see a newline before opening braces in compatible structures like functions, loops, and conditionals.
Prettier Default
function foo() {
bar();
}
With Allman Style Enabled
function foo()
{
bar();
}
Force Object Literals to One or Multiple Lines
By default, Prettier formats object literals by expanding or collapsing based on where you've placed line breaks. This means objects are styled literal by literal, which can be annoying to manage, especially across a large codebase.(See Prettier's rationale)
Prettier-er introduces the forceObjectBreak
setting, which lets you decide: preserve (default, follows Prettier's logic), forceSingleLine (all objects on one line), or forceMultiLine (every property gets its own line).
This makes it easy to enforce a consistent style, especially for teams or in projects where object formatting is a code review sticking point.
Force Single Line
const options = { foo: 1, bar: 2 };
Force Multi-Line
const options = {
foo: 1,
bar: 2
};
Preserve: Before (expands to multi-line)
const user = {
name: "John Doe", age: 30 };
Preserve: After
const user = {
name: "John Doe",
age: 30,
};
Preserve: Before (compacts to single line)
const user = { name: "John Doe",
age: 30
};
Preserve: After
const user = { name: "John Doe", age: 30 };
Matrix-style Arrays
Multi-dimensional data is much easier to scan when formatted visually as a matrix. Prettier-er's matrixArray
option enables this, but only when you signal it with line breaks in your source.
With Matrix Array enabled, any array that already includes newlines between elements will be formatted as a “grid,” preserving the visual structure. Arrays written on a single line (or with no manual matrix intent) remain unchanged.
Standard Array
const grid = [1, 2, 3, 4];
Matrix Array Formatting
const grid = [
1, 2,
3, 4,
];
One-line Getters & Setters
This toggle compresses trivial getter and setter functions to a single line. If a getter/setter has only one short statement and no internal comments, Prettier-er collapses it for readability.
For longer, multi-statement, or commented functions, this setting won't apply. The focus is on cutting down vertical clutter for boilerplate code.
Before
get name() {
return this._name;
}
After (One-line)
get name() { return this._name; }
New Line Else Statement
Normally, Prettier places else
or else if
directly after the closing brace of the preceding if
. Prettier-er's toggle inserts a newline before these keywords, emphasizing branch boundaries for more readable logic.
This is particularly helpful in large or complex conditionals. When used with Allman Style, else
ends up on its own line, further clarifying the structure.
Prettier Default
if (x > 0) {
doSomething();
} else {
doSomethingElse();
}
With New Line Else Enabled
if (x > 0) {
doSomething();
}
else {
doSomethingElse();
}
Keep Multiple Blank Lines (Block Padding)
By default, Prettier collapses consecutive blank lines down to one, even if the developer inserted them intentionally. Prettier-er's Keep Multiple Blank Lines toggle preserves those blank lines at the start or end of code blocks (like functions or classes).
This is helpful when you want visual separation around important logic, or to group code by intention.
Before (with blank lines, MBL on)
function example() {
const x = 5;
}
Prettier Default
function example() {
const x = 5;
}
Retain Blank Lines (General Whitespace)
Retain Blank Lines expands on block padding: it tells Prettier-er to preserve consecutive blank lines anywhere in your code, including between top-level declarations, imports, and function definitions.
For large files, this is an easy way to visually segment unrelated sections or functions, without constantly fighting the formatter.
Before (with blank lines, RBL on)
function foo() {
return 1;
}
function bar() {
return 2;
}
Prettier Default
function foo() {
return 1;
}
function bar() {
return 2;
}
Same-Line Selectors (CSS)
In CSS, selectors are usually listed one per line for clarity, especially when deeply nested. But this can be a bit much for simple rulesets.
Prettier-er's toggle enables a compact style: multiple selectors on the same line, separated by commas. Useful for short, related selectors that share a rule and don't need individual emphasis.
Prettier Default
h1,
h2,
h3 {
font-weight: bold;
}
With Same-Line Selectors Enabled
h1, h2, h3 {
font-weight: bold;
}
Readability Analysis Tool
In addition to formatting, Prettier-er offers a research-backed readability analysis tool. It scans your code for subtle patterns—like long lines, dense nesting, or poor spacing—that can quietly reduce clarity. The following sections explain how the analysis works and how to interpret its results.
How Readability Analysis Works
The analysis is performed entirely within the VS Code extension, without ever invoking the Prettier core. When run, it processes your code line by line, using structural metrics from the literature to highlight patterns that might increase cognitive overhead—not to enforce arbitrary rules, but to help you write clearer, more maintainable code.
The process begins when VS Code passes the document to the analysis tool via a command, just as it does for formatting. The tool reads the document line by line, constructing lightweight objects for each line that record its type and any relevant metrics.
Once all lines are processed, the analysis tool calculates averages for most metrics and tracks counts for ratio-based metrics (such as empty lines or comment lines). These are then divided as appropriate to produce the final ratios. More details on the calculation of each metric appear later in this documentation.
After computing the metrics, the tool checks each against user-defined thresholds in the workspace settings, allowing full control over analysis strictness. Any thresholds that are exceeded are reported, along with suggestions for improvement. The tool also performs a simple statistical check to flag outlier lines that may be disproportionately affecting a given metric.
The resulting analysis is written to a string and displayed in a new, unsaved tab in the editor window, ensuring that reports don't clutter your workspace.
Readability Analysis Options
Every analysis metric in Prettier-er is fully configurable through your workspace settings, just like its formatting options. When you run readability analysis, each metric is checked against your chosen thresholds (with sensible defaults that work for most projects). But true to Prettier-er's philosophy, you're free to tighten or relax any rule to fit your team or personal style. Below you'll find every available option and what it controls. Guidance for interpreting the results comes in the next section.
Line Length Threshold (default: 40)
Average characters per line. Raise to allow longer lines; lower to encourage wrapping. (Blank and comment lines count toward the average.)
Nesting Count Threshold (default: 1)
Average opening curly braces { per line. Raise to permit deeper inline nesting; lower to flatten code.
Member Accessor Threshold (default: 1)
Average identifier-following dots (.) per line. Raise to permit longer chains; lower to split them up.
Whitespace Ratio Threshold (default: 0.2)
Ratio of empty lines to non-empty lines. Raise the value to force more whitespace; lower it to allow denser code. (The analysis warns when the ratio drops below this value)
Identifier Count Threshold (default: 2)
Average identifiers per line. Raise to tolerate denser expressions; lower for simpler lines.
Identifier Minimum Length Threshold (default: 2)
Shortest allowed identifier length. Raise to flag longer names; lower to allow shorter ones.
Interpreting Readability Metrics
When a metric in your code breaks its configured threshold, it doesn't mean you've made a mistake. A flag isn't a failure, it simply means one of your metrics exceeded its threshold. Because all thresholds are yours to tune, treat the report as a suggestion, not a verdict.
Line Length
Unlike Prettier's classic “print width” (which defaults to 80 and controls only the longest allowed line), this setting watches for a pattern of generally long lines. Here, the default threshold is 40. Because the metric is an average, a single long line won't break it, and blank lines even pull the number down.
If you get flagged for high average line length, look for places where you can break up dense code into smaller chunks. Try adding newlines inside long function calls, splitting up complex expressions, or moving chained method calls to separate lines. Often, just reformatting long conditionals—by using temporary variables or breaking up ternaries—can do a lot to improve clarity (and lower your average). Additionally, if a single line is dramatically longer than the rest, it's flagged as an outlier, so you can quickly identify the biggest offenders.
Remember, this is a style guideline, not a hard rule. The goal is to make your code easier to read at a glance, not to force everything onto separate lines. Adjust the threshold higher or lower to match your team's preferences or your own style.
Nesting Count
High nesting scores generally mean you're wrapping several layers of code together on a single line, which can quickly make logic hard to follow. This is most common with nested loops, functions within functions, or deeply chained callbacks. Keep in mind this only counts nesting on one line, not multiple. This is intentional, because breaking up a series of opening braces on one line into multiple is a good way to fix this issue.
If you're flagged here, try breaking out your logic: use newlines to spread out nested code blocks, and consider whether any deeply nested functions or loops can be pulled out or refactored. A good rule of thumb: if you have to count the nesting level on your fingers, it's time to break things up. Editors with vertical line guides for nesting will thank you! The tool also calls out lines whose nesting is far above the file's norm, to help you quickly spot potential problem areas.
Like all metrics here, it's not a hard limit. Sometimes deep nesting is justified, but the goal is to keep each logical step as clear and approachable as possible.
Member Accessor Count
This flag is most common in object-oriented code or complex data structures, where you might see things like user.profile.settings.theme. When you chain several properties or methods in a row, it can be hard to keep track of where everything is coming from, especially if you need to debug or revisit your code later.
If your code is breaking this threshold, try breaking up long chains into multiple variables, with each representing a logical step. For example, instead of writing everything in one expression, you could do something like:
const settings = user.profile.settings;
const theme = settings.theme;
This approach makes your code easier to read and debug, and clarifies what each step in the chain represents.
The analysis also includes an outlier detector, so if one or two lines are responsible for most of the chaining, those specific lines will be called out in the report. This helps you quickly spot the biggest offenders.
As always, some chaining is perfectly fine—just keep an eye out for places where breaking things up can make your code more readable and less error-prone.
Comment-to-Code Ratio
Unlike other metrics, Comment-to-Code doesn't look at individual lines or compute an average. It's a document-wide ratio, which means outlier detection doesn't apply. The assumption is that comments play a key role in improving long-term readability, both for teammates and for your future self.
A low comment ratio suggests that your code might be hard to understand without context. Even if the logic is simple now, that clarity fades quickly with time. This metric nudges you to be proactive: adding context, explanations, or clarifying intent, all so that your code is easier to follow down the line.
Whitespace Ratio
Dense code is hard to scan. Without whitespace breaks, logic can blend together, making debugging harder and contributing to visual fatigue. This metric encourages breaking your code into chunks that are easier to read and follow at a glance.
Identifier Count
An identifier is any named reference—like variables, function names, object keys, and so on. If a line contains multiple distinct names, it increases the mental load required to understand what the line is doing.
The threshold is set to 2 by default. That means if, across your document, your average line contains more than two identifiers, you'll receive a warning. Outlier detection is also used, so if a specific line has far more identifiers than the rest, it may be called out directly.
A high identifier count is often a sign that your code is trying to do too much in a single line, forcing the reader to juggle too many references at once. This can make the logic harder to follow and the code more error-prone when revisiting later. Breaking up logic into simpler steps often improves both readability and maintainability.
Identifier Minimum Length
This is the only metric that does not rely on file-wide averages. Instead, it checks each declared identifier on the current line and asks a simple question: “Is this name shorter than the threshold?” The default threshold is 2
, so only single-letter declarations (i
, j
, x
, …) trigger a flag.
When a line triggers the check, the report lists every short name found on that line, so you can fix them in one pass.
Short loop counters are sometimes fine, but overusing microscopic names outside tight loops can hurt readability. Treat each flag as a nudge to choose something more descriptive (or lower the threshold if your team is happy with terse names).
Note that only declarations are used to decide whether a line is flagged; merely using a short variable won't trip the check unless that variable is also declared on the same line.
Comment-to-Code Ratio Threshold (default: 0.3)
Ratio of comment-only lines to code-only lines. Raise the value to make the rule stricter; lower it if you're comfortable with fewer comments. (The analysis warns when the ratio drops below this value)