website/blog/2025-11-27-3.7.0.md
We are excited to announce Prettier 3.7! This release focuses on polishing the TypeScript and Flow experience, specifically by aligning the formatting of classes and interfaces to be more consistent and predictable. We also want your opinion on the upcoming change to fix inconsistent opening brace print logic of class and interface body.
Additionally, we also fixed lots of bugs, added support for new features in Angular 21 and Graphql 16.12, added Front Matter support to Handlebars.
For plugin developers, we've added new APIs to give you more control over comment attachment and handling of ignored nodes.
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. Thank you for your continued support!
<!-- truncate -->In this release, we've focused heavily on improving consistency between Class and Interface formatting. Previously, these two similar constructs were printed quite differently, leading to visual inconsistencies. We've aligned their formatting rules to provide a more predictable and cleaner output.
// Input
interface MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
extends A, B {}
declare class MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
implements A, B {}
// Prettier 3.6
interface MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
> extends A,
B {}
declare class MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
implements A, B {}
// Prettier 3.7
interface MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
extends A, B {}
declare class MarkDef<
M extends string | Mark = Mark,
ES extends ExprRef | SignalRef = ExprRef | SignalRef,
>
implements A, B {}
// Input
export interface AreaConfig<ES extends ExprRef | SignalRef>
extends MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}
export class AreaConfig<ES extends ExprRef | SignalRef>
implements MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}
// Prettier 3.6
export interface AreaConfig<ES extends ExprRef | SignalRef>
extends MarkConfig<ES>,
PointOverlayMixins<ES>,
LineOverlayMixins<ES> {}
export class AreaConfig<ES extends ExprRef | SignalRef>
implements MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}
// Prettier 3.7
export interface AreaConfig<ES extends ExprRef | SignalRef>
extends MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}
export class AreaConfig<ES extends ExprRef | SignalRef>
implements MarkConfig<ES>, PointOverlayMixins<ES>, LineOverlayMixins<ES> {}
// Input
class ExtendsLongOneWithGenerics
extends
Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine,
> {}
class ExtendsLongOneWithGenerics
implements
Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine,
> {}
interface ExtendsLongOneWithGenerics
extends
Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine,
> {}
// Prettier 3.6
class ExtendsLongOneWithGenerics extends Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
class ExtendsLongOneWithGenerics
implements
Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
interface ExtendsLongOneWithGenerics
extends Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
// Prettier 3.7
class ExtendsLongOneWithGenerics extends Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
class ExtendsLongOneWithGenerics implements Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
interface ExtendsLongOneWithGenerics extends Bar<
SomeLongTypeSomeLongTypeSomeLongTypeSomeLongType,
ToBreakLineToBreakLineToBreakLine
> {}
In Prettier v2.3, to improve visual separation between class head and body, we started to print the opening { of the class body on a new line when the class has multiple heritages.
// Prettier 2.2
class loooooooooooooooooooong
extends looooooooooooooooooong
implements loooooooooooooooooooong {
property: string;
}
// Prettier 2.3
class loooooooooooooooooooong
extends looooooooooooooooooong
implements loooooooooooooooooooong
{
property: string;
}
However, not everyone is happy with this change.
Let us know what you think about applying this change to interfaces by leaving a comment on this issue.
If you have a better solution for this issue, we'll be happy to discuss it too.
Unless a better solution comes along, we'll align the interface body print with the one for the class body in Prettier v4.
<!-- prettier-ignore -->// Input
declare class loooooooooooooooooooong
implements looooooooooooooooooong, loooooooooooooooooooong {
property: string;
}
interface loooooooooooooooooooong
extends looooooooooooooooooong, loooooooooooooooooooong {
property: string;
}
// Prettier 3.7
declare class loooooooooooooooooooong
implements looooooooooooooooooong, loooooooooooooooooooong
{
property: string;
}
interface loooooooooooooooooooong
extends looooooooooooooooooong, loooooooooooooooooooong { // <-- This
property: string;
}
These changes also affect Flow syntax
// Input
import syntaxImportAssertions from "@babel/plugin-syntax-import-assertions" with {
BABEL_8_BREAKING: "false",
USE_ESM: "true", IS_STANDALONE: "false" };
// Prettier 3.6
import syntaxImportAssertions from "@babel/plugin-syntax-import-assertions" with { BABEL_8_BREAKING: "false", USE_ESM: "true", IS_STANDALONE: "false" };
// Prettier 3.7
import syntaxImportAssertions from "@babel/plugin-syntax-import-assertions" with {
BABEL_8_BREAKING: "false",
USE_ESM: "true",
IS_STANDALONE: "false",
};
The Stage 2 proposal "Discard Bindings" is now supported via Babel. Also keep in mind our policy on non-standardized syntax before using this proposed syntax feature with Prettier.
<!-- prettier-ignore -->const [void] = x;
const {x:void} = x;
// Input
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo
) {
doThings();
}
// Prettier 3.6 (--parser=typescript --experimental-operator-position=start)
if (
true
&& // This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
flibble.blibble.blobble?.bloo
) {
doThings();
}
// Prettier 3.6 (--parser=babel --experimental-operator-position=start)
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo
) {
doThings();
}
// Prettier 3.7
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo
) {
doThings();
}
Prettier already avoids changing indentation for test functions when you add .skip to
them. It now also treats the Playwright functions test.fixme, test.describe.skip and test.describe.fixme similar to test.skip.
// Input
test.fixme("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});
test.describe.skip("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});
test.describe.fixme("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});
// Prettier 3.6
test.fixme(
"does something really long and complicated so I have to write a very long name for the test",
() => {
// code
},
);
test.describe.skip(
"does something really long and complicated so I have to write a very long name for the test",
() => {
// code
},
);
test.describe.fixme(
"does something really long and complicated so I have to write a very long name for the test",
() => {
// code
},
);
// Prettier 3.7
test.fixme("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});
test.describe
.skip("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});
test.describe
.fixme("does something really long and complicated so I have to write a very long name for the test", () => {
// code
});
{import,require.resolve,require.resolve.paths,import.meta.resolve}() with long module name (#17882, #17908 by @kovsu & @fisker) {#change-17882}// Input
const a = require("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const b = require.resolve("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const c = require.resolve.paths("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const d = await import("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module")
const e = import.meta.resolve("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
// Prettier 3.6
const a = require("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const b = require.resolve(
"./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module",
);
const c = require.resolve.paths(
"./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module",
);
const d = await import(
"./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module"
);
const e = import.meta.resolve(
"./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module",
);
// Prettier 3.7
const a = require("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const b =
require.resolve("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const c = require.resolve
.paths("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const d =
await import("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
const e = import.meta
.resolve("./a/long/long/long/long/long/long/long/long/long/long/long/path/to/module");
// Input
if (foo) // comment
{
doThing();
} else // comment for else
{
doSomethingElse();
}
// Prettier 3.6
if (foo) {
// comment
doThing();
} // comment for else
else {
doSomethingElse();
}
// Prettier 3.7
if (foo) // comment
{
doThing();
} else // comment for else
{
doSomethingElse();
}
require() call with comments (#18037 by @fisker) {#change-18037}// Input
require(
// Comment
"foo"
);
// Prettier 3.6
require(// Comment
"foo");
// Prettier 3.7
require(
// Comment
"foo",
);
Boolean() call (#18087 by @kovsu) {#change-18087}Reduce diff when changing a condition to an opposite value, or change between !! and Boolean().
// Input
const foo = Boolean(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);
const bar = !!(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);
// Prettier 3.6
const foo = Boolean(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition,
);
const bar = !!(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);
// Prettier 3.7
const foo = Boolean(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);
const bar = !!(
a_long_long_condition ||
a_long_long_long_condition ||
a_long_long_long_condition
);
// Input
for (x of y)
// Comment
bar();
// Prettier 3.6
// Comment
for (x of y) bar();
// Prettier 3.7
for (x of y)
// Comment
bar();
// Input
for (
index = 0;
doSomething(foo[index]) !== bar && doSomething(foo[index]) !== baz;
index ++
) /* No op */;
// Prettier 3.6
for (
index = 0;
doSomething(foo[index]) !== bar && doSomething(foo[index]) !== baz;
index++ /* No op */
);
// Prettier 3.7
for (
index = 0;
doSomething(foo[index]) !== bar && doSomething(foo[index]) !== baz;
index++
) /* No op */ ;
// Input
class x {
method() // Class method
{
return 1
}
}
const object = {
method() // Object method
{
return 1
}
}
// Prettier 3.6
class x {
method() { // Class method
return 1;
}
}
const object = {
method() {
// Object method
return 1;
},
};
// Prettier 3.7
class x {
method() {
// class method
return 1;
}
}
const object = {
method() {
// object method
return 1;
},
};
// Input
1 << (bit % 8);
1 >> (bit - 8);
// Prettier 3.6
1 << bit % 8;
1 >> (bit - 8);
// Prettier 3.7
1 << (bit % 8);
1 >> (bit - 8);
// Input
assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides___(-1, -1), [1]);
assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides(-1, -1), [1, 2]);
// Prettier 3.6
assert.deepStrictEqual(linesCollection.getViewLinesIndentGuides___(-1, -1), [
1,
]);
assert.deepStrictEqual(
linesCollection.getViewLinesIndentGuides(-1, -1),
[1, 2],
);
// Prettier 3.7
assert.deepStrictEqual(
linesCollection.getViewLinesIndentGuides___(-1, -1),
[1],
);
assert.deepStrictEqual(
linesCollection.getViewLinesIndentGuides(-1, -1),
[1, 2],
);
// Input
fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b
);
new Fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b
);
// Prettier 3.6
fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b,
);
new Fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b,
);
// Prettier 3.7
fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b,
);
new Fn(
a &&
a_long_long_long_long_long_long_long_long_long_long_long_long_long_condition
? a
: b,
);
CallExpression and NewExpression (#18206 by @fisker) {#change-18206}// Input
TelemetryTrustedValue(
instance.capabilities.get(
TerminalCapability?.PromptTypeDetection
)?.promptType
)
new TelemetryTrustedValue(
instance.capabilities.get(
TerminalCapability?.PromptTypeDetection
)?.promptType
)
// Prettier 3.6
TelemetryTrustedValue(
instance.capabilities.get(TerminalCapability?.PromptTypeDetection)
?.promptType,
);
new TelemetryTrustedValue(
instance.capabilities.get(
TerminalCapability?.PromptTypeDetection,
)?.promptType,
);
// Prettier 3.7
TelemetryTrustedValue(
instance.capabilities.get(TerminalCapability?.PromptTypeDetection)
?.promptType,
);
new TelemetryTrustedValue(
instance.capabilities.get(TerminalCapability?.PromptTypeDetection)
?.promptType,
)
// Input
new A(
<div>
<div></div>
</div>
)
// Prettier 3.6
new A(
(
<div>
<div></div>
</div>
),
);
// Prettier 3.7
new A(
<div>
<div></div>
</div>,
);
// Input
a = new (
a_long_long_long_long_condition || a_long_long_long_long_condition || a_long_long_long_long_condition
)();
// Prettier 3.6
a = new (a_long_long_long_long_condition ||
a_long_long_long_long_condition ||
a_long_long_long_long_condition)();
// Prettier 3.7
a = new (
a_long_long_long_long_condition ||
a_long_long_long_long_condition ||
a_long_long_long_long_condition
)();
// Input
for ( let i = 0, j = 0, len = allMatches.length, lenJ = selections.length;i < len;) {}
// Prettier 3.6
for (
let i = 0, j = 0, len = allMatches.length, lenJ = selections.length;
i < len;
) {}
// Prettier 3.7
for (
let i = 0, j = 0, len = allMatches.length, lenJ = selections.length;
i < len;
) {}
// Input
class EnsureNoDisposablesAreLeakedInTestSuiteSuite extends eslint.Rule.RuleModule {};
// Prettier 3.6
class EnsureNoDisposablesAreLeakedInTestSuiteSuite extends eslint.Rule
.RuleModule {}
// Prettier 3.7
class EnsureNoDisposablesAreLeakedInTestSuiteSuite
extends eslint.Rule.RuleModule {}
// Input
export const test = (): any => /* first line
second line
*/
null;
// Prettier 3.6
export const test = (): any /* first line
second line
*/ => null;
// Prettier 3.6 (Second format)
SyntaxError: Unexpected token (1:22)
> 1 | export const test = (): any /* first line
| ^
2 | second line
3 | */ => null;
4 |
// Prettier 3.7
export const test = (): any =>
/* first line
second line
*/
null;
// Input
void (<_T extends never>() => {})<never>;
// Prettier 3.6
void <_T extends never>() => {}<never>;
// Prettier 3.7
void (<_T extends never>() => {})<never>;
TSMappedType format (#17785 by @fisker) {#change-17785}// Input (--parser=babel-ts)
export type A = B extends { C?: { [D in infer E]?: F } } ? G : H
// Prettier 3.6
TypeError: Cannot read properties of undefined (reading 'startsWith')
// Prettier 3.7
export type A = B extends { C?: { [D in infer E]?: F } } ? G : H;
TSImportType options (#17798 by @fisker) {#change-17798}Trailing comma in import type attribute wasn't allowed before TypeScript v5.9, now the bug has been fixed.
<!-- prettier-ignore -->// Input
type A = import("foo", {
with:{
type:'json',
} // <- Should be a comma here
})
// Prettier 3.6
type A = import("foo", {
with: {
type: "json",
} // <- Should be a comma here
});
// Prettier 3.7
type A = import("foo", {
with: {
type: "json",
}, // <- Should be a comma here
});
require() with comments (#18035 by @fisker) {#change-18035}// Input
import foo = require(
// Comment
"foo"
);
// Prettier 3.6
import foo = require(// Comment
"foo");
// Prettier 3.7
import foo = require(
// Comment
"foo"
);
= in type parameters (#18043 by @fisker) {#change-18043}// Input
export type OuterType2<
LongerLongerLongerLongerInnerType = LongerLongerLongerLongerLongerLongerLongerLongerOtherType
> = { a: 1 };
// Prettier 3.6
export type OuterType2<
LongerLongerLongerLongerInnerType = LongerLongerLongerLongerLongerLongerLongerLongerOtherType,
> = { a: 1 };
// Prettier 3.7
export type OuterType2<
LongerLongerLongerLongerInnerType =
LongerLongerLongerLongerLongerLongerLongerLongerOtherType,
> = { a: 1 };
// Input
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
Fooo1000 | Baz2000 | BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
type A = // comment
Fooo1000 | Baz2000 | BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
// Prettier 3.6
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
type A = // comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
// Prettier 3.7
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
type A = // comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
This change also affect Flow syntax
typescript and flow parser (#18110 by @fisker) {#change-18110}// Input
interface A {
a: // Comment
B;
b: // Comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
c: // Comment
& Fooo1000
& Baz2000
& BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
}
// Prettier 3.6 (--parser=typescript)
interface A {
a: // Comment
B;
b: // Comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
c: // Comment
Fooo1000 &
Baz2000 &
BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
}
// Prettier 3.6 (--parser=flow)
interface A {
a: B; // Comment
b: // Comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
c: Fooo1000 & // Comment
Baz2000 &
BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
}
// Prettier 3.7 (Same output for `--parser=typescript` and `--parser=flow`)
interface A {
a: B; // Comment
b: // Comment
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
c: // Comment
Fooo1000 &
Baz2000 &
BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
}
// Input
interface A {
foo;
<T>(): T;
}
type B = {
foo;
<T>(): T;
}
// Prettier 3.6 (--no-semi, first format)
interface A {
foo
<T>(): T
}
type B = {
foo
<T>(): T
}
// Prettier 3.6 (--no-semi, second format)
interface A {
foo<T>(): T
}
type B = {
foo<T>(): T
}
// Prettier 3.7
interface A {
foo;
<T>(): T
}
type B = {
foo;
<T>(): T
}
as const between flow and typescript parsers (#18161 by @fisker) {#change-18161}// Input
1 as /* comment */ const;
// Prettier 3.6 (--parser=typescript)
1 as /* comment */ const;
// Prettier 3.6 (--parser=flow)
1 /* comment */ as const;
// Prettier 3.7 (Same output for `--parser=typescript` and `--parser=flow`)
1 /* comment */ as const;
as/satisfies expression (#18162 by @fisker) {#change-18162}// Input
1 as /*
comment
*/
const;
// Prettier 3.6 (First format)
1 /*
comment
*/ as const;
// Prettier 3.6 (Second format)
SyntaxError: Unexpected keyword or identifier. (3:4)
1 | 1 /*
2 | comment
> 3 | */ as const;
| ^
4 |
// Prettier 3.7
1 as const /*
comment
*/;
// Input
interface I {
elements: // comment
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression]
}
// Prettier 3.6
interface I {
elements: // comment
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression];
}
// Prettier 3.7
interface I {
elements: // comment
| [string, ExpressionNode, ExpressionNode]
| [string, ExpressionNode, ExpressionNode, ObjectExpression];
}
// Input
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
Fooo1000 | Baz2000 | BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
// Prettier 3.6
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
// Prettier 3.7
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
// Input
const e = match (a) {
1 => true,
'foo' => false,
2 => {obj: 'literal'},
};
// Prettier 3.6
SyntaxError: Unexpected token `{`, expected the token `;` (1:21)
> 1 | const e = match (a) {
| ^
2 | 1 => true,
3 | 'foo' => false,
4 | 2 => {obj: 'literal'},
// Prettier 3.7
const e = match (a) {
1 => true,
"foo" => false,
2 => { obj: "literal" },
};
// Input
opaque type Counter super empty extends Box<T> = Container<T>;
opaque type Counter super Box<T> = Container<T>;
declare opaque type Counter super empty extends Box<T>;
declare opaque type Counter super Box<T>;
// Prettier 3.6
SyntaxError: Unexpected identifier, expected the token `=` (1:21)
// Prettier 3.7
opaque type Counter super empty extends Box<T> = Container<T>;
opaque type Counter super Box<T> = Container<T>;
declare opaque type Counter super empty extends Box<T>;
declare opaque type Counter super Box<T>;
/* Input */
[type=a s],
[type=a S],
[type=a I] {
list-style-type: lower-alpha;
}
/* Prettier 3.6 */
[type="a s"],
[type="a S"],
[type="a I"] {
list-style-type: lower-alpha;
}
/* Prettier 3.7 */
[type="a" s],
[type="a" S],
[type="a" I] {
list-style-type: lower-alpha;
}
/* Input */
:root {
--l: , #000;
}
/* Prettier 3.6 */
TypeError: Cannot read properties of undefined (reading 'value')
/* Prettier 3.7 */
:root {
--l: , #000;
}
/* Input */
:export {
nest: {
myColor: blue;
}
myColor: red;
}
/* Prettier 3.6 */
:export {
nest: {
mycolor: blue;
}
myColor: red;
}
/* Prettier 3.7 */
:export {
nest: {
myColor: blue;
}
myColor: red;
}
// (#17938 by @kovsu) {#change-17938}/* Input */
a[href="http://example.com"] {
color: red;
}
/* Prettier 3.6 */
a[href="http://example.com"]
{
color: red;
}
/* Prettier 3.7 */
a[href="http://example.com"] {
color: red;
}
/* Input */
a {
font: var(--size)/1;
}
/* Prettier 3.6 */
a {
font: var(--size) / 1;
}
/* Prettier 3.7 */
a {
font: var(--size)/1;
}
/* Input */
.foo {
background-image:
linear-gradient(to top, blue, red 100%),
/* texture */
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px),
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px);
}
/* Prettier 3.6 */
.foo {
background-image:
linear-gradient(to top, blue, red 100%),
/* texture */
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px),
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px);
}
/* Prettier 3.7 */
.foo {
background-image:
linear-gradient(to top, blue, red 100%),
/* texture */
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px),
repeating-linear-gradient(90deg, pink, yellow 20px, green 20px, pink 40px);
}
// Input
.foo {
@include bar('A (B)');
}
// Prettier 3.6
.foo {
@include bar('A( B)');
}
// Prettier 3.7
.foo {
@include bar('A (B)');
}
// Input
.foo {
@include transition(min-height ($spacer/2) ease-in-out);
}
// Prettier 3.6
.foo {
@include transition(min-height($spacer/2) ease-in-out);
}
// Prettier 3.7
.foo {
@include transition(min-height ($spacer/2) ease-in-out);
}
// Input
@fooBackground:line-gradient(#f00);
a {
background: @fooBackground;
}
// Prettier 3.6
@foobackground:line-gradient (#f00);
a {
background: @fooBackground;
}
// Prettier 3.7
@fooBackground: line-gradient(#f00);
a {
background: @fooBackground;
}
// Input
.average(@x, @y) {
@result: ((@x + @y) / 2);
}
div {
padding: .average(16px, 50px) [ @result ];
}
// Prettier 3.6
.average(@x, @y) {
@result: ((@x + @y) / 2);
}
div {
padding: .average(16px, 50px) [ @result];
}
// Prettier 3.7
.average(@x, @y) {
@result: ((@x + @y) / 2);
}
div {
padding: .average(16px, 50px)[@result];
}
allow attribute of iframe element (#17879 by @kovsu) {#change-17879}<!-- Input -->
<iframe allow="layout-animations 'none'; unoptimized-images 'none'; oversized-images 'none'; sync-script 'none'; sync-xhr 'none'; unsized-media 'none';"></iframe>
<!-- Prettier 3.6 -->
<iframe allow="layout-animations 'none'; unoptimized-images 'none'; oversized-images 'none'; sync-script 'none'; sync-xhr 'none'; unsized-media 'none';"></iframe>
<!-- Prettier 3.7 -->
<iframe
allow="
layout-animations 'none';
unoptimized-images 'none';
oversized-images 'none';
sync-script 'none';
sync-xhr 'none';
unsized-media 'none';
"
></iframe>
<!-- Input -->
<button
type="button"
onclick="console .log( 'Hello, this is my old-fashioned event handler!')"
>Press me</button>
<!-- Prettier 3.6 -->
<button
type="button"
onclick="console .log( 'Hello, this is my old-fashioned event handler!')"
>
Press me
</button>
<!-- Prettier 3.7 -->
<button
type="button"
onclick="console.log('Hello, this is my old-fashioned event handler!')"
>
Press me
</button>
Angular 20.1 added support for new assignment operators. Angular 21 added support for regular expression.
<!-- prettier-ignore --><!-- Input -->
<b (click)="
a ??= b">{{ /\d+/g}}</b>
<!-- Prettier 3.6 -->
<b
(click)="
a ??= b"
>{{ /\d+/g}}</b
>
<!-- Prettier 3.7 -->
<b (click)="a ??= b">{{ /\d+/g }}</b>
<!-- Input -->
{{a() // comment}}
<!-- Prettier 3.6 -->
{{ a(// comment) // comment }}
<!-- Prettier 3.7 -->
{{
a() // comment
}}
<!-- Input -->
{{ foo?.bar!.baz }}
<!-- Prettier 3.6 -->
{{ (foo?.bar)!.baz }}
<!-- Prettier 3.7 -->
{{ foo?.bar!.baz }}
Front matter can now be used in Handlebars.
<!-- prettier-ignore -->---
title: My page title
keywords:
- word
- other word
---
<h1>{{title}}</h1>
<ul>
{{#each keywords}}
<li>{{this}}</li>
{{/each}}
</ul>
else if syntax for custom helpers (#17856 by @kovsu) {#change-17856}{{! Input }}
{{#animated-if this.foo}}
foo content
{{else if (this.bar)}}
bar content
{{/animated-if}}
{{! Prettier 3.6 }}
{{#animated-if this.foo}}
foo content
{{else}}{{#if (this.bar)}}
bar content
{{/if}}{{/animated-if}}
{{! Prettier 3.7 }}
{{#animated-if this.foo}}
foo content
{{else if (this.bar)}}
bar content
{{/animated-if}}
<style> tags (#18065 by @kovsu, @fisker) {#change-18065}{{! Input }}
<style>
#foo {
color: red;
}
</style>
{{! Prettier 3.6 (--html-whitespace-sensitivity=ignore) }}
<style>
#foo {
color: red;
}
</style>
{{! Prettier 3.7 (--html-whitespace-sensitivity=ignore) }}
<style>
#foo {
color: red;
}
</style>
--html-whitespace-sensitivity=ignore (#18133 by @fisker) {#change-18133}{{! Input }}
<div> </div>
{{! Prettier 3.6 (--html-whitespace-ensitivity=ignore) }}
<div>
</div>
{{! Prettier 3.7 (--html-whitespace-ensitivity=ignore) }}
<div></div>
# Input
"Description"
query {
node {
id
}
}
# Prettier 3.6
SyntaxError: Syntax Error: Unexpected description, descriptions are supported only on type definitions. (1:1)
> 1 | "Description"
| ^
2 | query {
3 | node {
4 | id
# Prettier 3.7
"Description"
query {
node {
id
}
}
This change improves table alignment in all languages.
<!-- prettier-ignore --><!-- Input -->
| | |
| :-: | :-: |
| ✔ | ✘ |
| ✘ | ✔ |
| ✔ | ✘ |
<!-- Input -->
| | |
| :-: | :-: |
| ✔ | ✘ |
| ✘ | ✔ |
| ✔ | ✘ |
<!-- Prettier 3.7 -->
| | |
| :-: | :-: |
| ✔ | ✘ |
| ✘ | ✔ |
| ✔ | ✘ |
TOML Front Matter can be processed by the appropriate plugin if any. Works for HTML and CSS files too.
This is a supplementary fix for #17143, where the original PR could not determine whether the contents on the left and right sides were both words.
<!-- prettier-ignore --><!-- Input -->
1***2***3
1**_2_**3
<!-- Prettier 3.6 -->
1**_2_**3
1**_2_**3
<!-- Prettier 3.7 -->
1***2***3
1***2***3
import and export parsing (#17996 by @kovsu & @fisker) {#change-17996}- import is a word in lists
- export is a word in lists, too!
- import is a word in list
s
- export is a word in lists, too
!
- import is a word in lists
- export is a word in lists, too!
# Input
only: issues
# Comment
# Prettier 3.6
only: issues
# Comment
# Prettier 3.7
only: issues
# Comment
# Input
a: a
---
b: b
...
c: c
...
---
d: d
# Prettier 3.6
a: a
---
b: b
---
c: c
---
d: d
# Prettier 3.7
a: a
---
b: b
...
c: c
...
---
d: d
# Input
{ "foo" # comment
:bar }
# Prettier 3.6
{ "foo": bar } # comment
# Prettier 3.7
{ ? "foo" # comment
: bar }
plugin.parser.preprocess() to return a Promise (#17679 by @fisker) {#change-17679}Align with plugin.printer.preprocess(), which allows to return a Promise.
We still suggest move your async work into plugin.parser.parse(), which already allow to return a Promise, we may remove support for plugin.parser.preprocess() in future.
AstPath#call() to access property of nullish properties (#17860 by @fisker) {#change-17860}Previously, to check for a possible non-existent child node, we had to ensure the node existed first.
const isFoo = path.call(() => path.node?.type === "Foo", "foo", "bar");
// Uncaught TypeError: Cannot read properties of undefined (reading 'bar')
We have to
const isFoo =
path.node.foo?.bar &&
path.call(() => path.node?.type === "Foo", "foo", "bar");
Since Prettier 3.7, accessing a property of nullish properties no longer throws errors.
plugin.printer.canAttachComment() (#18055 by @fisker) {#change-18055}This prevent attaching comments to a specific child of node.
For example in this JavaScript code const object = {property};, the Identifier node (property) appears as both Property.key and Property.value in the AST, obviously it can only print once, if the comment is attached to the non-printable child the comment will get lost.
Plugins can now avoid this by adding a canAttachComment like this:
export const canAttachComment = (node, [parent]) =>
!(
parent?.type === "Property" &&
parent.shorthand &&
parent.key === node &&
parent.key !== parent.value
);
export const print = (path, options, print) => {
const { node } = path;
switch (node.type) {
case "Property":
if (node.shorthand) {
return print("value");
}
// ...
}
};
plugin.printer.printPrettierIgnored() (#18070 by @fisker) {#change-18070}For a node with prettier-ignore comment, Prettier prints the text of the node directly. However, this may cause problems, for example, if the node needs to be parenthesized or needs to print a leading semicolon to prevent ASI issue in --no-semi mode.
Since Prettier 3.7, plugins can add a printPrettierIgnored() function to the printer to customize the prettier-ignored node print process. This function uses the extract same signature as plugin.printer.print()
estree printer (#18072 by @fisker) {#change-18072}Previously, if a plugin wanted to make a plugin with an estree printer that outputs different code based on the built-in one, the plugin needed to provide both parsers and printers. Prettier 3.7 allows a plugin to create a plugin that only provides an estree printer.
node_modules/.cache/ directory when --cache is not enabled (#18124 by @chiawendt) {#change-18124}Running prettier . without --cache no longer creates an empty node_modules/.cache/ directory.