Prettier 3.4: A lot of bug fixes
This release includes numerous bug fixes and other improvements.
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!
Other Changes
JavaScript
#13315 by @fisker, @syi0808)
Fix template literal print with array (// Input
const string = `${[[1, 2], [3, 4]]}`
// Prettier 3.3
const string = `${[
[2],
[4],
]}`;
// Prettier 3.4
const string = `${[
[1, 2],
[3, 4],
]}`;
#16500 by @syi0808)
Add missing parentheses in tagged template literals (// Input
(String?.raw)``;
(getTag?.())``;
// Prettier 3.3
String?.raw``;
getTag?.()``;
// Prettier 3.4
(String?.raw)``;
(getTag?.())``;
\
in string literals (#16563 by @sosukesuzuki, #16763 by @fisker)
Don't remove useless Previously, Prettier would remove useless escape characters (\
) from string literals. However, this behavior was inconsistent as it did not apply to template literals, which was reported in issue #16542.
This issue is a feature request to extend this behavior to template literals as well.
After discussing this internally, the Prettier team concluded that removing useless escape characters, whether in string literals or template literals, is the responsibility of a linter, not a formatter.
This change disables the removal of useless escape characters in string literals across all languages supported by Prettier. To keep the previous behavior, we recommend using ESLint rules like no-useless-escape
.
Escaped quotes (e.g. "\"\'"
) may get unescaped when changing between different quotes. This is explained on the Rationale page of the official documentation.
// Input
const str = "\a";
// Prettier 3.3
const str = "a";
// Prettier 3.4
const str = "\a";
#16593 by @sosukesuzuki)
Improve comment formatting for logical expression in unary expression (// Input
!(
cond1 || // foo
cond2 || // bar
cond3 // baz
);
// Prettier 3.3
!(
(
cond1 || // foo
cond2 || // bar
cond3
) // baz
);
// Prettier 3.4
!(
cond1 || // foo
cond2 || // bar
cond3 // baz
);
#16643, #16705 by @fisker)
Removed support for experimental syntax (DecimalLiteral
- The Decimal proposal decided not to introduce new syntax. Thedecimal
plugin will be removed from Babel 8.importReflection
- The "Import Reflection" proposal has been renamed to "Source Phase Imports"
TypeScript
#16500 by @syi0808)
Add missing parentheses in tagged template literals (// Input
(String?.raw!)``;
(String?.raw)!``;
// Prettier 3.3
String?.raw!``;
String?.raw!``;
// Prettier 3.4
(String?.raw)!``;
(String?.raw)!``;
#16574 by @sosukesuzuki)
Preserve a comment on between decorator and modified parameter property (The current version of Prettier unexpectedly moves a line comment between a parameter property modified by readonly
, private
, public
, etc and a decorator. This output results in invalid TypeScript code.
This change ensures that the original format is preserved.
// Input
class Foo {
constructor(
@decorator
// comment
readonly foo,
) {}
}
// Prettier 3.3
class Foo {
constructor(
@decorator
readonly // comment
foo,
) {}
}
// Prettier 3.4
class Foo {
constructor(
@decorator
// comment
readonly foo,
) {}
}
#16578 by @sosukesuzuki)
Preserve a comment between modifier and the decorated property name (There was a bug where block comments between the modifier and the name of a decorated property were being treated as trailing comments of the decorator. This behavior was not only unexpected but also lacked idempotency.
With this change, the position of the block comment between the modifier and the property name is preserved.
// Input
class Foo {
@decorator
readonly /* comment */ propertyName;
}
// Prettier 3.3
class Foo {
@decorator /* comment */
readonly propertyName;
}
// Prettier 3.3 (second output)
class Foo {
@decorator /* comment */ readonly propertyName;
}
// Prettier 3.4
class Foo {
@decorator
readonly /* comment */ propertyName;
}
#16586 by @sosukesuzuki)
Don't print an extra line break for arrow function with type parameter in assignment (There was a bug where an extra line was inserted when assigning a chained arrow function with type parameters to a variable if there was a line comment above it.
This change ensures that the extra line is no longer inserted.
// Input
const foo1 =
// comment
<T,>() => () => 1;
// Prettier 3.3
const foo1 =
// comment
<T,>() =>
() =>
1;
// Prettier 3.4
const foo1 =
// comment
<T,>() =>
() =>
1;
#16729 by @fisker)
Support "Top-level await statements" (// Input
(await (await fetch()).json()).foo
// Prettier 3.3
await(await fetch()).json().foo;
// Prettier 3.4
(await (await fetch()).json()).foo;
#16730 by @fisker)
Fix class heritage breaks even though it remains inside the line width (// Input
export class JiraCreatePixFraudDetectionGateway
implements Pick<IssuePixFraudDetectionGateway, "createPixFraudDetectionIssue">
{
constructor(private readonly logger: Logger) {
this.logger = logger.child({
context: JiraCreatePixFraudDetectionGateway.name,
});
}
}
// Prettier 3.3
export class JiraCreatePixFraudDetectionGateway
implements
Pick<IssuePixFraudDetectionGateway, "createPixFraudDetectionIssue">
{
constructor(private readonly logger: Logger) {
this.logger = logger.child({
context: JiraCreatePixFraudDetectionGateway.name,
});
}
}
// Prettier 3.4
export class JiraCreatePixFraudDetectionGateway
implements Pick<IssuePixFraudDetectionGateway, "createPixFraudDetectionIssue">
{
constructor(private readonly logger: Logger) {
this.logger = logger.child({
context: JiraCreatePixFraudDetectionGateway.name,
});
}
}
declare
before accessibility in class properties (#16731 by @fisker)
Print // Input
class A {
declare private readonly name: string;
}
// Prettier 3.3
class A {
private declare readonly name: string;
}
// Prettier 3.4
class A {
declare private readonly name: string;
}
CSS
#16570 by @seiyab)
Resolve some types of overrun in CSS (/* Input */
@media (prefers-reduced-data: no-preference) {
@font-face {
unicode-range: U+0000-00FF, U+0131,
U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
}
/* Prettier 3.3 */
@media (prefers-reduced-data: no-preference) {
@font-face {
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
U+2215, U+FEFF, U+FFFD;
}
}
/* Prettier 3.4 */
@media (prefers-reduced-data: no-preference) {
@font-face {
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
}
#16572 by @sosukesuzuki)
Remove extra indentation in pseudo-class function (This change fixes a bug where extra indentation was added when line breaks were included in the argument list of pseudo-class functions like :where()
, :is()
, and :not
.
/* Input */
:where(input:not([type="button"], [type="reset"], [type="submit"]), textarea, select) {
/* CSS here */
}
/* Prettier 3.3 */
:where(
input:not([type="button"], [type="reset"], [type="submit"]),
textarea,
select
) {
/* CSS here */
}
/* Prettier 3.4 */
:where(
input:not([type="button"], [type="reset"], [type="submit"]),
textarea,
select
) {
/* CSS here */
}
#16583 by @sosukesuzuki, @fisker)
Fix formatting of incomplete CSS value comments (When formatting CSS value comments, the trailing /
may be lost, resulting in an invalid comment.
This change ensures that value comments are not truncated.
/* Input */
h1 {
--OFF: /* OFF */;
}
/* Prettier 3.3 */
h1 {
--OFF: /* OFF *;
}
/* Prettier 3.4 */
h1 {
--OFF: /* OFF */;
}
SCSS
#16607 by @fisker)
Fix error thrown when formatting SCSS file (// Input
@if true {
$newKey: ($key: ( $theme-name: $value ))
}
// Prettier 3.3
Error
// Prettier 3.4
@if true {
$newKey: (
$key:
(
$theme-name: $value,
),
);
}
#16617 by @Ma-hawaj, @fisker)
Fix wrong trailing comma position after comment in SCSS (/* Input */
$z-indexes: (
header: 1035,
overlay: 1202 // TODO: change to 1050 after bootstrap modals will be removed
);
/* Prettier 3.3 */
$z-indexes: (
header: 1035,
overlay: 1202 // TODO: change to 1050 after bootstrap modals will be removed,
);
/* Prettier 3.4 */
$z-indexes: (
header: 1035,
overlay: 1202, // TODO: change to 1050 after bootstrap modals will be removed
);
HTML
#16765 by @fisker)
Keep doctype in non-html files unchanged (In Prettier v3, we print HTML5 doctype in lowercase, it's safe for HTML files, however users may use the html
parser to format other files eg: XHTML files, lowercase the doctype
will break XHTML documents.
Starting with Prettier 3.4, we'll only lowercase HTML5 doctype (<!doctype html>
) when the file extension is .html
or .htm
, otherwise they will be untouched.
<!-- Input -->
<!-- foo.xhtml -->
<!DOCTYPE html>
<!-- Prettier 3.3 -->
<!doctype html>
<!-- Prettier 3.4 -->
<!DOCTYPE html>
Vue
#16733 by @fisker)
Fix extra semicolon inserted in Vue event binding with non-ascii characters (<!-- Input -->
<template>
<button @click="点击事件">点击!</button>
<button @click="onClick">Click!</button>
</template>
<!-- Prettier 3.3 -->
<template>
<button @click="点击事件;">点击!</button>
<button @click="onClick">Click!</button>
</template>
<!-- Prettier 3.4 -->
<template>
<button @click="点击事件">点击!</button>
<button @click="onClick">Click!</button>
</template>
Angular
#16862 by @fisker)
Support Angular 19 (Angular 19 added support for typeof
keyword in template expressions.
<!-- Input -->
<div>{{ typeof
x ===
'object' ? 'Y' : 'N'}}</div>
<!-- Prettier 3.3 -->
<div>
{{ typeof
x ===
'object' ? 'Y' : 'N'}}
</div>
// Prettier 3.4
<div>{{ typeof x === "object" ? "Y" : "N" }}</div>
Markdown
#15526 by @TomasLudvik)
Remove excessive spaces after line prefixes for unordered lists in Markdown (<!-- Input -->
- first line
- second line indented
- third line
- fourth line
- fifth line
<!-- Prettier 3.3 -->
- first line
- second line indented
- third line
- fourth line
- fifth line
<!-- Prettier 3.4 -->
- first line
- second line indented
- third line
- fourth line
- fifth line
#16546 by @seiyab)
Fix incorrect wrap in sentence with linkReference (<!-- Input (--prose-wrap=always) -->
This folder has [VHS] tape files to create gifs for the [Widget Showcase]. To run them, install VHS from main (the theme and screenshot commands are not yet released).
<!-- Prettier 3.3 -->
This folder has [VHS] tape files to create gifs for the [Widget Showcase]. To run
them, install VHS from main (the theme and screenshot commands are not yet released).
<!-- Prettier 3.4 -->
This folder has [VHS] tape files to create gifs for the [Widget Showcase]. To
run them, install VHS from main (the theme and screenshot commands are not yet
released).
#16619 by @tats-u)
Preserve non-ASCII whitespaces at the end of the line and beginning of the next line (Prettier removes non-ASCII spaces at the end of the line and beginning of the next line. However, this behavior is not consistent with the CommonMark spec.
https://spec.commonmark.org/0.31.2/#soft-line-breaks
Spaces at the end of the line and beginning of the next line are removed:
https://spec.commonmark.org/0.31.2/#unicode-whitespace-character
A Unicode whitespace character is a character in the Unicode Zs general category, or a tab (U+0009), line feed (U+000A), form feed (U+000C), or carriage return (U+000D).
Unicode whitespace is a sequence of one or more Unicode whitespace characters.
A space is U+0020.
The CommonMark spec doesn't mention non-ASCII spaces here, so removing them changes the content of the Markdown document.
<!-- Input -->
EM Space (U+2003) EM Space
全角スペース (U+3000) 全形空白
<!-- Prettier 3.3 -->
EM Space (U+2003) EM Space
全角スペース (U+3000) 全形空白
<!-- Prettier 3.4 -->
EM Space (U+2003) EM Space
全角スペース (U+3000) 全形空白
#16691 by @tats-u)
Don't break a line a between Chinese or Japanese and others (Markdown documents are mainly converted to HTML or components of JavaScript-based frameworks. This means that paragraphs in Markdown are eventually processed by the browser according to CSS rules. This is because many Markdown converter preserve line breaks in paragraphs in input Markdown and HTML itself does not specify how browsers should handle line breaks in text in HTML.
According to CSS rules (CSS Text Module Level 3 or later), browsers should remove line breaks between Chinese/Japanese characters instead of replacing them with spaces. However, this rule has been ignored by WebKit-based or Webkit-derived browsers (Chrome, Safari, and so on) for long time.
For example, the following HTML paragraph:
<p>
日本語
汉语
漢語
<p>
generated from the following Markdown:
日本語
汉语
漢語
should be rendered as follows according to CSS rules and actually is rendered such by Firefox:
日本語汉语漢語
However, Chrome and Safari render it as follows:
日本語 汉语 漢語
This is why we should stop Prettier from line breaking between Chinese/Japanese characters. We decided to stop Prettier from forcing users to use a plugin for a Markdown converter that concatenates lines that start or end with Chinese/Japanese characters (remark-join-cjk-lines, for example).
Also, a line break between Chinese/Japanese and others are equivalent to a space according to before the commit suspending a concrete rule in CSS Text Module Level 3 by commenting it out fixing an issue on the CSS Working Group Editor Drafts. Firefox follows this rule. Therefore, all browsers render the following paragraph:
<!-- prettier-ignore -->
```html
<p>日本語 English 汉语 한국어 漢語</p>
<p></p>
as follows:
日本語 English 汉语 한국어 漢語
However, Prettier has broken a line between Chinese/Japanese characters in Markdown for a long time, and between Chinese/Japanese and latin characters in some cases since 3.0.0. For example, the following Markdown paragraph:
日本語English汉语
English
漢語
is formatted as follows if --prose-wrap
is set to the other value than preserve
in Prettier 3.x:
日本語English汉语English漢語
However, the following HTML, which is generated by a Markdown-to-HTML converter based on the above Markdown:
<p>
日本語English汉语
English
漢語
</p>
is rendered as follows by all browsers:
日本語English汉语 English 漢語
This is why we should stop Prettier from line breaking al so around Chinese/Japanese characters in Markdown. We are going to conform Prettier's behavior to this rule in a future version. After that, line breaks between Chinese/Japanese and others will be allowed again under certain rules.
One of the few exceptions is spaces and line breaks between Chinese/Japanese and Korean letters. The following Markdown paragraphs are equivalent even in the current Prettier version:
현재 韓國의 大統領은 尹錫悅이다.
현재
韓國의
大統領은
尹錫悅이다.
You get the former if you format the latter with --prose-wrap=always
and a sufficiently long --print-width
value or with --prose-wrap=never
, and you get the latter if you format the former with --prose-wrap=always
with a extremely short --print-width
value. Therefore, we do not have to touch such spaces and line breaks.
Another exception is those between a Chinese/Japanese character and a meaningful symbol in Markdown like *
, `
, [
, and ]
. For example, the following Markdown paragraph is equivalent even in the current Prettier version:
**Yarn** のCLI経由でフォーマットするには `yarn prettier -w ` を実行してください。
**Yarn**
のCLI経由でフォーマットするには
`yarn prettier -w .`
を実行してください。
<!-- Input (--prose-wrap=always --print-width=20) -->
日本語 汉语 漢語 English 한국어 日本語 汉语 漢語 English 한국어 日本語 汉语 漢語 English 한국어 日本語 汉语 漢語 English 한국어
日本語汉语漢語English한국어日本語汉语漢語English한국어日本語汉语漢語English한국어日本語汉语漢語English한국어
<!-- Prettier 3.3 -->
日本語 汉语 漢語
English 한국어 日本
語 汉语 漢語 English
한국어 日本語 汉语
漢語 English 한국어
日本語 汉语 漢語
English 한국어
日本語汉语漢語
English한국어日本語
汉语漢語
English한국어日本語
汉语漢語
English한국어日本語
汉语漢語
English한국어
<!-- Prettier 3.4 -->
日本語 汉语 漢語 English
한국어
日本語 汉语 漢語 English
한국어
日本語 汉语 漢語 English
한국어
日本語 汉语 漢語 English
한국어
日本語汉语漢語English한국어日本語汉语漢語English한국어日本語汉语漢語English한국어日本語汉语漢語English한국어
#16816 by @tats-u)
Tell regexp-util to generate regex compatible with u flag (CJK characters outside of BMP and Ideographic Variation Sequences (IVS; variation selectors dedicated for han/kanji), which consume 2 characters in JavaScript string, have not been treated as CJK. This is due to the fact that Prettier has not passed the appropriate flag "u"
to the regexp-util
package.
In the following example, “𠮷” (U+20BB7) is out of BMP, and “葛󠄀” is a combination of a han “葛” (U+845B) in BMP and an IVS U+E0100. The latter requires a font with the support of Adobe Japan-1 (e.g. Yu Gothic UI and Source Han Sans) to be rendered as a form different from that of the character without IVS (葛).
<!-- Input (--prose-wrap=never) -->
a 字 a 字 a 字
𠮷
𠮷
葛󠄀
葛󠄀
終
<!-- Prettier 3.3 -->
a 字 a 字 a 字 𠮷 𠮷 葛󠄀 葛󠄀 終
<!-- Prettier 3.4 -->
a 字 a 字 a 字𠮷𠮷葛󠄀葛󠄀終
YAML
#
(#16489 by @fyc09)
Fix yaml comment in non-singleline items missing space before # Input
123: # hello
# comment
# Prettier 3.3
123:# hello
# comment
# Prettier 3.4
123: # hello
# comment
API
prettier.doc.printDocToString
(#13315 by @fisker)
Stop doc mutation in For performance reason, prettier.doc.printDocToString
used to mutate .parts
of the fill
command during print. It was converted to a pure function to ensure output correctness.
getPreferredQuote
public (#16567 by @sosukesuzuki)
Make This change makes the internal getPreferredQuote
function a part of the public API.
In languages like JavaScript, both single quotes and double quotes can be used for string literals. Prettier determines the quote to enclose a string literal based on the number of quotes within the string and the value of the singleQuote
option. For more details, please refer to the Rationale page.
The getPreferredQuote
function determines the appropriate quote to enclose a string literal and has the following interface:
type Quote = '"' | "'";
function getPreferredQuote(
text: string,
preferredQuoteOrPreferSingleQuote: Quote | boolean,
): Quote;
Here are some examples of how to use it:
import * as prettier from "prettier";
const SINGLE_QUOTE = `'`;
const DOUBLE_QUOTE = `"`;
console.log(prettier.util.getPreferredQuote(`Hello World Test`, SINGLE_QUOTE)); // '
console.log(prettier.util.getPreferredQuote(`Hello World Test`, DOUBLE_QUOTE)); // "
console.log(prettier.util.getPreferredQuote(`'Hello' "World" 'Test'`, SINGLE_QUOTE)); // "
console.log(prettier.util.getPreferredQuote(`"Hello" 'World' "Test"`, DOUBLE_QUOTE)); // '
console.log(prettier.util.getPreferredQuote(`"Hello" "World" "Test"`, SINGLE_QUOTE)); // '
console.log(prettier.util.getPreferredQuote(`'Hello' 'World' 'Test'`, DOUBLE_QUOTE)); // "
Making this function public will benefit plugin developers. Since the function is relatively short, you can find more details in the implementation.
#16857 by @sosukesuzuki)
Fix loading ESM-style shared config file in Node.js 23 (In Prettier 3.3, attempting to load an ESM-style shared config file in Node.js 23 resulted in the following warnings, preventing the options from being loaded:
[warn] Ignored unknown option { __esModule: true }.
[warn] Ignored unknown option { default: { trailingComma: "es5", tabWidth: 4, singleQuote: true } }.
This issue was caused by a new module feature in Node.js 23, known as require(ESM)
. Prettier 3.4 resolves this problem, allowing the options to load correctly.
For more details, please see https://github.com/prettier/prettier/issues/16812.
CLI
#16684 by @marcusirgens)
Ignore files in the Jujutsu directory (The Jujutsu VCS uses the .jj
directory, similarly to how Git uses .git
.
This change adds .jj
to the list of directories which are silently ignored by prettier.
Miscellaneous
cursorOffset
feature sometimes being catastrophically slow (#15709 by @ExplodingCabbage)
Fix Previously, Prettier's cursorOffset
feature would be spectacularly slow in certain unfortunate circumstances (namely when the user's cursor was not contained within a leaf node of the AST, and the non-leaf node containing it was very large and being significantly reformatted by Prettier). As a consequence, if you used Prettier via an editor integration that used cursorOffset
under the hood, your editor would sometimes inexplicably hang when you tried to format a file.
All examples of this phenomenon that we are aware of should now be fixed, but bug reports of any further pathological examples would be welcome.