Skip to main content

Prettier 3.9: Major parser upgrades and Formatting improvements

· 37 min read

We are excited to announce Prettier 3.9!

This release brings major parser upgrades for Markdown, YAML, Flow, GraphQL, and Angular, along with formatting improvements for JavaScript and TypeScript (particularly in --no-semi mode).

If you find Prettier valuable, please consider sponsoring us on OpenCollective or supporting the upstream projects we rely on. Your contributions help us keep improving the tool for everyone.

Thank you for your continued support! ❤️

A reminder, when Prettier is installed or updated, it’s strongly recommended to specify the exact version in package.json: "prettier": "3.9.0", not "prettier": "^3.9.0".

If you use @prettier/plugin-oxc or @prettier/plugin-hermes, don't forget upgrade them to ensure the new formatting rules are applied.

Highlights

Markdown

Upgrade parser to latest micromark (#18277 by @seiyab, @j-f1, @fisker)

We've upgraded Prettier's Markdown parser from the outdated remark-parse v8 to the modern micromark v4. This upgrade significantly enhances CommonMark and GFM compliance, resolves numerous long-standing parsing bugs, and lays a stronger foundation for future enhancements.

Huge thanks to @seiyab, @j-f1, and everyone else who contributed to this long-awaited improvement for their tremendous effort in driving this major upgrade!

Note: While the core Markdown parser has been upgraded, the MDX parser upgrade is not yet complete. If you're familiar with the unified ecosystem, micromark, or MDX, we'd love your help to finish the migration — contributions are very welcome!

YAML

Update dependency yaml to v2 (#18419 by @ota-meshi, @fisker)

The YAML parser has been upgraded to use yaml v2, which fixes many long standing parse issues.

Thanks to excellent work on the yaml-unist-parser side by @ota-meshi.

GraphQL

Support GraphQL.js v17 (#19171 by @YBJ0000, #19297 by @fisker)

Prettier now fully supports newer syntax features from GraphQL.js v17, including directives on directive definitions, fragment arguments, and other enhancements.

Fragment arguments

# Input
fragment variableProfilePic on User {
...dynamicProfilePic(size: $size)
}

# Prettier 3.8
SyntaxError: Syntax Error: Expected Name, found "(". (2:23)

# Prettier 3.9
fragment variableProfilePic on User {
...dynamicProfilePic(size: $size)
}

Directives on directive definitions (and extend directive extensions)

# Input
directive @a @b on QUERY
extend directive @a @b

# Prettier 3.8
Error: Syntax Error: Expected "on", found "@".

# Prettier 3.9
directive @a @b on QUERY
extend directive @a @b

Flow

New Rust-based Flow parser (#19398 by @SamChou19815)

Prettier now uses the new Rust-based Flow parser (oxidized) released by the Flow team, which improves performance for Flow-typed code.

In local parser-only benchmarks, the new parser parsed Prettier's valid Flow fixtures in 266.4ms median time, compared with 422.6ms for the old parser, and parsed flow_parser.js in 1298.0ms, compared with 2269.6ms.

Other Changes

JavaScript

Stabilize comment around break and continue in no-semi mode (#7161 by @thorn0, @fisker)

// Input
for (;;) {
if (condition) {
break; // breaking comment

(possibleArray || []).sort()
}
}

// Prettier 3.8 (--no-semi, first format)
for (;;) {
if (condition) {
break // breaking comment

;(possibleArray || []).sort()
}
}

// Prettier 3.8 (--no-semi, second format)
for (;;) {
if (condition) {
break // breaking comment
;(possibleArray || []).sort()
}
}

// Prettier 3.9 (--no-semi, both first and second format)
for (;;) {
if (condition) {
break; // breaking comment

(possibleArray || []).sort();
}
}

Remove redundant parentheses in "return statement" (#18142 by @fisker)

// Input
function sequenceExpressionInside() {
return ( // Reason for a
a, b
);
}

// Prettier 3.8
function sequenceExpressionInside() {
return (
// Reason for a
(a, b)
);
}

// Prettier 3.9
function sequenceExpressionInside() {
return (
// Reason for a
a, b
);
}

Fix alignment in embedded template interpolations (#18380 by @fisker)

// Input
string = `
.class {
flex-direction: column${
long_cond && long_cond && long_cond
? "-reverse" :
""
};
}
`;
css = css`
.class {
flex-direction: column${
long_cond && long_cond && long_cond
? "-reverse" :
""
};
}
`;

// Prettier 3.8
string = `
.class {
flex-direction: column${
long_cond && long_cond && long_cond ? "-reverse" : ""
};
}
`;
css = css`
.class {
flex-direction: column${long_cond && long_cond && long_cond
? "-reverse"
: ""};
}
`;

// Prettier 3.9
string = `
.class {
flex-direction: column${
long_cond && long_cond && long_cond ? "-reverse" : ""
};
}
`;
css = css`
.class {
flex-direction: column${
long_cond && long_cond && long_cond ? "-reverse" : ""
};
}
`;

Avoid linebreaks in embedded template interpolations (#18380 by @fisker)

// Input
string = /* Comment */ `
<div>${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}</div>
`;
html = /* HTML */ `
<div>${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}</div>
`;

// Prettier 3.8
string = /* Comment */ `
<div>${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}</div>
`;
html = /* HTML */ `
<div>
${long_cond && long_cond && long_cond && long_cond && long_cond
? "content"
: ""}
</div>
`;

// Prettier 3.9
string = /* Comment */ `
<div>${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}</div>
`;
html = /* HTML */ `
<div>
${long_cond && long_cond && long_cond && long_cond && long_cond ? "content" : ""}
</div>
`;

Improve logical not expression print (#18397, #18401 by @fisker)

  • Fixed inner logical expression can be double parenthesized in some case.
  • Inline the expression in if/while/do..while condition, to reduce diff when changing condition to negated value.
// Input
if (!(
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
)) {
} else if (
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
) {
}

// Prettier 3.8
if (
!(
// `import("foo")`
(
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
)
)
) {
} else if (
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
) {
}

// Prettier 3.9
if (!(
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
)) {
} else if (
// `import("foo")`
node.type === "ImportExpression" ||
// `type foo = import("foo")`
node.type === "TSImportType"
) {
}

This code example above is a real world case right here in the Prettier codebase

Remove unexpected space inserted before expression in CSS selectors (#18460 by @kovsu)

// Input
css`foo:${bar} {}`

// Prettier 3.8
css`
foo: ${bar} {

}
`;

// Prettier 3.9
css`
foo:${bar} {
}
`;

Keep iife function comments in parentheses (#18538 by @fisker)

// Input
const a = (
/**
* @param {number} foo
* @return {number}
*/
function (foo) {
return foo + 1;
}
)(1);

// Prettier 3.8
const a = /**
* @param {number} foo
* @return {number}
*/
(function (foo) {
return foo + 1;
})(1);

// Prettier 3.9
const a = (
/**
* @param {number} foo
* @return {number}
*/
function (foo) {
return foo + 1;
}
)(1);

Fix comment print for parenthesized callee (#18540 by @fisker)

// Input
const a = (
function(){}
/* comment for callee */
)(),
b = (
function(){}
)(/* comment for call */)

// Prettier 3.8
const a = (function () {})(),
/* comment for callee */
b = (function () {})(/* comment for call */);

// Prettier 3.9
const a = (
function () {}
/* comment for callee */
)(),
b = (function () {})(/* comment for call */);

Preserve trailing double spaces in JSDoc (#18594 by @seiyab)

Some tools treat these spaces as meaningful.

await prettier.format("/**\n * With 2 spaces \n */", { parser: "babel" });

/* Prettier 3.8 */
// -> '/**\n * With 2 spaces\n */\n'

/* Prettier 3.9 */
// -> '/**\n * With 2 spaces \n */\n'
// ^^ space preserved

Drop support for the old "import assertions" syntax (#18611 by @fisker)

import assertions (using the assert keyword) is an old version of the current import attributes proposal.

// Old (deprecated)
import foo from "./foo.json" assert { type: "json" };

// Current standard
import foo from "./foo.json" with { type: "json" };

Babel 8 completely removed support for the legacy assert syntax (the old parser plugin is gone). Without Babel parser support, Prettier can no longer reliably parse or format code using import ... assert { ... }.

Please migrate to the with syntax.

- import foo from "./foo.json" assert { type: "json" };
+ import foo from "./foo.json" with { type: "json" };

See also Prettier's disclaimer about non-standard syntax.

Improve comment handling around empty call argument list (#18615 by @fisker)

// Input
call
// line comment
();
call(// line comment
);
call(
// line comment
);

call
/* block comment */
();
call(/* block comment */
);
call(
/* block comment */
);

// Prettier 3.8
call();
// line comment
call(); // line comment
call();
// line comment

call();
/* block comment */
call /* block comment */();
call();
/* block comment */

// Prettier 3.9
call
// line comment
();
call(
// line comment
);
call(
// line comment
);

call
/* block comment */
();
call(/* block comment */);
call(/* block comment */);

Fix unstable comment in call expression (#18615 by @fisker)

// Input
foo =
call(
/* call argument */);

// Prettier 3.8 (First format)
foo =
call();
/* call argument */

// Prettier 3.8 (Second format)
foo = call();
/* call argument */

// Prettier 3.9
foo = call(/* call argument */);

Allow empty call expression argument list with long comments to break (#18615 by @fisker)

// Input
call(/* a long long long long long long long long long long long long comment */);
array = [/* a long long long long long long long long long long long long comment */];
object = {/* a long long long long long long long long long long long long comment */};

// Prettier 3.8
call(/* a long long long long long long long long long long long long comment */);
array = [
/* a long long long long long long long long long long long long comment */
];
object = {
/* a long long long long long long long long long long long long comment */
};

// Prettier 3.9
call(
/* a long long long long long long long long long long long long comment */
);
array = [
/* a long long long long long long long long long long long long comment */
];
object = {
/* a long long long long long long long long long long long long comment */
};

Don't force empty array and object with short comments to break (#18617 by @fisker)

// Input
const array = [/* comment */]
const object = {/* comment */}

// Prettier 3.8
const array = [
/* comment */
];
const object = {
/* comment */
};

// Prettier 3.9
const array = [/* comment */];
const object = {/* comment */};

Fix dangling comment print in function parameters (#18623 by @fisker)

// Input
fn = function(
// comment
) {}
arrow = (
// comment
) => {}

// Prettier 3.8
fn = function () // comment
{};
arrow = () =>
// comment
{};

// Prettier 3.9
fn = function (
// comment
) {};
arrow = (
// comment
) => {};

Fix inconsistent comment attach between NewExpression and CallExpression (#18669 by @fisker)

// Input
foo( // comment
bar
);
new Foo( // comment
bar
);

// Prettier 3.8
foo(
// comment
bar,
);
new Foo(bar); // comment

// Prettier 3.9
foo(
// comment
bar,
);
new Foo(
// comment
bar,
);

Fix missing parentheses around optional chaining (#18720 by @fisker)

// Input
(a?.b()).c();

// Prettier 3.8
a?.b().c();

// Prettier 3.9
(a?.b()).c();

Fix range format on variable declaration (#18734, #18740 by @fisker)

// Input
let i ="format me!" ;
// ^^^^^^^^^^^^^^ Range

// Prettier 3.8
let i = "format me!"; ;

// Prettier 3.9
let i = "format me!";

Improve blank line detection between statements (#18736 by @fisker)

// Input
const exports = text
.matchAll(/(?<=\n)exports\.(?<specifier>\w+) = \k<specifier>;/g)
.map((match) => match.groups.specifier)
.toArray()
;

foo();

// Prettier 3.8 (--no-semi)
const exports = text
.matchAll(/(?<=\n)exports\.(?<specifier>\w+) = \k<specifier>;/g)
.map((match) => match.groups.specifier)
.toArray();
foo();

// Prettier 3.9
const exports = text
.matchAll(/(?<=\n)exports\.(?<specifier>\w+) = \k<specifier>;/g)
.map((match) => match.groups.specifier)
.toArray();

foo();

Preserve empty line before ; in no-semi mode (#18736, #18737 by @fisker)

// Input
a

;[].b()

// Prettier 3.8 (--no-semi)
a
;[].b()

// Prettier 3.9
a

;[].b()

Print leading semicolon before type cast comments (#18751 by @fisker)

// Input
;/** @type {string[]} */ (['foo', 'bar']).forEach(doStuff)

// Prettier 3.8 (--no-semi, first format)
/** @type {string[]} */ ;(["foo", "bar"]).forEach(doStuff)

// Prettier 3.8 (--no-semi, second format)
/** @type {string[]} */ ;["foo", "bar"].forEach(doStuff)

// Prettier 3.9 (--no-semi)
;/** @type {string[]} */ (["foo", "bar"]).forEach(doStuff)

Prevent moving comments after arrow into parameters (#18775 by @fisker)

// Input
KEYPAD_NUMBERS.map(num => ( // Buttons 0-9
<div />
));

const createIdFilter =
(foo) => /** @param {string} id */
(id) => id;

// Prettier 3.8
KEYPAD_NUMBERS.map(
(
num // Buttons 0-9
) => <div />
);

const createIdFilter = (foo /** @param {string} id */) => (id) => id;

// Prettier 3.9
KEYPAD_NUMBERS.map((num) => (
// Buttons 0-9
<div />
));

const createIdFilter = (foo) => /** @param {string} id */ (id) => id;

Fix comments above else (#18813 by @fisker)

// Input
/* Case 1 */
if (1) {
}

/* Case 2 */
else if (2) {
}

// Prettier 3.8
/* Case 1 */
if (1) {
} else if (2) {

/* Case 2 */
}

// Prettier 3.9
/* Case 1 */
if (1) {
}

/* Case 2 */
else if (2) {
}

Fix "comment not printed" error in debugger statement (#18840 by @fisker)

// Input
debugger // Comment
;

// Prettier 3.8
Error: Comment "comment" was not printed. Please report this error!

// Prettier 3.9
debugger; // Comment

Improve do..while print in no-semi mode (#18851 by @fisker)

// Input
do {
doStuff()
} while (1)

// comment
;[foo, bar].forEach(doStuff)

// Prettier 3.8
do {
doStuff()
} while (
1

// comment
)
;[foo, bar].forEach(doStuff)

// Prettier 3.9
do {
doStuff()
} while (1)

// comment
;[foo, bar].forEach(doStuff)

Fix duplicated comments in empty branches with experimentalTernaries (#18963 by @kovsu)

// Input
condition ? ifTrue
: [
// Hello, world!
];

// Prettier 3.8 (--experimental-ternaries)
condition ? ifTrue
// Hello, world!
: (
[
// Hello, world!
]
);

// Prettier 3.9 (--experimental-ternaries)
condition ? ifTrue : (
[
// Hello, world!
]
);

Preserve blank lines between JSX attributes (#19161 by @Poliklot)

Prettier now keeps one existing blank line between JSX attributes, consistent with how it preserves blank lines in many other places. Multiple blank lines are still collapsed to one.

// Input
<Button
type="submit"
variant="primary"
size="large"



disabled={isSubmitting}
onClick={handleSubmit}
/>;

// Prettier 3.8
<Button
type="submit"
variant="primary"
size="large"
disabled={isSubmitting}
onClick={handleSubmit}
/>;

// Prettier 3.9
<Button
type="submit"
variant="primary"
size="large"

disabled={isSubmitting}
onClick={handleSubmit}
/>;

Fix multiline JSDoc-style comment alignment in assignments (#19180 by @kovsu)

// Input
const foo = /**
* comment
*/ bar;

// Prettier 3.8
const foo = /**
* comment
*/ bar;

// Prettier 3.9
const foo =
/**
* comment
*/ bar;

Remove space in for statement without "update" expression (#19188 by @Poliklot)

// Input
for (initialize;;) {}
for (; condition;) {}

// Prettier 3.8
for (initialize; ; ) {}
for (; condition; ) {}

// Prettier 3.9
for (initialize; ;) {}
for (; condition;) {}

Fix unstable comment in arrow function with sequence expression body (#19253 by @itsybitsybootsy)

// Input
const fn = () => (a, b, c /* abc */);

// Prettier 3.8
const fn = () => (a, b, c) /* abc */;

// Prettier 3.9
const fn = () => (a, b, c /* abc */);

Fix unstable comment in arrow function with assignment expression body (#19262 by @itsybitsybootsy)

// Input
const fn = () => (a = b /* abc */);

// Prettier 3.8
const fn = () => (a = b) /* abc */;

// Prettier 3.9
const fn = () => (a = b /* abc */);

Keep trailing comments inside the parentheses of a sequence or assignment expression (#19263 by @sarathfrancis90)

// Input
const x = (a, b, c /* comment */);
const y = (a = b /* comment */);

// Prettier 3.8
const x = (a, b, c) /* comment */;
const y = (a = b) /* comment */;

// Prettier 3.9
const x = (a, b, c /* comment */);
const y = (a = b /* comment */);

Improve formatting of comments in objects (#19310 by @fisker)

// Input
const P = {/** X */
Y: "z",
}

// Prettier 3.8
const P = {
/** X */ Y: "z",
};

// Prettier 3.9
const P = {
/** X */
Y: "z",
};

TypeScript

Fix linebreak in member chain (#17251 by @fisker)

// Input
model = types
.model({ something: mxSomething })
.volatile<| "a" | "b">(() => [
annularThingamabobWrapper,
octahedralWeevilService,
cantileveredRhubarbProcessor,
annularPlatypusGenerator,
]);

// Prettier 3.8
model = types
.model({ something: mxSomething })
.volatile<
"a" | "b"
>(() => [annularThingamabobWrapper, octahedralWeevilService, cantileveredRhubarbProcessor, annularPlatypusGenerator]);

// Prettier 3.9
model = types
.model({ something: mxSomething })
.volatile<"a" | "b">(() => [
annularThingamabobWrapper,
octahedralWeevilService,
cantileveredRhubarbProcessor,
annularPlatypusGenerator,
]);

Fix formatting for new expression with type cast (#18507 by @fisker)

// Input
new (require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P)({
rootDir: dir,
});

(
require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P
)({
rootDir: dir,
});

// Prettier 3.8
new (require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P)({
rootDir: dir,
});

(
require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P
)({
rootDir: dir,
});

// Prettier 3.9
new (
require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P
)({
rootDir: dir,
});

(
require("./webpack/plugins/next-trace-entrypoints-plugin")
.TraceEntryPointsPlugin as P
)({
rootDir: dir,
});

Fix line comments jumping past member expression lookups (#18522 by @ulrichstark)

Line comments between a member expression object and property were incorrectly moved past subsequent content like as expressions.

// Input
function getClassNameFromPrototypeMethod(container) {
return ((container // a
.left as PropertyAccessExpression) // b
.expression as PropertyAccessExpression) // c
.expression; // d
}

// Prettier 3.8
function getClassNameFromPrototypeMethod(container) {
return (
(
container.left as PropertyAccessExpression // a
).expression as PropertyAccessExpression // b
).expression; // c // d
}

// Prettier 3.9
function getClassNameFromPrototypeMethod(container) {
return (
(
container // a
.left as PropertyAccessExpression
) // b
.expression as PropertyAccessExpression
) // c
.expression; // d
}

Fix inconsistent formatting of arrow function (#18589 by @fisker)

// Input
a.map(() => ({
name,
}));
a.map(():A => ({
name,
}));
a.map(():{A} => ({
name,
}));

// Prettier 3.8
a.map(() => ({
name,
}));
a.map(
(): A => ({
name,
}),
);
a.map((): { A } => ({
name,
}));

// Prettier 3.9
a.map(() => ({
name,
}));
a.map((): A => ({
name,
}));
a.map((): { A } => ({
name,
}));

Improve comment print inside type arguments (#18619 by @fisker)

// Input
foo<
// Comment
Type
>(
// Comment
value
)
foo<
Type // Comment
>(
)

// Prettier 3.8
foo<// Comment
Type>(
// Comment
value,
);
foo<Type>(); // Comment

// Prettier 3.9
foo<
// Comment
Type
>(
// Comment
value,
);
foo<
Type // Comment
>();

Fix conditional type comment indentation (#18644 by @lindsaycode05)

// Input
type T = any extends B
// Comment
// Multiline comment
? undefined | NonNullable<B>[foo]
: B[foo];

// Prettier 3.8
type T = any extends B
? // Comment
// Multiline comment
undefined | NonNullable<B>[foo]
: B[foo];

// Prettier 3.9
type T = any extends B
? // Comment
// Multiline comment
undefined | NonNullable<B>[foo]
: B[foo];

Fix inconsistent formatting of class superClass (#18652, #18659 by @fisker)

// Input
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz {}
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz! {}

// Prettier 3.8
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz {}
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName extends (foo
.bar.Baz!) {}

// Prettier 3.9
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz {}
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz! {}

Consistent print non-null assertions (#18654 by @fisker)

// Input
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset))!,
);
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset)),
);

// Prettier 3.8
const corners = baseCorners.map(
(corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset))!,
);
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset)),
);

// Prettier 3.9
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset))!,
);
const corners = baseCorners.map((corner) =>
curveCatmullRomCubicApproxPoints(curveOffsetPoints(corner, offset)),
);
// Input
isEqual(a?.map(([t, _]) => t?.id)!, b?.map(([t, _]) => t?.id));
isEqual(a?.map(([t, _]) => t?.id)!, b?.map(([t, _]) => t?.id)!);

// Prettier 3.8
isEqual(
a?.map(([t, _]) => t?.id)!,
b?.map(([t, _]) => t?.id),
);
isEqual(a?.map(([t, _]) => t?.id)!, b?.map(([t, _]) => t?.id)!);

// Prettier 3.9
isEqual(
a?.map(([t, _]) => t?.id)!,
b?.map(([t, _]) => t?.id),
);
isEqual(
a?.map(([t, _]) => t?.id)!,
b?.map(([t, _]) => t?.id)!,
);

Preserve non-null assertion around optional chaining (#18661 by @fisker)

// Input
(a?.b!).c;
(a?.b)!.c;
(a?.b!)!.c;

// Prettier 3.8
(a?.b)!.c;
(a?.b)!.c;
a?.b!!.c;

// Prettier 3.9
(a?.b!).c;
(a?.b)!.c;
(a?.b!)!.c;

Respect quoteProps option when printing enum keys (#18700, #18703 by @fisker)

// Input
enum E {
A1 = 0,
"A2" = 1,
"A-3" = 2,
}

// Prettier 3.8
enum E {
A1 = 0,
"A2" = 1,
"A-3" = 2,
}

// Prettier 3.9 (--quote-props=as-needed)
enum E {
A1 = 0,
A2 = 1,
"A-3" = 2,
}

// Prettier 3.9 (--quote-props=consistent)
enum E {
"A1" = 0,
"A2" = 1,
"A-3" = 2,
}

Respect quoteProps in method signature (#18702 by @fisker)

// Input
type T = {
method1(): string;
"method2"(): string;
"method-3"(): string;
};

// Prettier 3.8
type T = {
method1(): string;
"method2"(): string;
"method-3"(): string;
};

// Prettier 3.9 (--quote-props=as-needed)
type T = {
method1(): string;
method2(): string;
"method-3"(): string;
};

// Prettier 3.9 (--quote-props=consistent)
type T = {
"method1"(): string;
"method2"(): string;
"method-3"(): string;
};

Keep comments in mapped type inline (#18731 by @fisker)

// Input
type B = {
/* comment */ [b in B]: string
};

// Prettier 3.8
type B = {
/* comment */
[b in B]: string;
};

// Prettier 3.9
type B = {
/* comment */ [b in B]: string;
};

Print semicolon before type assertion in no-semi mode (#18738 by @fisker)

// Input
a;
<b>c;

// Prettier 3.8 (--no-semi, first format)
a
<b>c

// Prettier 3.8 (--no-semi, second format)
a < b > c

// Prettier 3.9 (--no-semi)
a
;<b>c

Add parentheses to conditional types in type parameter constraint (#18760 by @kovsu)

// Input
const foo = <Foo extends (Bar extends Baz ? A : B)>() => true;

// Prettier 3.8
const foo = <Foo extends Bar extends Baz ? A : B>() => true;

// Prettier 3.9
const foo = <Foo extends (Bar extends Baz ? A : B)>() => true;

Fix printing comment for the last operand of union types (#18798 by @fisker)

// Input
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) & Bar; // Final comment2
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) | Bar; // Final comment2

// Prettier 3.8
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) &
Bar; // Final comment2
type Foo2 =
| (
| "thing1" // Comment1
| "thing2"
) // Comment2
| Bar; // Final comment2

// Prettier 3.9
type Foo2 = (
| "thing1" // Comment1
| "thing2" // Comment2
) &
Bar; // Final comment2
type Foo2 =
| (
| "thing1" // Comment1
| "thing2" // Comment2
)
| Bar; // Final comment2

Don't break union type when it can fit (#18827 by @fisker)

// Input
type Browser = "chromium" | "webkit" | "firefox" | "chromium-msedge" | "chromium-chrome";

// Prettier 3.8
type Browser =
| "chromium"
| "webkit"
| "firefox"
| "chromium-msedge"
| "chromium-chrome";

// Prettier 3.9
type Browser =
"chromium" | "webkit" | "firefox" | "chromium-msedge" | "chromium-chrome";

Remove extra indention for union type in conditional type (#18827 by @fisker)

// Input
type A = T extends "arrow"
? ExcalidrawArrowElement["startBinding"] | ExcalidrawElbowArrowElement["startBinding"]
: never;

// Prettier 3.8
type A = T extends "arrow"
?
| ExcalidrawArrowElement["startBinding"]
| ExcalidrawElbowArrowElement["startBinding"]
: never;

// Prettier 3.9
type A = T extends "arrow"
? | ExcalidrawArrowElement["startBinding"]
| ExcalidrawElbowArrowElement["startBinding"]
: never;

Align JSDoc before union type elements (#18833 by @fisker)

// Input
type A =
|
/**
* Comment
*/
a
| (b | c);
type A2 =
|
// comment
a
| (b | c);

// Prettier 3.8
type A =
| /**
* Comment
*/
a
| (b | c);
type A2 =
| // comment
a
| (b | c);

// Prettier 3.9
type A =
| /**
* Comment
*/
a
| (b | c);
type A2 =
| // comment
a
| (b | c);

Prevent moving comment in class properties (#18837 by @fisker)

// Input
class foo {
bar () /* bar */;
baz /* baz */ ();
}

// Prettier 3.8
class foo {
bar /* bar */();
baz /* baz */();
}

// Prettier 3.9
class foo {
bar(); /* bar */
baz /* baz */();
}

Fix trailing comment in abstract method parameters (#19200 by @itsybitsybootsy)

// Input
abstract class Foo {
abstract method(
param1: number,
// param2: number,
): void;
}

// Prettier 3.8
abstract class Foo {
abstract method(param1: number) // param2: number,
: void;
}

// Prettier 3.9
abstract class Foo {
abstract method(
param1: number,
// param2: number,
): void;
}

Avoid unnecessary semicolon before call signatures in no-semi mode (#19212 by @fisker)

// Input
export interface MyInterface {
someMethod: (a: number) => Promise<number>
anotherMethod: (a: string) => Promise<string>
(a: string): Promise<string>
}

// Prettier 3.8 (--no-semi)
export interface MyInterface {
someMethod: (a: number) => Promise<number>
anotherMethod: (a: string) => Promise<string>;
(a: string): Promise<string>
}

// Prettier 3.9
export interface MyInterface {
someMethod: (a: number) => Promise<number>
anotherMethod: (a: string) => Promise<string>
(a: string): Promise<string>
}

Preserve comment position after = in type alias declarations (#19410 by @WhoamiI00)

// Input
type A =
/* 1 */ | /* 2 */ (
/* 3 */ | /* 4 */ {
key: string;
}
);

// Prettier 3.8
type A /* 2 */ = /* 1 */ /* 3 */ /* 4 */ {
key: string;
};

// Prettier 3.9
type A = /* 1 */ /* 2 */ /* 3 */ /* 4 */ {
key: string;
};

Fix missing parentheses around instantiation expression (#19442 by @fisker)

// Input
async function* f() {
makeRecord = (yield* makeRecordFactory)<ManualValue>;
makeRecord = (yield makeRecordFactory)<ManualValue>;
makeRecord = (await makeRecordFactory)<ManualValue>;
}

// Prettier 3.8
async function* f() {
makeRecord = yield* makeRecordFactory<ManualValue>;
makeRecord = yield makeRecordFactory<ManualValue>;
makeRecord = await makeRecordFactory<ManualValue>;
}

// Prettier 3.9
async function* f() {
makeRecord = (yield* makeRecordFactory)<ManualValue>;
makeRecord = (yield makeRecordFactory)<ManualValue>;
makeRecord = (await makeRecordFactory)<ManualValue>;
}

Flow

Fix parenthesis for optional function as return type (#11004, #19331 by @vjeux, @fisker)

// Input
const fn = (a: number): ?((string) => string) => {
return a > 0 ? (s) => `${s}: ${a}` : null;
};

// Prettier 3.8
const fn = (a: number): ?(string) => string => {
return a > 0 ? (s) => `${s}: ${a}` : null;
};

// Prettier 3.9
const fn = (a: number): ?((string) => string) => {
return a > 0 ? (s) => `${s}: ${a}` : null;
};

Add support for Flow match instance patterns and Flow records (#18511 by @gkz)

// Input
record R {
num: number,
}

const x = R {num: 1};

const label = match (x) {
R {num: 0} => "zero",
R {num: 1} => "one",
R {const num} => `${num} items`,
}

// Prettier 3.8
// Unsupported

// Prettier 3.9
// Same as input

Improve comment format in inexact tuples (#18616 by @fisker)

// Input
type A = [// comment
...];
type B = [/* comment */...];

// Prettier 3.8
type A = [...
// comment
];
type B = [...
/* comment */
];

// Prettier 3.9
type A = [
...// comment
];
type B = [.../* comment */];

Fix dangling comment print in components (#18629 by @fisker)

// Input
component A(
// comment
) {}
component B(/* comment */) {}

// Prettier 3.8
component A() // comment
{}
component B /* comment */() {}

// Prettier 3.9
component A(
// comment
) {}
component B(/* comment */) {}

Fix dangling comment print in hooks (#18630 by @fisker)

// Input
hook A(
// comment
) {}
declare hook B(// comment
): void
type C = hook (// comment
)=> void

// Prettier 3.8
hook A() // comment
{}
declare hook B(): // comment
void;
type C = hook () => // comment
void;

// Prettier 3.9
hook A(
// comment
) {}
declare hook B(
// comment
): void;
type C = hook (
// comment
) => void;

Support implicit declared functions and components (#18690 by @fisker)

// Input
declare module "foo" {
function greet(name: string): string;
component Button(label: string);
}

// Prettier 3.8
SyntaxError: Unexpected token `;`, expected the token `{` (2:39)

// Prettier 3.9
<Same as input>

Improve long mapped type to across multiple line (#18779 by @kovsu)

// Input
type MappedType = {
[KeyType in ValueType extends string ? VeryLongStringTypeNameHere : VeryLongNumberTypeNameHere]: KeyType
}

// Prettier 3.8
type MappedType = {
[KeyType in ValueType extends string
? VeryLongStringTypeNameHere
: VeryLongNumberTypeNameHere]: KeyType,
};

// Prettier 3.9
type MappedType = {
[
KeyType in ValueType extends string
? VeryLongStringTypeNameHere
: VeryLongNumberTypeNameHere
]: KeyType,
};

Add parentheses support for keyof type operator (#18801 by @marcoww6)

// Input
type T1 = (keyof Foo)[];
type T2 = (keyof Foo)["bar"];

// Prettier 3.8
type T1 = keyof Foo[];
type T2 = keyof Foo["bar"];

// Prettier 3.9
type T1 = (keyof Foo)[];
type T2 = (keyof Foo)["bar"];

Support literal initializers and multiple declarations in DeclareVariable (#18929 by @fisker)

// Input
declare const x: string, y: number;
declare const s = 'foo';

// Prettier 3.8
SyntaxError: Unexpected token `,`, expected the token `;` (1:24)

// Prettier 3.9
declare const x: string, y: number;
declare const s = "foo";

Print async keyword for component declarations (#19053 by @mvitousek)

// Input
async component MyAsyncComponent() {}

// Prettier 3.8
component MyAsyncComponent() {}

// Prettier 3.9
async component MyAsyncComponent() {}

Add support for writeonly, in, and out variance modifiers (#19102 by @marcoww6)

Support new Flow variance syntax: writeonly on object type properties/indexers, and in T/out T on type parameters.

// Input
type T = {writeonly foo: string};
type Contravariant<in T> = T;
type Covariant<out T> = T;

// Prettier 3.8
// SyntaxError

// Prettier 3.9
type T = { writeonly foo: string };
type Contravariant<in T> = T;
type Covariant<out T> = T;

Do not expand object type with inline comments (#19287 by @fisker)

// Input
type T = {|/* comment */|};

// Prettier 3.8
type T = {|
/* comment */
|};

// Prettier 3.9
type T = {| /* comment */ |};

Preserve Flow comment syntax (#19398 by @SamChou19815)

Preserves Flow comment syntax annotations instead of printing them as regular Flow syntax.

// Input
function foo<T>(bar /*: T[] */, baz /*: T */) /*: S */ {}

// Prettier 3.8
function foo<T>(bar: T[], baz: T): S {}

// Prettier 3.9
function foo<T>(bar /*: T[] */, baz /*: T */) /*: S */ {}

JSON

Preserve number and string representation in json-stringify parser (#18405 by @fisker)

Previous versions of Prettier json-stringify parser used JSON.stringify() to print numbers and strings. This led to the loss of the original value representation in a few rare cases. For example, extremely large or small numbers were rounded, and some special characters were unescaped. Technically, these transformations do not change how JSON values are read, but they are not something a formatter is concerned about. Prettier 3.9 now keeps the original representation, even if it can be simplified.

// Input
[
"\u00FF",
1e9999,
0.4e669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006,
-9223372036854775809,
1e3,
{
1e3: 1,
1e999999999999999999999999999999: 1,
1: 1
}
]

// Prettier 3.8
[
"ÿ",
null,
null,
-9223372036854776000,
1000,
{
"1000": 1,
"Infinity": 1,
"1": 1
}
]

// Prettier 3.9
[
"\u00FF",
1e9999,
0.4e669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006,
-9223372036854775809,
1e3,
{
1e3: 1,
1e999999999999999999999999999999: 1,
"1": 1
}
]

CSS

Fix line breaks when css attribute values contain literal newlines (#18605 by @kovsu)

/* Input */
div span[foo="a long long long long long long long long\
long long long long attritbute value"] {
}

/* Prettier 3.8 */
div
span[foo="a long long long long long long long long\
long long long long attritbute value"] {
}

/* Prettier 3.9 */
div span[foo="a long long long long long long long long\
long long long long attritbute value"] {
}

SCSS

Prevent adding trailing comma in math expression within function arguments (#18530 by @kovsu)

// Input
@include container($foo: 2 * ($bar + $baz));

// Prettier 3.8
@include container(
$foo: 2 *
(
$bar + $baz,
)
);

// Prettier 3.9
@include container($foo: 2 * ($bar + $baz));

Fix comment print in maps (#18535 by @kovsu)

// Input
$map: (
// comment
);

// Prettier 3.8
$map: (// comment);

// Prettier 3.9
$map: (
// comment
);

Avoid adding trailing comma to parenthesized scalars (#19091 by @cyphercodes)

// Input
.a { background-color: rgba($color: (#808080), $alpha: 0.9); }

// Prettier 3.8
.a {
background-color: rgba(
$color: (
#808080,
),
$alpha: 0.9
);
}

// Prettier 3.9
.a {
background-color: rgba($color: (#808080), $alpha: 0.9);
}

Remove space before ; delimiter in SCSS if() function (#19384 by @kovsu)

// Input
$value: if(sass($x): 1 ; else: 2);

// Prettier 3.8
$value: if(sass($x): 1 ; else: 2);

// Prettier 3.9
$value: if(sass($x): 1; else: 2);

HTML

Fix a bug around unicode in front matter (#18453 by @seiyab)

<!-- Input -->
---
title: 😄
---

<!-- comment -->

<!-- Prettier 3.8 -->
---
title: 😄
---


<!-- comment --

<!-- Prettier 3.9 -->
---
title: 😄
---

<!-- comment -->

Fix closing tag of pre-like elements being split across lines (#19046 by @kovsu)

<!-- Input -->
<div>
<pre>
Content
</pre>
</div>

<!-- Prettier 3.8 -->
<div>
<pre>
Content
</pre
>
</div>

<!-- Prettier 3.9 -->
<div>
<pre>
Content
</pre>
</div>

Keep an inline comment attached to the preceding node (#19304 by @kovsu)

<!-- Input -->
<p><b>x</b><!-- comment --></p>

<!-- Prettier 3.8 -->
<p>
<b>x</b
><!-- comment -->
</p>

<!-- Prettier 3.9 -->
<p><b>x</b><!-- comment --></p>

Angular

Support @content block (#19431 by @fisker)

<!-- Input -->
<FancyButton [label]="title">
@content(icon) {
<span>Icon</span>
}
@content(description) {
<span>Description text</span>
}
<span>Other children</span>
</FancyButton>

<!-- Prettier 3.8 -->
SyntaxError: Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.) (9:15)

<!-- Prettier 3.9 -->
<FancyButton [label]="title">
@content(icon) {
<span>Icon!</span>
}
@content(description) {
<span>Description text</span>
}
<span>Other children</span>
</FancyButton>

Support comments in html element (#19465 by @fisker)

<!-- Input -->
<div
// comment 0
/* comment 1 */
attr1="value1"
/*
comment 2
spanning multiple lines
*/
attr2="value2"
></div>

<!-- Prettier 3.8 -->
<div attr1="value1" attr2="value2"></div>

<!-- Prettier 3.9 -->
<div
// comment 0
/* comment 1 */
attr1="value1"
/*
comment 2
spanning multiple lines
*/
attr2="value2"
></div>

GraphQL

Fix crash with comment only object value (#18581 by @fisker)

# Input
query {
search(filters: {
# TODO
}) {
id
}
}

# Prettier 3.8
Error: Comment "TODO" was not printed. Please report this error!

# Prettier 3.9
query {
search(
filters: {
# TODO
}
) {
id
}
}

Allow interfaces to break (#18582 by @fisker)

# Input
type Foo implements InterfaceOne & InterfaceTwo & InterfaceThree & InterfaceFour & InterfaceFive & InterfaceSix {
id: ID!
}

# Prettier 3.8
type Foo implements InterfaceOne & InterfaceTwo & InterfaceThree & InterfaceFour & InterfaceFive & InterfaceSix {
id: ID!
}

# Prettier 3.9
type Foo implements InterfaceOne &
InterfaceTwo &
InterfaceThree &
InterfaceFour &
InterfaceFive &
InterfaceSix {
id: ID!
}

Fix crash with comment only list value (#19422 by @fisker)

# Input
query {
a(x: [
# only comment
])
}

# Prettier 3.8
Error: Comment "only comment" was not printed. Please report this error!

# Prettier 3.9
query {
a(
x: [
# only comment
]
)
}

Markdown

Fix Markdown emphasis near non-breaking space (#17424 by @rkdfx)

Underscore-style emphasis adjacent to a Unicode whitespace (e.g. non-breaking space) was being rewritten to asterisks, because the surrounding text was treated as a regular word.

<!-- Input -->
- _REPORTED_ — message received;
- _NEEDSINFO_ — additional info needed;

<!-- Prettier 3.8 -->
- *REPORTED* — message received;
- *NEEDSINFO* — additional info needed;

<!-- Prettier 3.9 -->
- _REPORTED_ — message received;
- _NEEDSINFO_ — additional info needed;

Support setext headings (#18473 by @fisker)

<!-- Input -->
Setext Heading 1
================

Setext Heading 2
----------------

Multiline
Setext
Heading
-------

<!-- Prettier 3.8 -->
# Setext Heading 1

## Setext Heading 2

Multiline
Setext
Heading

---

<!-- Prettier 3.9 -->
Setext Heading 1
================

Setext Heading 2
----------------

Multiline
Setext
Heading
-------

Treat U+3000 as CJK punctuation-equivalent and make sure to recognize U+FF5E as CJK punctuation again in Markdown prose wrap (#18656 by @tats-u)

We improved the text wrapping in Markdown written in Chinese and Japanese by treating some non Unicode punctuation characters as equivalent to CJK punctuation. A part of this was assumed to have been introduced in 3.5.0 (#16832), but it was not fully implemented and not noticed until now due to insufficient test cases.

await prettier.format(
"U+3000\u{3000}\nU+301C\u{301C}\nU+FF5E\u{FF5E}\nU+1F221\u{1F221} 测试 Test テスト\n",
{ parser: "markdown", proseWrap: "always" },
);

/* Prettier 3.8 */
// -> 'U+3000  U+301C〜U+FF5E~ U+1F221🈡 测试 Test テスト\n'
//  ^ extra space  ^ extra space

/* Prettier 3.9 */
// -> 'U+3000 U+301C〜U+FF5E~U+1F221🈡 测试 Test テスト\n'

Fix CJK line break loss near list-like numbers (#18867 by @it-education-md)

Avoid collapsing line breaks before list-like numbers followed by CJK characters (e.g. 3.中).

<!-- Input -->
1.a
2.b
3.中

<!-- Prettier 3.8 -->
1.a
2.b 3.中

<!-- Prettier 3.9 -->
1.a
2.b
3.中

Fix extra blank line between a list and an indented code block (#19154 by @YBJ0000)

Prettier forced two blank lines whenever an indented code block followed a list. The extra blank line is never semantically required and contradicts Prettier's usual behavior of collapsing consecutive blank lines into a single one.

<!-- Input -->
- one

two

<!-- Prettier 3.8 -->
- one


two

<!-- Prettier 3.9 -->
- one

two

Support formatting LWC (Lightning Web Components) syntax in code block (#19291 by @fisker)

<!-- Input -->
```lwc
<test-this attr={test}>
Awesome </test-this>
```

<!-- Prettier 3.8 -->
Same as input

<!-- Prettier 3.9 -->
```lwc
<test-this attr={test}> Awesome </test-this>
```

Escape unindented indented pseudo setext headers (#19350 by @tats-u)

Indented lines composed of only = or - were previously unindented without being escaped, which caused them to be mistakenly parsed as setext headers. With this change, Prettier now escapes such lines to prevent the misinterpretation:

Check parse results on CommonMark playground

<!-- Input -->
Not a header
===
Not a header
---
Not a list item or a header
-

<!-- Prettier 3.8 -->
Not a header
===
Not a header
---
Not a list item or a header -

<!-- Prettier 3.9 -->
Not a header
\===
Not a header
\---
Not a list item or a header
\-

YAML

Fix mapping format when the key wraps (#18330 by @kovsu)

# Input
long long long long long long long long long long long long long long long long long long key: bar

# Prettier 3.8 (--prose-wrap=always)
long long long long long long long long long long long long long long long long
long long key: bar

# Prettier 3.9 (--prose-wrap=always)
? long long long long long long long long long long long long long long long
long long long key
: bar

Fix block value ends with whitespace (#18331 by @kovsu)

import { inspect } from "node:util";
import * as prettier from "prettier";

const input = "foo: |\n x\n ";
const output = await prettier.format(input, { parser: "yaml" });
console.log(inspect(output));

// Prettier 3.8
//-> 'foo: |\n x\n\n'

// Prettier 3.9
//-> 'foo: |\n x\n'

Preserve empty line after block scalar (#19205 by @seirl)

# Input
foo: >
a
b
bar: baz

qux: quux

# Prettier 3.8
foo: >
a
b
bar: baz
qux: quux

# Prettier 3.9
foo: >
a
b
bar: baz

qux: quux

CLI

Fix crashes on formatting directory names that have special characters (#18452 by @fisker)

prettier . --check

# Prettier 3.8
[error] Invalid configuration for file "<CWD>\test\username[repo-name]\.editorconfig":
[error] Invalid regular expression: /^(?=.)username[repo-name]$/: Range out of order in character class

# Prettier 3.9
Checking formatting...
[warn] test/username[repo-name]/test.js
[warn] Code style issues found in the above file. Run Prettier with --write to fix.

Stop searching for EditorConfig files above Git worktrees (#18891 by @seiyab)

Prettier now treats a .git file as a project root marker, in addition to a .git directory. This prevents EditorConfig settings from a parent directory from being applied when formatting files inside Git worktrees or submodules.

Fix --cache-strategy content not working (#18914 by @paulofduarte)

file-entry-cache v11 renamed the useChecksum option to useCheckSum. Prettier was still passing the old casing, so content-based cache comparison was silently disabled and only file size was compared.

Fix formatting file with a leading quote (") in the name (#19315 by @kovsu)

prettier --check '".md'

# Prettier 3.8
Checking formatting...
[error] Explicitly specified file was ignored due to negative glob patterns: "".md".

# Prettier 3.9
Checking formatting...
[warn] ".md
[warn] Code style issues found in the above file. Run Prettier with --write to fix.

Update experimental cli (#19381, #19415 by @Simomson, @43081j, @fisker)

Full changelog:

Miscellaneous

Update Printer interface (#19014 by @Janther)

export interface Printer<T = any> {
// ...
- print: (AstPath<T>) => Doc,
+ print: (
+ selector?: string | number | Array<string | number> | AstPath<T>,
+ args?: unknown,
+ ) => Doc,
// ...
}