Prettier 3.4: A lot of bug fixes (original) (raw)

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

Fix template literal print with array (#13315 by @fisker, @syi0808)


// Input

const string = `${[[1, 2], [3, 4]]}`

// Prettier 3.3

const string = `${[

  [2],

  [4],

]}`;

// Prettier 3.4

const string = `${[

  [1, 2],

  [3, 4],

]}`;

Add missing parentheses in tagged template literals (#16500 by @syi0808)


// Input

(String?.raw)``;

(getTag?.())``;

// Prettier 3.3

String?.raw``;

getTag?.()``;

// Prettier 3.4

(String?.raw)``;

(getTag?.())``;

Don't remove useless \ in string literals (#16563 by @sosukesuzuki, #16763 by @fisker)

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";


// 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

);

Removed support for experimental syntax (#16643, #16705 by @fisker)

TypeScript

Add missing parentheses in tagged template literals (#16500 by @syi0808)


// Input

(String?.raw!)``;

(String?.raw)!``;

// Prettier 3.3

String?.raw!``;

String?.raw!``;

// Prettier 3.4

(String?.raw)!``;

(String?.raw)!``;

Preserve a comment on between decorator and modified parameter property (#16574 by @sosukesuzuki)

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,

  ) {}

}

Preserve a comment between modifier and the decorated property name (#16578 by @sosukesuzuki)

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;

}

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;

Support "Top-level await statements" (#16729 by @fisker)


// Input

(await (await fetch()).json()).foo

// Prettier 3.3

await(await fetch()).json().foo;

// Prettier 3.4

(await (await fetch()).json()).foo;

Fix class heritage breaks even though it remains inside the line width (#16730 by @fisker)


// 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,

    });

  }

}


// 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

Resolve some types of overrun in CSS (#16570 by @seiyab)


/* 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;

  }

}

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 */

}

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

Fix error thrown when formatting SCSS file (#16607 by @fisker)


// Input

@if true {

  <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi><mi>e</mi><mi>w</mi><mi>K</mi><mi>e</mi><mi>y</mi><mo>:</mo><mo stretchy="false">(</mo></mrow><annotation encoding="application/x-tex">newKey: (</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">n</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mord mathnormal" style="margin-right:0.03588em;">Key</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span></span></span></span>key: ( <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mi>h</mi><mi>e</mi><mi>m</mi><mi>e</mi><mo>−</mo><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">theme-name: </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span><span class="mord mathnormal">e</span><span class="mord mathnormal">m</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">nam</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>value ))

}

// Prettier 3.3

Error

// Prettier 3.4

@if true {

  $newKey: (

    $key:

      (

        <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mi>h</mi><mi>e</mi><mi>m</mi><mi>e</mi><mo>−</mo><mi>n</mi><mi>a</mi><mi>m</mi><mi>e</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">theme-name: </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span><span class="mord mathnormal">e</span><span class="mord mathnormal">m</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">nam</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>value,

      ),

  );

}


/* 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

Keep doctype in non-html files unchanged (#16765 by @fisker)

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 () 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


<!-- 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

Support Angular 19 (#16862 by @fisker)

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

Remove excessive spaces after line prefixes for unordered lists in Markdown (#15526 by @TomasLudvik)


<!-- 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

Fix incorrect wrap in sentence with linkReference (#16546 by @seiyab)


<!-- 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).

Preserve non-ASCII whitespaces at the end of the line and beginning of the next line (#16619 by @tats-u)

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) 全形空白

Don't break a line a between Chinese or Japanese and others (#16691 by @tats-u)

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:

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:

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:

is formatted as follows if --prose-wrap is set to the other value than preserve in Prettier 3.x:

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:

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한국어

Tell regexp-util to generate regex compatible with u flag (#16816 by @tats-u)

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


# Input

123: # hello

  # comment

# Prettier 3.3

123:# hello

  # comment

# Prettier 3.4

123: # hello

  # comment

API

Stop doc mutation in prettier.doc.printDocToString (#13315 by @fisker)

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.

Make getPreferredQuote public (#16567 by @sosukesuzuki)

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.

Fix loading ESM-style shared config file in Node.js 23 (#16857 by @sosukesuzuki)

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

Ignore files in the Jujutsu directory (#16684 by @marcusirgens)

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

Fix cursorOffset feature sometimes being catastrophically slow (#15709 by @ExplodingCabbage)

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.