Prettier 3.7: Improved formatting consistency and new plugin features!
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!
Highlights
TypeScript
Fix the inconsistent printing between class and interface (#18094, #18091, #18215 by @fisker)
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.
The extra indentation for type parameters in class has been removed
// 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 {}
Align interface heritages print with class
// 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> {}
Align single heritage print with super class
// 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
> {}
Inconsistent opening brace print logic of class and interface body
In Prettier v2.3, to improve visual separation between class head and body, we start to print { of 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, please leave 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 up, we'll align the interface body print with the one for the class body in Prettier v4.
// 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
Other Changes
JavaScript
Allow break import attributes into multiple lines (#17329 by @fisker)
// 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",
};
Add support for "Discard Bindings" proposal (#17708 by @fisker)
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.
const [void] = x;
const {x:void} = x;
Fix inconsistent comment format (#17723 by @fisker)
// 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();
}
Add additional Playwright test functions (#17876 by @BPScott)
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
});
Avoid break {import,require.resolve,require.resolve.paths,import.meta.resolve}() with long module name (#17882, #17908 by @kovsu & @fisker)
// 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");
Improve comment handing inside if statement (#17998 by @fisker)
// 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();
}
Improve require() call with comments (#18037 by @fisker)
// Input
require(
// Comment
"foo"
);
// Prettier 3.6
require(// Comment
"foo");
// Prettier 3.7
require(
// Comment
"foo",
);
Remove indention in logical expression in Boolean() call (#18087 by @kovsu)
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
);
Fix comments handling for for-statements (#18099 by @fisker, @sosukesuzuki)
// 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();
Improve comment printing around empty statement (#18108 by @fisker)
// 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 */ ;
Fix inconsistent comment print between class methods and object methods (#18147 by @fisker)
// 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;
},
};
Add missing parentheses in bitwise operators (#18163 by @fs0414)
// 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);
Fix inconsistent break for array literals (#18172 by @Dunqing, @fisker)
// 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],
);
Fix inconsistent logical expression print (#18205 by @fisker)
// 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,
);
Fix inconsistent print between CallExpression and NewExpression (#18206 by @fisker)
// 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,
)
Remove redundant parentheses around JSX element (#18243 by @fisker)
// 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>,
);
Improve formatting of logical expression as callee of new expression (#18245 by @fisker)
// 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
)();
Remove empty line in for statement without "update" (#18300 by @fisker)
// 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;
) {}
Improve super class format (#18325 by @fisker)
// 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 {}
TypeScript
Fix misplacement of comments after arrow (#17421 by @o-m12a, @t-mangoe)
// 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;
Add missing parentheses to arrow function in instantiation expression (#17724 by @fisker)
// Input
void (<_T extends never>() => {})<never>;
// Prettier 3.6
void <_T extends never>() => {}<never>;
// Prettier 3.7
void (<_T extends never>() => {})<never>;
Fix TSMappedType format (#17785 by @fisker)
// 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;
Print trailing comma in TSImportType options (#17798 by @fisker)
Trailing comma in import type attribute wasn't allowed before TypeScript v5.9, now the bug has been fixed.
// 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
});
Improve CommonJS module require() with comments (#18035 by @fisker)
// Input
import foo = require(
// Comment
"foo"
);
// Prettier 3.6
import foo = require(// Comment
"foo");
// Prettier 3.7
import foo = require(
// Comment
"foo"
);
Line breaking after = in type parameters (#18043 by @fisker)
// 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 };
Remove unexpected blank line before union types (#18109 by @jspereiramoura, @fisker)
// 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
Fix inconsistent comment printing between typescript and flow parser (#18110 by @fisker)
// 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;
}
Fix missing semicolon before call signatures (#18118 by @fisker)
// 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
}
Fix inconsistent print of as const between flow and typescript parsers (#18161 by @fisker)
// 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;
Fix comment around as/satisfies expression (#18162 by @fisker)
// 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
*/;
Fix miss aligned union type (#18165 by @fisker)
// 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];
}
Remove extra empty line in union type (#18197 by @Dunqing)
// Input
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
Fooo1000 | Baz2000 | BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
// Prettier 3.6
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
// Prettier 3.7
type SuperLongTypeNameLoremIpsumLoremIpsumBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBlaBla =
| Fooo1000
| Baz2000
| BarLoooooooooooooooooooooooooooooooooooooooooooooooooLong;
Flow
Add basic support for "match" syntax (#17681 by @fisker)
// 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" },
};
Add support for opaque type with lower and upper bound (#17792 by @SamChou19815)
// 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>;
CSS
Handle attribute selector with case-sensitive and uppercase case-insensitive flags (#17841, #17865 by @kovsu)
/* 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;
}
Fix crash when formatting special custom properties (#17899 by @fisker)
/* Input */
:root {
--l: , #000;
}
/* Prettier 3.6 */
TypeError: Cannot read properties of undefined (reading 'value')
/* Prettier 3.7 */
:root {
--l: , #000;
}
Fix selector been lowercased incorrectly inside css modules (#17929 by @kovsu)
/* 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;
}
Fix formatting of CSS selectors contains // (#17938 by @kovsu)
/* 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;
}
Remove unexpected space between font size and line height (#18114 by @kovsu)
/* Input */
a {
font: var(--size)/1;
}
/* Prettier 3.6 */
a {
font: var(--size) / 1;
}
/* Prettier 3.7 */
a {
font: var(--size)/1;
}
Fix extra indent for CSS comma-separated values after a block comment (#18228 by @seiyab)
/* 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);
}
SCSS
Fix parentheses space problem in mixin argument (#17836 by @kovsu)
// Input
.foo {
@include bar('A (B)');
}
// Prettier 3.6
.foo {
@include bar('A( B)');
}
// Prettier 3.7
.foo {
@include bar('A (B)');
}
Fix formatting of space-separated values (#17903 by @kovsu)
// 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);
}
Less
Fix variable name being lowercased incorrectly (#17820 by @kovsu)
// 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;
}
Keep property/variable accessors tight (#17983 by @kovsu)
// 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];
}
HTML
Support format allow attribute of iframe element (#17879 by @kovsu)
<!-- 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>
Format inline event handler (#17909 by @kovsu)
<!-- 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
Support Angular 21 (#17722, #18294 by @fisker)
Angular 20.1 added support for new assignment operators. Angular 21 added support for regular expression.
<!-- 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>
Fix comments get duplicated in interpolation (#17862 by @fisker)
<!-- Input -->
{{a() // comment}}
<!-- Prettier 3.6 -->
{{ a(// comment) // comment }}
<!-- Prettier 3.7 -->
{{
a() // comment
}}
Fix formatting of "non-null assertion" (#18047 by @fisker)
<!-- Input -->
{{ foo?.bar!.baz }}
<!-- Prettier 3.6 -->
{{ (foo?.bar)!.baz }}
<!-- Prettier 3.7 -->
{{ foo?.bar!.baz }}
Ember / Handlebars
Added Front Matter support to Handlebars (#17781 by @Codezilluh)
Front matter can now be used in Handlebars.
---
title: My page title
keywords:
- word
- other word
---
<h1>{{title}}</h1>
<ul>
{{#each keywords}}
<li>{{this}}</li>
{{/each}}
</ul>
Preserve else if syntax for custom helpers (#17856 by @kovsu)
{{! 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}}
Remove extra blank lines in <style> tags (#18065 by @kovsu, @fisker)
{{! 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>
Don't force elements to break with --html-whitespace-sensitivity=ignore (#18133 by @fisker)
{{! Input }}
<div> </div>
{{! Prettier 3.6 (--html-whitespace-ensitivity=ignore) }}
<div>
</div>
{{! Prettier 3.7 (--html-whitespace-ensitivity=ignore) }}
<div></div>
GraphQL
Support "executable descriptions" (#18214 by @fisker)
# 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
}
}
Markdown
Improve emoji size measurement (#17813 by @seiyab)
This change improves table alignment in all languages.
<!-- Input -->
| | |
| :-: | :-: |
| ✔ | ✘ |
| ✘ | ✔ |
| ✔ | ✘ |
<!-- Input -->
| | |
| :-: | :-: |
| ✔ | ✘ |
| ✘ | ✔ |
| ✔ | ✘ |
<!-- Prettier 3.7 -->
| | |
| :-: | :-: |
| ✔ | ✘ |
| ✘ | ✔ |
| ✔ | ✘ |
Infer TOML parser for Front Matter (#17965 by @kovsu)
TOML Front Matter can be processed by the appropriate plugin if any. Works for HTML and CSS files too.
Fix strong emphasis format (#18010 by @yin1999)
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.
<!-- 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
MDX
Fix import and export parsing (#17996 by @kovsu & @fisker)
{/* Input */}
- import is a word in lists
- export is a word in lists, too!
{/* Prettier 3.6 */}
- import is a word in list
s
- export is a word in lists, too
!
{/* Prettier 3.7 */}
- import is a word in lists
- export is a word in lists, too!
YAML
Preserve empty line between map and comment (#17843 by @kovsu)
# Input
only: issues
# Comment
# Prettier 3.6
only: issues
# Comment
# Prettier 3.7
only: issues
# Comment
Preserve explicit document end marker (#18296 by @fisker)
# 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
Use explicit key style for flow-mapping keys with trailing comments (#18324 by @kovsu, @fisker)
# Input
{ "foo" # comment
:bar }
# Prettier 3.6
{ "foo": bar } # comment
# Prettier 3.7
{ ? "foo" # comment
: bar }
API
Allow plugin.parser.preprocess() to return a Promise (#17679 by @fisker)
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.
Allow AstPath#call() to access property of nullish properties (#17860 by @fisker)
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.
Pass ancestors to plugin.printer.canAttachComment() (#18055 by @fisker)
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");
}
// ...
}
};
Add support for plugin.printer.printPrettierIgnored() (#18070 by @fisker)
For a node with prettier-ignore comment, Prettier prints the text of the node directly. However, this may cause problems, for example, 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 printPrettierIgnored() function to the printer to customize the prettier-ignored node print process. This function uses the extract same signature as plugin.printer.print()
Allow plugin to provide an estree printer (#18072 by @fisker)
Previously, if a plugin wanted to make a plugin with estree printer that outputs different code based on the built-in one, plugin need provide both parsers and printers. Prettier 3.7 allows a plugin to create a plugin that only provides estree printer.
CLI
Avoid creating node_modules/.cache/ directory when --cache is not enabled (#18124 by @chiawendt)
Run prettier . without --cache no longer creates an empty node_modules/.cache/ directory.
