website/blog/2026-06-27-3.9.0.md
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.
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 to v2 (#18419 by @ota-meshi, @fisker) {#change-18419}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.
Prettier now fully supports newer syntax features from GraphQL.js v17, including directives on directive definitions, fragment arguments, and other enhancements.
<!-- prettier-ignore --># 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
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.
break and continue in no-semi mode (#7161 by @thorn0, @fisker) {#change-7161}// 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();
}
}
// 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
);
}
// 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" : ""
};
}
`;
// 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>
`;
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
// Input
css`foo:${bar} {}`
// Prettier 3.8
css`
foo: ${bar} {
}
`;
// Prettier 3.9
css`
foo:${bar} {
}
`;
// 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);
// 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 */);
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
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.
// 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 */);
// 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 */);
// 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 =;
// 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 */
};
// Input
const array = [/* comment */]
const object =
// Prettier 3.8
const array = [
/* comment */
];
const object = {
/* comment */
};
// Prettier 3.9
const array = [/* comment */];
const object =;
// Input
fn = function(
// comment
) {}
arrow = (
// comment
) => {}
// Prettier 3.8
fn = function () // comment
{};
arrow = () =>
// comment
{};
// Prettier 3.9
fn = function (
// comment
) {};
arrow = (
// comment
) => {};
NewExpression and CallExpression (#18669 by @fisker) {#change-18669}// 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,
);
// Input
(a?.b()).c();
// Prettier 3.8
a?.b().c();
// Prettier 3.9
(a?.b()).c();
// Input
let i ="format me!" ;
// ^^^^^^^^^^^^^^ Range
// Prettier 3.8
let i = "format me!"; ;
// Prettier 3.9
let i = "format me!";
// 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();
; in no-semi mode (#18736, #18737 by @fisker) {#change-18736}// Input
a
;[].b()
// Prettier 3.8 (--no-semi)
a
;[].b()
// Prettier 3.9
a
;[].b()
// 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)
// 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;
else (#18813 by @fisker) {#change-18813}// 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) {
}
// Input
debugger // Comment
;
// Prettier 3.8
Error: Comment "comment" was not printed. Please report this error!
// Prettier 3.9
debugger; // Comment
do..while print in no-semi mode (#18851 by @fisker) {#change-18851}// 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)
experimentalTernaries (#18963 by @kovsu) {#change-18963}// 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!
]
);
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.
<!-- prettier-ignore -->// 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}
/>;
// Input
const foo = /**
* comment
*/ bar;
// Prettier 3.8
const foo = /**
* comment
*/ bar;
// Prettier 3.9
const foo =
/**
* comment
*/ bar;
for statement without "update" expression (#19188 by @Poliklot) {#change-19188}// Input
for (initialize;;) {}
for (; condition;) {}
// Prettier 3.8
for (initialize; ; ) {}
for (; condition; ) {}
// Prettier 3.9
for (initialize; ;) {}
for (; condition;) {}
// 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 */);
// Input
const fn = () => (a = b /* abc */);
// Prettier 3.8
const fn = () => (a = b) /* abc */;
// Prettier 3.9
const fn = () => (a = b /* abc */);
// 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 */);
// Input
const P = {/** X */
Y: "z",
}
// Prettier 3.8
const P = {
/** X */ Y: "z",
};
// Prettier 3.9
const P = {
/** X */
Y: "z",
};
// 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,
]);
// 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,
});
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
}
// 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,
}));
// 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
>();
// 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];
// 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! {}
// 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)!,
);
// 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;
quoteProps option when printing enum keys (#18700, #18703 by @fisker) {#change-18700}// 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,
}
quoteProps in method signature (#18702 by @fisker) {#change-18702}// 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;
};
// 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;
};
no-semi mode (#18738 by @fisker) {#change-18738}// 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
// 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;
// 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
// 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";
// 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;
// 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);
// 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 */();
}
// 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;
}
no-semi mode (#19212 by @fisker) {#change-19212}// 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>
}
= in type alias declarations (#19410 by @WhoamiI00) {#change-19410}// 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;
};
// 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>;
}
// 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;
};
// 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
// 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 */];
// 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 */) {}
// 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;
// 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>
// 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,
};
keyof type operator (#18801 by @marcoww6) {#change-18801}// 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"];
DeclareVariable (#18929 by @fisker) {#change-18929}// 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";
async keyword for component declarations (#19053 by @mvitousek) {#change-19053}// Input
async component MyAsyncComponent() {}
// Prettier 3.8
component MyAsyncComponent() {}
// Prettier 3.9
async component MyAsyncComponent() {}
writeonly, in, and out variance modifiers (#19102 by @marcoww6) {#change-19102}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;
// Input
type T = {|/* comment */|};
// Prettier 3.8
type T = {|
/* comment */
|};
// Prettier 3.9
type T = {| /* comment */ |};
Preserves Flow comment syntax annotations instead of printing them as regular Flow syntax.
<!-- prettier-ignore -->// 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-stringify parser (#18405 by @fisker) {#change-18405}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
}
]
/* 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"] {
}
// 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));
// Input
$map: (
// comment
);
// Prettier 3.8
$map: (// comment);
// Prettier 3.9
$map: (
// comment
);
// 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);
}
; delimiter in SCSS if() function (#19384 by @kovsu) {#change-19384}// 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);
<!-- Input -->
---
title: 😄
---
<!-- comment -->
<!-- Prettier 3.8 -->
---
title: 😄
---
<!-- comment --
<!-- Prettier 3.9 -->
---
title: 😄
---
<!-- comment -->
<!-- Input -->
<div>
<pre>
Content
</pre>
</div>
<!-- Prettier 3.8 -->
<div>
<pre>
Content
</pre
>
</div>
<!-- Prettier 3.9 -->
<div>
<pre>
Content
</pre>
</div>
<!-- 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>
@content block (#19431 by @fisker) {#change-19431}<!-- 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>
<!-- 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>
# 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
}
}
# 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!
}
# 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
]
)
}
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.
<!-- prettier-ignore --><!-- 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;
<!-- 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
-------
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'
Avoid collapsing line breaks before list-like numbers followed by CJK characters (e.g. 3.中).
<!-- prettier-ignore --><!-- Input -->
1.a
2.b
3.中
<!-- Prettier 3.8 -->
1.a
2.b 3.中
<!-- Prettier 3.9 -->
1.a
2.b
3.中
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.
<!-- prettier-ignore --><!-- Input -->
- one
two
<!-- Prettier 3.8 -->
- one
two
<!-- Prettier 3.9 -->
- one
two
<!-- 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>
```
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
<!-- prettier-ignore --><!-- 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
\-
# 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
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'
# 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
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.
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.
--cache-strategy content not working (#18914 by @paulofduarte) {#change-18914}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.
") in the name (#19315 by @kovsu) {#change-19315}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.
Full changelog:
Printer interface (#19014 by @Janther) {#change-19014}export interface Printer<T = any> {
// ...
- print: (AstPath<T>) => Doc,
+ print: (
+ selector?: string | number | Array<string | number> | AstPath<T>,
+ args?: unknown,
+ ) => Doc,
// ...
}