Prettier 3.2: Support JSONC and Angular’s ICU expression
This release includes new features such as adding a JSONC parser, adding Angular’s ICU expressions, and many bug fixes.
We are still seeking feedback for the --experimental-ternaries
option released in Prettier 3.1. Please read A curious case of the ternaries and respond via the Google Forms link provided.
Additionally, we recommend reading Prettier's CLI: A Performance Deep Dive by Fabio Spampinato. This faster CLI is slated to be released as version 4.0.
If you appreciate Prettier and would like to support our work, please consider sponsoring us directly via our OpenCollective or by sponsoring the projects we depend on, such as typescript-eslint, remark, and Babel. Thank you for your continued support!
Highlights
JSON
jsonc
parser added (#15831 by @fisker)
New Previously, we infer the parser of .jsonc
files to be json
, but if we want keep the trailing comma, we'll have to use a hacky workaround config {parser: "json5", quoteProps: "preserve", singleQuote: false}
.
The new added jsonc
parser:
- Always quote the object keys.
- Wrap strings with double quotes.
- Of course, respect the
trailingComma
option.
Angular
#15777 by @sosukesuzuki)
Support formatting for Angular ICU expression (Support two kinds of Angular ICU expressions: plural
and select
.
<span i18n>
Updated:
{minutes, plural,
=0 {just now}
=1 {one minute ago}
other {{{minutes}} minutes ago}
}
</span>
<span i18n>
The author is {gender, select, male {male} female {female} other {other}}
</span>
Other Changes
JavaScript
#15209 by @bakkot)
Avoid introducing linebreaks in template interpolations (In a template string like
`this is a long message which contains an interpolation: ${format(data)} <- like this`;
avoid adding a linebreak when formatting the expression unless one is already present or it's unavoidable due to e.g. a nested function. Previously a linebreak could be introduced whenever some interpolation in the template was sufficiently "not simple":
`this is a long message which contains an interpolation: ${format(
data,
)} <- like this`;
Now it will instead be left alone.
If a linebreak is already present within the ${...}
, format as normal.
#15522 by @seiyab)
Fix non-idempotent formatting of method chain with empty line (// Input
Foo.a()
.b();
// Prettier 3.1 (first format)
Foo.a()
.b();
// Prettier 3.1 (second format)
Foo.a().b();
// Prettier 3.2
Foo.a()
.b();
#15677 by @fisker)
Fix formatting of ternary in function call (// Input
stopDirectory = await (useCache
? memoizedFindProjectRoot
: findProjectRootWithoutCache)(path.dirname(path.resolve(filePath)));
// Prettier 3.1
stopDirectory = await (useCache
? memoizedFindProjectRoot
: findProjectRootWithoutCache)(path.dirname(path.resolve(filePath)));
// Prettier 3.2
stopDirectory = await (
useCache ? memoizedFindProjectRoot : findProjectRootWithoutCache
)(path.dirname(path.resolve(filePath)));
#15806 by @fisker)
Fix inconsistencies for optional-chaining (Only happens when using typescript
, meriyah
or other ESTree parsers except babel
.
// Input
function someFunctionName() {
return isEqual(a.map(([t, _]) => t?.id), b.map(([t, _]) => t?.id));
return isEqual(a?.map(([t, _]) => t?.id), b?.map(([t, _]) => t?.id));
}
theValue = Object.entries(someLongObjectName).filter(
([listingId]) => someListToCompareToHere.includes(listingId),
);
theValue = Object.entries(someLongObjectName).filter(
([listingId]) => someListToCompareToHere?.includes(listingId),
);
// Prettier 3.1
function someFunctionName() {
return isEqual(
a.map(([t, _]) => t?.id),
b.map(([t, _]) => t?.id),
);
return isEqual(a?.map(([t, _]) => t?.id), b?.map(([t, _]) => t?.id));
}
theValue = Object.entries(someLongObjectName).filter(([listingId]) =>
someListToCompareToHere.includes(listingId),
);
theValue = Object.entries(someLongObjectName).filter(
([listingId]) => someListToCompareToHere?.includes(listingId),
);
// Prettier 3.2
function someFunctionName() {
return isEqual(
a.map(([t, _]) => t?.id),
b.map(([t, _]) => t?.id),
);
return isEqual(
a?.map(([t, _]) => t?.id),
b?.map(([t, _]) => t?.id),
);
}
theValue = Object.entries(someLongObjectName).filter(([listingId]) =>
someListToCompareToHere.includes(listingId),
);
theValue = Object.entries(someLongObjectName).filter(([listingId]) =>
someListToCompareToHere?.includes(listingId),
);
if
(#15826 by @fisker)
Fix comments in // Input
if (foo) for (i = 2; i > 0; i--) console.log(i); // comment 1
else bar();
for (;;){
if (foo) continue; // comment 2
else bar();
}
// Prettier 3.1
Error: Comment "comment 2" was not printed. Please report this error!
// Prettier 3.2
if (foo)
for (i = 2; i > 0; i--) console.log(i); // comment 1
else bar();
for (;;) {
if (foo)
continue; // comment 2
else bar();
}
TypeScript
#15811 by @seiyab)
Improve conditional type alias layout (// Input
type FallbackFlags<F extends Flags | undefined> =
Equals<NonNullableFlag<F>["flags"], {}> extends true
? Dict<any>
: NonNullableFlag<F>["flags"];
// Prettier 3.1
type FallbackFlags<F extends Flags | undefined> = Equals<
NonNullableFlag<F>["flags"],
{}
> extends true
? Dict<any>
: NonNullableFlag<F>["flags"];
// Prettier 3.2
type FallbackFlags<F extends Flags | undefined> =
Equals<NonNullableFlag<F>["flags"], {}> extends true
? Dict<any>
: NonNullableFlag<F>["flags"];
HTML
#15748 by @fisker)
Fix formatting of prettier-ignored unclosed elements (<!-- Input -->
<!-- prettier-ignore -->
<h1>
Hello <span>world!
<!-- Prettier 3.1 -->
<!-- prettier-ignore -->
<h1>
<!-- Prettier 3.2 -->
<!-- prettier-ignore -->
<h1>
Hello <span>world!
Angular
prettier-ignore
d angular control flow block (#15827 by @fisker)
Fix <!-- Input -->
<!-- prettier-ignore -->
@if (condition) {
Foo
} @else {
Other
}
<!-- Prettier 3.1 -->
<!-- prettier-ignore -->
@if (condition) {
Foo
}
} @else {
Other
}
<!-- Prettier 3.2 -->
<!-- prettier-ignore -->
@if (condition) {
Foo
}
@else {
Other
}
track
in 3rd expression of for
blocks (#15887 by @sosukesuzuki)
Avoid adding colon for <!-- Input -->
@for (item of items; let i = $index; track block) {}
<!-- Prettier 3.1 -->
@for (item of items; let i = $index; track: block) {}
<!-- Prettier 3.2 -->
@for (item of items; let i = $index; track block) {}
Ember / Handlebars
#15605 by @maxpowa)
Preserve path literal segments (Fixes scenarios where an input handlebars file containing literal segments would be reformatted to unwrap the literal segments, causing syntax errors in the resulting output.
<!-- Input -->
{{input.[funky<api!response]}}
{{input.[this one has spaces]}}
{{input.[anotherone].[0]}}
<!-- Prettier 3.1 -->
{{input.funky<api!response}}
{{input.this one has spaces}}
{{input.anotherone.0}}
<!-- Prettier 3.2 -->
{{input.[funky<api!response]}}
{{input.[this one has spaces]}}
{{input.anotherone.[0]}}
GraphQL
#15870 by @ArchitGajjar)
Improve GraphQL union types formatting (# Input
union SearchResult = Conference| Festival | Concert | Venue | Conference| Festival | Concert | Venue
# Prettier 3.1
union SearchResult =
Conference
| Festival
| Concert
| Venue
| Conference
| Festival
| Concert
| Venue
# Prettier 3.2
union SearchResult =
| Conference
| Festival
| Concert
| Venue
| Conference
| Festival
| Concert
| Venue
API
#15666 by @fisker)
Support absolute path as plugin in config file (// prettier.config.cjs
module.exports = {
plugins: [
// posix style
"/path/to/plugin.js",
// Windows style
"D:\\\\path\\to\\plugin.js",
// Use `require.resolve`
require.resolve("my-awesome-prettier-plugin"),
],
};
getFileInfo
and getSupportInfo
type definitions (#15854 by @auvred)
Fix const plugin: Plugin = {};
prettier.getFileInfo("./file.ext", {
plugins: [plugin],
});
prettier.getSupportInfo({ plugins: [plugin], showDeprecated: true });
Miscellaneous
#15750 by @ExplodingCabbage)
Fix false claim in docs that cursorOffset is incompatible with rangeStart/rangeEnd (The cursorOffset option has in fact been compatible with rangeStart/rangeEnd for over 5 years, thanks to work by @ds300. However, Prettier's documentation (including the CLI --help
text) continued to claim otherwise, falsely. The documentation is now fixed.