跳至主内容区

Prettier 2.3:赋值更一致,短键名不折行,Handlebars 正式支持

· 1 分钟阅读
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

本次发布主要修复了 JavaScript 格式化工具中一些长期存在的问题。请注意,使用新版本重新格式化项目可能会产生较大的差异。如果尚未使用 ignoreRevsFilegit blame 中隐藏这些全局变更,现在或许是时候了。

一个显著的里程碑是期待已久的 Ember / Handlebars 格式化工具正式发布。这将是核心库直接包含的最后一个格式化工具。未来为了可持续性,新语言应仅通过插件添加。

我们衷心感谢以下资金贡献者:SalesforceIndeedFrontend Masters、Airbnb、Shogun Labs、Skyscanner、Konstantin Pschera 以及帮助我们持续发展的众多其他贡献者。如果您喜欢 Prettier 并希望支持我们的工作,请访问我们的 OpenCollective。也请考虑支持 Prettier 所依赖的项目,如 typescript-eslintremarkBabel

本次发布的大部分变更归功于 Fisker CheungGeorgii DolzhykovSosuke Suzuki 的辛勤工作,以及其他众多贡献者。

温馨提示:安装或更新 Prettier 时,强烈建议package.json 中指定精确版本:"2.3.0",而非 "^2.3.0"

重要更新

JavaScript

更一致地格式化赋值语句 (#10222, #10643, #10672 by @thorn0; #10158 by @sosukesuzuki)

此前 Prettier 在处理赋值语句折行时存在诸多问题,例如较长的右侧表达式往往不会折行。现在这一问题已得到解决。

// Prettier 2.2
aParticularlyLongAndObnoxiousNameForIllustrativePurposes = anotherVeryLongNameForIllustrativePurposes;

aParticularlyLongAndObnoxiousNameForIllustrativePurposes = "a very long string for illustrative purposes"
.length;

someReallyLongThingStoredInAMapWithAReallyBigName[
pageletID
] = _someVariableThatWeAreCheckingForFalsiness
? Date.now() - _someVariableThatWeAreCheckingForFalsiness
: 0;

class x {
private readonly rawConfigFromFile$: BehaviorSubject<any> = new BehaviorSubject(
notRead
);
}

// Prettier 2.3
aParticularlyLongAndObnoxiousNameForIllustrativePurposes =
anotherVeryLongNameForIllustrativePurposes;

aParticularlyLongAndObnoxiousNameForIllustrativePurposes =
"a very long string for illustrative purposes".length;

someReallyLongThingStoredInAMapWithAReallyBigName[pageletID] =
_someVariableThatWeAreCheckingForFalsiness
? Date.now() - _someVariableThatWeAreCheckingForFalsiness
: 0;

class x {
private readonly rawConfigFromFile$: BehaviorSubject<any> =
new BehaviorSubject(notRead);
}

避免短键名对象属性折行 (#10335 by @thorn0)

对象字面量中短属性名后的换行通常显得不自然。即使换行能节省 1-2 个字符长度,这种折行也往往不合理。Prettier 2.3 会避免在短于 tabWidth + 3 的属性名后换行 —— 例如默认配置下为 5 个字符,tabWidth: 4 时为 7 个字符。此启发式规则未来可能会调整。

// Prettier 2.2
const importantLink = {
url:
"https://prettier.io/docs/en/rationale.html#what-prettier-is-concerned-about",
gitHubUrl:
"https://github.com/prettier/prettier/blob/main/docs/rationale.md#what-prettier-is-concerned-about",
};

// Prettier 2.3
const importantLink = {
url: "https://prettier.io/docs/en/rationale.html#what-prettier-is-concerned-about",
gitHubUrl:
"https://github.com/prettier/prettier/blob/main/docs/rationale.md#what-prettier-is-concerned-about",
};

Ember / Handlebars

Handlebars 支持结束 alpha 进入正式发布 (#10290 by @dcyriller & @thorn0)

这一切始于2017年。Prettier 对 Handlebars 的支持其实已经存在一段时间,但此前并未正式发布,因为功能尚未完善。它的状态从 "alpha" 到 "experimental" 再到 "beta",如果你查阅过旧版发布说明,会发现它后来竟又从 "beta" 莫名变回了 "alpha"...

不过无论如何,这一刻终于来临:Prettier 现在可以正式格式化带有 Handlebars 的 HTML 模板了!🎉

该功能使用 Ember 的 Handlebars 解析器 Glimmer,因此得益于 Ember 团队的努力,它应该符合 HTML 规范。

支持 --html-whitespace-sensitivity 选项,默认值为 strict。这意味着 Prettier 会始终保留标签周围的空白字符,并认为在原本无空格处添加空格(或反之)是不安全的操作,因为这可能影响浏览器中的渲染效果。目前暂不支持 css 选项值(暂时视为 strict)。

该功能命名为 "Ember / Handlebars" 而非单纯的 "Handlebars",是因为 Glimmer 不支持某些 Handlebars 语法和使用场景。这主要是因为 Handlebars 作为模板引擎(预处理器)不关心底层内容语法,而 Glimmer 同时解析两种语法——HTML 和 Handlebars——并将结果组合成单一树状结构供 Prettier 打印。这意味着 Prettier 无法格式化无法解析成此类结构的 Handlebars 文件,原因可能是底层语法非 HTML,或是模板指令与标签重叠导致无法呈现树状结构(例如 {{#if foo}}<div>{{/if})。即便存在这些限制,该格式化工具对非 Ember 的 Handlebars 用户仍有实用价值。至于 Ember 不支持的语法,未来 Prettier 版本很可能增加支持,因为 Glimmer 底层使用了完整的 Handlebars 解析器。

默认情况下,扩展名为 .hbs.handlebars 的文件会被识别为 Handlebars。对于其他扩展名,需通过 --parser 选项指定值为 glimmer——例如,使用命令行或更推荐的方式:配置覆盖

欢迎在 Playground 上体验此格式化工具!

格式化改进

JavaScript

优化柯里化箭头函数的格式化 (#9992, #10543 by @sosukesuzuki & @thorn0)

// Prettier 2.2
const currying = (argument1) => (argument2) => (argument3) => (argument4) => (
argument5
) => (argument6) => foo;

// Prettier 2.3
const currying =
(argument1) =>
(argument2) =>
(argument3) =>
(argument4) =>
(argument5) =>
(argument6) =>
foo;

改进 React Hooks 调用的格式化 (#10238 by @sosukesuzuki)

// Prettier 2.2
const { firstName, lastName } = useMemo(() => parseFullName(fullName), [
fullName,
]);

// Prettier 2.3
const { firstName, lastName } = useMemo(
() => parseFullName(fullName),
[fullName]
);

增强多行类头与类体之间的视觉区分 (#10085 by @sosukesuzuki)

// Prettier 2.2
class loooooooooooooooooooong
extends looooooooooooooooooong
implements loooooooooooooooooooong {
property: string;
}

// Prettier 2.3
class loooooooooooooooooooong
extends looooooooooooooooooong
implements loooooooooooooooooooong
{
property: string;
}

精简纯数字数组的格式化 (#10106, #10160 by @thorn0)

虽然 Prettier 通常避免此类格式化(因其不利于差异对比),但在这种特殊情况下,我们认为其收益大于风险。

若数组元素中存在同行尾随单行注释(// ...),则不会应用精简格式。而独占一行的单行注释(以及空行)可用于逻辑分组,不影响精简格式的应用。

// Input
const lazyCatererNumbers = [1, 2, 4, 7, 11, 16, 22, 29, 37, 46,

// n > 10
56, 67, 79, 92, 106, 121, 137, 154, 172, 191, 211, 232, 254, 277, 301, 326, 352, 379, 407, 436, 466,
497, 529, 562, 596, 631, 667, 704, 742, 781,
// n > 40
821, 862, 904, 947, 991, 1036, 1082, 1129, 1177, 1226, 1276, 1327, 1379];

// Prettier 2.2
const lazyCatererNumbers = [
1,
2,
4,
7,
11,
16,
22,
29,
37,
// ... ✂ 46 lines ✂ ...
1379,
];

// Prettier 2.3
const lazyCatererNumbers = [
1, 2, 4, 7, 11, 16, 22, 29, 37, 46,

// n > 10
56, 67, 79, 92, 106, 121, 137, 154, 172, 191, 211, 232, 254, 277, 301, 326,
352, 379, 407, 436, 466, 497, 529, 562, 596, 631, 667, 704, 742, 781,
// n > 40
821, 862, 904, 947, 991, 1036, 1082, 1129, 1177, 1226, 1276, 1327, 1379,
];

改进成员表达式和调用表达式头部嵌套 await 的格式化 (#10342 by @thorn0)

尽管 Prettier 在此处尽力优化,但仍建议避免编写此类代码。为团队成员考虑,请使用中间变量代替。

// Input
const getAccountCount = async () =>
(await
(await (
await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)
).findItem("My bookmarks")).getChildren()
).length

// Prettier 2.2
const getAccountCount = async () =>
(
await (
await (await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)).findItem(
"My bookmarks"
)
).getChildren()
).length;

// Prettier 2.3
const getAccountCount = async () =>
(
await (
await (
await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)
).findItem("My bookmarks")
).getChildren()
).length;

改进函数调用中 do 表达式的格式化 (#10693 by @sosukesuzuki)

"do 表达式"属于 ECMAScript 阶段1提案

// Prettier 2.2
expect(
do {
var bar = "foo";
bar;
}
).toBe("foo");

// Prettier 2.3
expect(do {
var bar = "foo";
bar;
}).toBe("foo");

条件运算符缩进风格统一 (#10187, #10266 by @sosukesuzuki)

// Prettier 2.2
const dotNotationMemberExpression = (callNode.parent?.type ===
AST_NODE_TYPES.ChainExpression
? callNode.parent.parent
: callNode.parent
).TSESTree.BinaryExpression;

const computedMemberExpression = (callNode.parent?.type ===
AST_NODE_TYPES.ChainExpression
? callNode.parent.parent
: callNode.parent)[TSESTree.BinaryExpression];

const callExpressionCallee = (callNode.parent?.type ===
AST_NODE_TYPES.ChainExpression
? callNode.parent.parent
: callNode.parent)(TSESTree.BinaryExpression);

const typeScriptAsExpression = (callNode.parent?.type ===
AST_NODE_TYPES.ChainExpression
? callNode.parent.parent
: callNode.parent) as TSESTree.BinaryExpression;

// Prettier 2.3
const dotNotationMemberExpression = (
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
? callNode.parent.parent
: callNode.parent
).TSESTree.BinaryExpression;

const computedMemberExpression = (
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
? callNode.parent.parent
: callNode.parent
)[TSESTree.BinaryExpression];

const callExpressionCallee = (
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
? callNode.parent.parent
: callNode.parent
)(TSESTree.BinaryExpression);

const typeScriptAsExpression = (
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
? callNode.parent.parent
: callNode.parent
) as TSESTree.BinaryExpression;

HTML

基于前缀的 class 属性多行格式化 (#7865 by @thorn0)

HTML 类名的格式化现在会保持所有类名在一行显示,直到达到行长度限制;此时,具有相同前缀的连续类名将被分组到单独的行中。对于 Bootstrap 和 Tailwind CSS 等布局框架(它们会给元素添加大量类名),这种处理方式相比之前的策略(所有类名保持在一行)或每个类名单独一行,更能提升可读性和可维护性。

<!-- Prettier 2.2 -->
<div
class="SomeComponent__heading-row d-flex flex-column flex-lg-row justify-content-start justify-content-lg-between align-items-start align-items-lg-center"
></div>

<!-- Prettier 2.3 -->
<div
class="
SomeComponent__heading-row
d-flex
flex-column flex-lg-row
justify-content-start justify-content-lg-between
align-items-start align-items-lg-center
"
></div>

其他变更

JavaScript

修复同一行多个注释的不稳定问题 (#9672 by @fisker)

// Input
a;
/*1*//*2*/
/*3*/
b;

// Prettier 2.2
a; /*2*/
/*1*/ /*3*/
b;

// Prettier 2.2 (second format)
a; /*2*/ /*3*/
/*1*/ b;

// Prettier 2.3
a;
/*1*/ /*2*/
/*3*/
b;

避免格式化紧邻 rangeStart 结束的节点 (#9704 by @fisker)

此前在进行范围格式化时,这类节点会被视为范围的一部分,现在则被排除在外。该修复不仅影响 JavaScript,也适用于支持范围格式化的其他语言。

// Input
foo = 1.0000;bar = 1.0000;baz=1.0000;
^^^^^^^^^^^^^ Range

// Prettier 2.2
foo = 1.0;
bar = 1.0;baz=1.0000;

// Prettier 2.3
foo = 1.0000;bar = 1.0;baz=1.0000;

修复 JSX 结束标签内的注释处理 (#9711 by @fisker)

// Input
<a><// comment
/a>;

// Prettier 2.2
<a></// comment
a>;

// Prettier 2.3
<a></
// comment
a
>;

修复语言注释检测不一致问题 (#9743 by @fisker)

/* HTML */ 注释必须直接位于模板字面量之前,才能将后者识别为 HTML-in-JS。此前在某些位置错误识别了这类注释。

// Input
foo /* HTML */ = `<DIV>
</DIV>`;

// Prettier 2.2 (--parser=babel)
foo /* HTML */ = `<div></div>`;

// Prettier 2.2 (--parser=meriyah)
foo /* HTML */ = `<DIV>
</DIV>`;

// Prettier 2.3 (All JavaScript parsers)
foo /* HTML */ = `<DIV>
</DIV>`;

修复忽略指令后多余分号问题 (#9850 by @fisker)

// Input
// prettier-ignore
'use strict';

function foo() {
// prettier-ignore
"use strict";;
}

// Prettier 2.2
// prettier-ignore
'use strict';;

function foo() {
// prettier-ignore
"use strict";;
}

// Prettier 2.3
// prettier-ignore
'use strict';

function foo() {
// prettier-ignore
"use strict";
}

修复包含 U+3000 字符时的 JSX 格式不稳定问题 (#9866 by @fisker)

// Input
<p>
<span /> {this.props.data.title} <span />
//----- ^ U+3000 --------------- ^ U+3000
</p>

// Prettier 2.2
<p>
<span />
 {this.props.data.title} <span />
//----- ^ U+3000 --------------- ^ U+3000
</p>;

// Prettier 2.2 (second format)
<p>
<span /> {this.props.data.title} <span />
//----- ^ U+3000 --------------- ^ U+3000
</p>;

// Prettier 2.3
<p>
<span /> {this.props.data.title} <span />
//----- ^ U+3000 --------------- ^ U+3000
</p>;

修复表达式 a(b => c => function (){}) 导致的错误 (#10278 by @thorn0)

该问题为 v2.2.0 版本引入的回归错误。

// Input
a(b => c => function (){})

// Prettier 2.2
TypeError: Cannot read property 'length' of undefined

// Prettier 2.3
a((b) => (c) => function () {});

改进行内装饰器格式化 (#10296 by @fisker)

// Input
class Foo {
@decorator([]) bar() {}
@decorator(
[]
) baz() {}
}

// Prettier 2.2
class Foo {
@decorator([]) bar() {}
@decorator([])
baz() {}
}

// Prettier 2.3
class Foo {
@decorator([]) bar() {}
@decorator([]) baz() {}
}

修复私有字段的 ASI 保护机制 (#10334 by @thorn0)

// Input
class C {
#field = 'value';
["method"]() {}
}

// Prettier 2.2 (with --no-semi)
class C {
#field = "value"
["method"]() {}
}

// Prettier 2.3 (with --no-semi)
class C {
#field = "value";
["method"]() {}
}

支持模块块提案 (#10417 by @sosukesuzuki, @thorn0)

支持格式化 模块块 Stage 2 提案

// Input
module { export let foo = "foo"; };

// Prettier 2.2
SyntaxError: Unexpected token, expected ";"

// Prettier 2.3
module {
export let foo = "foo";
};

修复管道操作中 yield 缺少括号的问题 (#10446 by @fisker)

// Input
function* f() {
return x |> (yield #);
}

// Prettier 2.2
function* f() {
return x |> yield #;
}

// Prettier 2.3
function* f() {
return x |> (yield #);
}

优化 Babel 错误恢复机制的选择性 (#10495 by @fisker, #9787 by @sosukesuzuki, #10065, #10322 by @thorn0)

此前由于错误恢复机制,Babel 解析器过于宽松,导致生成各种 Prettier 无法处理的 AST 形态。Prettier 2.3 现在仅允许 Babel 恢复少数无害类型的错误——例如多个同名 const 声明。其他任何错误都将被报告为语法错误。

// Input
foo("a", , "b");

// Prettier 2.2
TypeError: Cannot read property 'type' of null

// Prettier 2.3
[error] stdin: SyntaxError: Argument expression expected. (1:10)
[error] > 1 | foo("a", , "b");
[error] | ^
// Input
const \u{20} = 1

// Prettier 2.2
const = 1;

// Prettier 2.3
[error] stdin: SyntaxError: Invalid Unicode escape (1:7)
[error] > 1 | const \u{20} = 1
[error] | ^ ^

避免纯数字数组的末参数紧贴换行 (#10517 by @thorn0)

这是针对纯数字数组的又一特殊处理。

// Input
instantiate(game, [
transform([-0.7, 0.5, 0]),
render_colored_diffuse(game.MaterialDiffuse, game.Meshes["monkey_flat"], [1, 1, 0.3, 1]),
]);

// Prettier 2.2
instantiate(game, [
transform([-0.7, 0.5, 0]),
render_colored_diffuse(game.MaterialDiffuse, game.Meshes["monkey_flat"], [
1,
1,
0.3,
1,
]),
]);

// Prettier 2.3
instantiate(game, [
transform([-0.7, 0.5, 0]),
render_colored_diffuse(
game.MaterialDiffuse,
game.Meshes["monkey_flat"],
[1, 1, 0.3, 1]
),
]);

改进 AMD define 的识别机制 (#10528 by @thorn0)

Prettier 对 AMD define 调用进行了特殊处理以避免意外换行。现在仅当 define 调用位于函数或程序顶层,且参数传递符合 AMD 规范时才会进行格式化。

// Prettier 2.2
const someVariable = define("some string literal", anotherVariable, yetAnotherVariable);

// Prettier 2.3
const someVariable = define(
"some string literal",
anotherVariable,
yetAnotherVariable
);

修复重复的 prettier-ignore 注释 (#10666 by @fisker)

// Input
foo = a.
// prettier-ignore
b;

// Prettier 2.2
foo =
// prettier-ignore
a.
// prettier-ignore
b;

// Prettier 2.3
foo = a.
// prettier-ignore
b;

mapDoc 中处理条件分组 (#10695 by @thorn0)

此修复特别解决了 JS 中 HTML 模板的替换断裂问题。

// Input
export default function include_photoswipe(
gallery_selector = ".my-gallery"
): string {
return /* HTML */ `
<script>
window.addEventListener("load", () =>
initPhotoSwipeFromDOM("${gallery_selector}")
);
</script>`;
}

// Prettier 2.2
export default function include_photoswipe(
gallery_selector = ".my-gallery"
): string {
return /* HTML */ ` <script>
window.addEventListener("load", () =>
initPhotoSwipeFromDOM("PRETTIER_HTML_PLACEHOLDER_0_13_IN_JS")
);
</script>`;
}

// Prettier 2.3
export default function include_photoswipe(
gallery_selector = ".my-gallery"
): string {
return /* HTML */ ` <script>
window.addEventListener("load", () =>
initPhotoSwipeFromDOM("${gallery_selector}")
);
</script>`;
}

避免导致代码损坏的参数展开 (#10712 by @thorn0)

// Input
glimseGlyphsHazardNoopsTieTie(({ a, b = () => {
console.log();
}}) => averredBathersBoxroomBuggyNurl());

// Prettier 2.2
glimseGlyphsHazardNoopsTieTie(({ a, b = () => {
console.log();
} }) => averredBathersBoxroomBuggyNurl());

// Prettier 2.3
glimseGlyphsHazardNoopsTieTie(
({
a,
b = () => {
console.log();
},
}) => averredBathersBoxroomBuggyNurl()
);

修复 for-of 循环中 async 缺少括号的问题 (#10781 by @fisker)

参见 https://github.com/tc39/ecma262/issues/2034

// Input
for ((async) of []);

// Prettier 2.2
for (async of []);

// Prettier 2.2 (second format)
SyntaxError: Unexpected token, expected "=>" (1:15)
> 1 | for (async of []);

// Prettier 2.3
for ((async) of []);

支持 async do 表达式提案 (#10813 by @sosukesuzuki)

参见 https://github.com/tc39/proposal-async-do-expressions

// Input
const x = async do {
await requestAPI().json();
};

// Prettier 2.2
SyntaxError: Unexpected token, expected ";" (1:17)

// Prettier 2.3
const x = async do {
await requestAPI().json();
};

TypeScript

修复 MethodDefinition 中的注释丢失问题 (#9872 by @fisker)

此问题仅存在于 typescript 解析器,babel-ts 不受影响。

// Input
class Foo {
bar() /* bat */;
}

// Prettier 2.2
Error: Comment "bat" was not printed. Please report this error!

// Prettier 2.3
class Foo {
bar /* bat */();
}

修复方法类型声明参数中出现的不必要换行问题 (#10024 by @sosukesuzuki, #10357 by @thorn0)

// Input
type Foo = {
method(foo: "foo"): `
`
};

// Prettier 2.2
type Foo = {
method(
foo: "foo"
): `
`;
};

// Prettier 2.3
type Foo = {
method(foo: "foo"): `
`;
};

在类型参数中打印尾部逗号 (#10109 by @sosukesuzuki, #10353 by @thorn0)

自 2018 年 1 月发布的 TypeScript 2.7 起,TypeScript 已支持在类型参数中使用尾部逗号。当 trailingComma 选项设为 all 时,Prettier 2.3 会打印这些逗号。若需兼容 TypeScript 2.7 或更早版本,请保留该选项为更保守的默认值 es5。请注意,TypeScript 目前仍不支持 在类型参数实例化(type argument instantiations)中使用尾部逗号。

// Input
export class BaseSingleLevelProfileTargeting<
T extends ValidSingleLevelProfileNode,
> {
// ...
}

// Prettier 2.2
export class BaseSingleLevelProfileTargeting<
T extends ValidSingleLevelProfileNode
> {
// ...
}

// Prettier 2.3 with --trailling-comma=all
export class BaseSingleLevelProfileTargeting<
T extends ValidSingleLevelProfileNode,
> {
// ...
}

允许包裹包含返回类型注解的非简洁箭头函数参数 (#10316 by @thorn0)

// Prettier 2.2
users.map(
(user: User): User => {
return user;
}
);

// Prettier 2.3
users.map((user: User): User => {
return user;
})

修正非空断言的括号问题 (#10337 by @thorn0)

包含非空断言的表达式中有时会缺失必要的括号,此问题已修复。

// Input
const myFunction2 = (key: string): number =>
({
a: 42,
b: 42,
}[key]!)

// Prettier 2.2 (invalid syntax)
const myFunction2 = (key: string): number =>
{
a: 42,
b: 42,
}[key]!;

// Prettier 2.3
const myFunction2 = (key: string): number =>
({
a: 42,
b: 42,
}[key]!);

缩进成员表达式和调用表达式头部的类型断言 (#10341 by @thorn0)

// Input
const accountCount = (findItemInSection(BOOKMARKED_PROJECTS_SECTION_NAME,
"My bookmarks") as TreeItem).getChildren().length;

// Prettier 2.2
const accountCount = (findItemInSection(
BOOKMARKED_PROJECTS_SECTION_NAME,
"My bookmarks"
) as TreeItem).getChildren().length;

// Prettier 2.3
const accountCount = (
findItemInSection(
BOOKMARKED_PROJECTS_SECTION_NAME,
"My bookmarks"
) as TreeItem
).getChildren().length;

支持 intrinsic 关键字 (#10390 by @sosukesuzuki)

// Input
type Uppercase<S extends string> = intrinsic;

// Prettier 2.2
Error: unknown type: "TSIntrinsicKeyword"

// Prettier 2.3
type Uppercase<S extends string> = intrinsic;

支持 TypeScript 4.2 (#10418, #10466, #10546, #10589 by @sosukesuzuki)

abstract 构造签名
// Input
type T = abstract new () => void;

// Prettier 2.2
SyntaxError: Unexpected token, expected ";" (1:19)

// Prettier 2.3
type T = abstract new () => void;

导入需求声明中的类型导入
// Input
import type A = require("A");

// Prettier 2.2
SyntaxError: Only ECMAScript imports may use 'import type'.

// Prettier 2.3
import type A = require("A");

修复联合类型与交叉类型中注释错位问题 (#10457 by @thorn0)

// Input
type Foo = "1" | "2" /* two */ | "3";

// Prettier 2.2
type Foo = "1" | "2" | /* two */ "3";

// Prettier 2.3
type Foo = "1" | "2" /* two */ | "3";

不为嵌套类型断言添加括号 (#10702 by @thorn0)

// Input
foo as unknown as Bar

// Prettier 2.2
(foo as unknown) as Bar;

// Prettier 2.3
foo as unknown as Bar;

通过 babel-ts 支持 TypeScript 4.3 (#10811 by @sosukesuzuki)

类元素中的 override 修饰符
class Foo extends  {
override method() {}
}
类中的静态索引签名([key: KeyType]: ValueType
class Foo {
static [key: string]: Bar;
}
类型声明中的 get/set 访问器
interface Foo {
set foo(value);
get foo(): string;
}

Flow

修复 declare export * from … 中缺失的分号 (#9767 by @fisker)

// Input
declare export * from "ES6_ExportAllFrom_Source2";

// Prettier 2.2
declare export * from "ES6_ExportAllFrom_Source2"

// Prettier 2.3
declare export * from "ES6_ExportAllFrom_Source2";

通过 babel-flow 支持函数中的 this 类型注解 (#10397 by @sosukesuzuki)

this 类型注解自 Babel 7.13 起已支持。

// Input
var foo: (this: boolean) => void;

// Prettier 2.2
SyntaxError: Unexpected token, expected ")" (1:15)

// Prettier 2.3
var foo: (this: boolean) => void;

修复范围格式化问题 (#10505 by @thorn0)

Prettier 在处理函数声明中的某些范围时存在问题,会抛出 SyntaxError。Prettier 2.3 可无错误地格式化这些情况。示例如下:

declare export function graphql<Props, Variables, Component: React$ComponentType<Props>>
// ^^^^^ range 1
(query: GQLDocument, config?: Config<Props, QueryConfigOptions<Variables>>):
(Component: Component) => React$ComponentType<$Diff<React$ElementConfig<Component>, {
data: Object|void,
// ^^^^ range 2
mutate: Function|void
}>>

支持 Flow 的索引访问类型 (#10594 by @gkz)

// Input
const x: Obj['foo'] = 1;

// Prettier 2.2
// Error: unsupported node type "IndexedAccessType"

// Prettier 2.3
const x: Obj["foo"] = 1;

支持 Flow 的可选索引访问类型 (#10788 by @gkz)

// Input
type T = Obj?.['foo'];

// Prettier 2.2
// Error: unsupported node type "OptionalIndexedAccessType"

// Prettier 2.3
type T = Obj?.['foo'];

JSON

--quote-props=preserve 时,JSON5 不使用智能引号 (#10323 by @thorn0)

quoteProps 选项设为 preservesingleQuotesfalse(默认值)时,字符串始终使用双引号输出,包括类似 "bla\"bla" 的情况。这实际上允许使用 --parser json5 处理"带注释和尾随逗号的 JSON"。

// Input
{
"char": "\"",
}

// Prettier 2.2
{
"char": '"',
}

// Prettier 2.3
{
"char": "\"",
}

更严格的 JSON 解析器 (#10346, #10443, #10456, #10434 by @fisker)

Prettier 内部使用 JavaScript 表达式解析器解析 JSON。因此 jsonjson5 解析器曾非常宽松,允许所有类型的 JavaScript 表达式。现在它们严格得多,但仍允许部分简单非标准语法(例如支持 JSON6,但排除多重减号:----123)。

// Input
[1, 2, 1 + 2]

// Prettier 2.2
[1, 2, 1 + 2]

// Prettier 2.3
SyntaxError: BinaryExpression is not allowed in JSON. (1:8)
> 1 | [1, 2, 1 + 2]
| ^

改进错误提示信息 (#10433 by @fisker)

// Input
{key: "foo" + "bar"}

// Prettier 2.2 (--parser=json-stringify)
SyntaxError: BinaryExpression is not allowed in JSON. (1:7)
> 1 | {key: "foo" + "bar"}
| ^

// Prettier 2.3
SyntaxError: BinaryExpression is not allowed in JSON. (1:7)
> 1 | {key: "foo" + "bar"}
| ^^^^^^^^^^^^^

修复 JSON 范围格式化的语法错误 (#10497 by @fisker)

// Input
[{ a: 1.0000}, {"b": 2.0000 }]
// ^^^^^^^^^^^ range

// Prettier 2.2
SyntaxError: Unexpected token (1:4)
> 1 | "b": 2.0000
| ^

// Prettier 2.3
[{ a: 1.0000}, { "b": 2.0 }]

CSS

修复自定义 CSS -custom-url() 调用中的绝对路径问题 (#9966 by @vjeux)

CSS 解析器会将其解析为 ["division", "absolute/path"] 而非单个 "/absolute/path" 标记,除非您处于 url() 调用中。由于我们在除法后添加了空格,导致生成错误路径。修复方案是避免在除法符号作为调用首字符时打印空格,这种处理方式应该是安全的。

/* Input */
-custom-url(/absolute/path)

/* Prettier 2.2 */
-custom-url(/ absolute/path)

/* Prettier 2.3 */
-custom-url(/absolute/path)

防止 @keyframes 参数被解析为 Less 变量 (#10773 by @tmazeika)

/* Input */
@keyframes :global(spin) {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

/* Prettier 2.2 */
@keyframes: global(spin){
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
};

/* Prettier 2.3 */
@keyframes :global(spin) {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

SCSS

修复括号内注释被破坏的问题 (#9710 by @fisker)

// Input
.simplification {
foo: (
calc() // not a comment anymore
);
}

// Prettier 2.2
.simplification {
foo: (calc() // not a comment anymore);
}

// Prettier 2.3
.simplification {
foo: (
calc() // not a comment anymore
);
}

修复键值为列表或映射的映射表 (#10005 by @fisker)

// Input
$map: (
('my list'): 'hello world',
);

// Prettier 2.2
TypeError: Cannot read property 'length' of undefined

// Prettier 2.3
$map: (
("my list"): "hello world",
);

Ember / Handlebars

保留波浪符注释 (~) (#9082 by @kangax, @fisker)

{{!-- Input --}}
{{~! Comment }}
{{! Comment ~}}
{{~! Comment ~}}

{{!-- Prettier 2.2 --}}
{{! Comment }}
{{! Comment }}
{{! Comment }}

{{!-- Prettier 2.3 --}}
{{~! Comment }}
{{! Comment ~}}
{{~! Comment ~}}

添加空白敏感模式 (#9885 by @dcyriller)

--html-whitespace-sensitivity strict
{{!-- Input --}}
<span>123 {{mustache}}</span>
<span>
123 {{mustache}}</span>
<span>123 {{mustache}}
</span>
<span>
123 {{mustache}}
</span>

{{!-- Prettier 2.2 --}}
<span>
123 {{mustache}}
</span>
<span>
123 {{mustache}}
</span>
<span>
123 {{mustache}}
</span>
<span>
123 {{mustache}}
</span>

{{!-- Prettier 2.3 --}}
<span>123 {{mustache}}</span>
<span>
123
{{mustache}}</span>
<span>123
{{mustache}}
</span>
<span>
123
{{mustache}}
</span>

将最终 blockParam 独立成行打印 (#9978 by @dcyriller)

{{!-- Input --}}
<MyComponent @prop={{true}} @prop2={{true}} @prop3={{true}} @prop4={{true}} as |thing|></MyComponent>
{{#block param hashKey=hashValue hashKey=hashValue hashKey=hashValue as |blockParam|}}
Hello
{{/block}}

{{!-- Prettier 2.2 --}}
<MyComponent
@prop={{true}}
@prop2={{true}}
@prop3={{true}}
@prop4={{true}} as |thing|
/>
{{#block
param
hashKey=hashValue
hashKey=hashValue
hashKey=hashValue as |blockParam|
}}
Hello
{{/block}}

{{!-- Prettier 2.3 --}}
<MyComponent
@prop={{true}}
@prop2={{true}}
@prop3={{true}}
@prop4={{true}}
as |thing|
/>
{{#block
param
hashKey=hashValue
hashKey=hashValue
hashKey=hashValue
as |blockParam|
}}
Hello
{{/block}}

修复属性格式化问题 (#10145 by @thorn0)

  • 修复属性及文本中 {{ 的转义问题

  • 修复含插值属性在单引号 ' 和双引号 " 间的选择问题

  • 修复 class 属性中输出 [object Object] 的错误

  • class 属性实现简单格式化,效果与 Prettier 在 v2.3.0 之前对 HTML 的格式化一致

{{!-- Input --}}
<div class="
foo"></div>
<div bar='"{{expr}}"'></div>
<div baz="\{{ non-expression }}"></div>

{{!-- Prettier 2.2 --}}
<div class="[object Object],foo"></div>
<div bar=""{{expr}}""></div>
<div baz="{{ non-expression }}"></div>

{{!-- Prettier 2.3 --}}
<div class="foo"></div>
<div bar='"{{expr}}"'></div>
<div baz="\{{ non-expression }}"></div>

将文本内容拆分为多行 (#10179 by @dcyriller)

{{!-- Input --}}
<div>
A long enough string to trigger a line break that would prevent wrapping more and more.
</div>

{{!-- Prettier 2.2 --}}
<div>
A long enough string to trigger a line break that would prevent wrapping more and more.
</div>

{{!-- Prettier 2.3 --}}
<div>
A long enough string to trigger a line break that would prevent wrapping more
and more.
</div>

保留数字字符引用 (如 A) (#10550 by @rwjblue and @thorn0)

{{! Input }}
<span class="stampFont" style="font-family: 'stampfont'">&#xf000;</span>

{{! Prettier 2.2 }}
<span class="stampFont" style="font-family: 'stampfont'"></span>

{{! Prettier 2.3 }}
<span class="stampFont" style="font-family: 'stampfont'">&#xf000;</span>

禁止断开起始双花括号与路径 (#10586 by @dcyriller)

{{!-- Input --}}
<GlimmerComponent
@errors={{or this.aVeryLongProperty (and this.aProperty (v-get bike "number" "message"))}}
data-test-beneficiary-account-number
/>
<GlimmerComponent
@progress={{aPropertyEngdingAfterEightiethColumnToHighlightAWeirdClosingParenIssue}}
/>

{{!-- Prettier 2.2 --}}
<GlimmerComponent
@errors={{
or
this.aVeryLongProperty
(and this.aProperty (v-get bike "number" "message"))
}}
data-test-beneficiary-account-number
/>
<GlimmerComponent
@progress={{
aPropertyEngdingAfterEightiethColumnToHighlightAWeirdClosingParenIssue
}}
/>

{{!-- Prettier 2.3 --}}
<GlimmerComponent
@errors={{or
this.aLongProperty
(and this.aProperty (v-get bike "number" "message"))
}}
data-test-beneficiary-account-number
/>
<GlimmerComponent
@progress={{aPropertyEngdingAfterEightiethColumnToHighlightAWeirdClosingParenIssue}}
/>

GraphQL

修复匿名操作中关键字后缺失的空格 (#10689 by @patriscus)

# Input
query ($unnamed: String) {
id
}

# Prettier 2.2
query($unnamed: String) {
id
}

# Prettier 2.3
query ($unnamed: String) {
id
}

Markdown

修复含字符串字面量的 JavaScript 代码块末尾多余空行 (#9736 by @fisker)

<!-- Input -->
Markdown

```js
"· "
```

<!-- Prettier 2.2 -->
Markdown

```js
"· ";

```

<!-- Prettier 2.3 -->
Markdown

```js
"· ";
```

修复空 Front Matter 的格式化问题 (#9791 by @fisker)

<!-- Input -->
---
---

# Title

a|b|c|
|:--|:-:|--:|
|d|e|f|

---

text

<!-- Prettier 2.2 -->
---
---
# Title

a|b|c|
|:--|:-:|--:|
|d|e|f|
---

text

<!-- Prettier 2.3 -->
---
---

# Title

| a | b | c |
| :-- | :-: | --: |
| d | e | f |

---

text

支持在 front matter 中使用 YAML 文档结束标记 (#9878 by @michaelbeaumont)

新增使用 ... 作为 front matter 结束分隔符的功能。

<!-- Input -->
---
title: Hello
slug: home
...

Markdown

<!-- Prettier 2.2 -->
---

title: Hello
slug: home
...

Markdown

<!-- Prettier 2.3 -->
---
title: Hello
slug: home
...

Markdown

YAML

修复锚点后接空行导致错误抛出 SyntaxError 的问题 (#10516 by @eemeli & @thorn0)

Prettier 此前无法解析此合法 YAML 文件。感谢 Eemeli Aro 在底层解析器中修复此错误。

# Input
key1: &default

subkey1: value1

key2:
<<: *default

# Prettier 2.2
SyntaxError: Nested mappings are not allowed in compact mappings (1:7)

# Prettier 2.3
key1: &default
subkey1: value1

key2:
<<: *default

API

格式化 .prettierrc 时将其视为 YAML 文件 (#8105 by @fisker)

.prettierrc 文件可使用 JSON 或 YAML 编写。此前 Prettier 格式化时默认使用 json 解析器,若内容为 YAML 会抛出 SyntaxError。现将其视为 YAML 文件处理。若内容实为 JSON,仍会按 JSON 格式输出(而非类 JSON 的 YAML)。

使用数组替代 concat (#9733 by @fisker, @thorn0)

为简化 AST 打印器代码,串联命令的数据结构已从 { type: 'concat', parts: Doc[] } 改为 Doc[]。旧格式已被弃用,但为保持兼容性,文档打印器仍支持该格式,且 doc.builders.concat(及其他构建函数)将在 Prettier 的下个主版本前继续使用旧格式。

若您开发插件,仅当插件需内省或修改组合文档时才需关注此变更。若需处理,请调整内省代码以兼容新格式,确保插件支持未来 Prettier 版本。极少数情况下(如插件调用其他插件打印嵌入式语言后内省返回文档)可能引发问题,但通常插件无需此类操作。

替换插件中的 concat(…) 调用时,可使用 ESLint 规则 prettier-doc/no-concat 自动修复。

// Prettier 2.2
myDoc = group(concat(["foo", line, "bar"]));

// Prettier 2.3
myDoc = group(["foo", line, "bar"]);

修复 lineSuffixBoundary 中间表示命令 (#10122 by @thorn0)

lineSuffixBoundary 命令的实现存在 bug,导致其效用受限:打印算法未正确将其识别为潜在换行点。此 bug 修复后,我们建议插件作者重新尝试该命令,观察其是否能简化尾随注释的打印逻辑。

// Input
group([
"let foo = [",
indent([
softline,
[lineSuffixBoundary, "item1,"],
line,
[lineSuffixBoundary, "item2,", lineSuffix(" // comment")],
line,
[lineSuffixBoundary, "item3"],
]),
softline,
"];",
])

// Prettier 2.2
let foo = [item1, item2, // comment
item3];

// Prettier 2.3
let foo = [
item1,
item2, // comment
item3
];

新增 indentIfBreak 中间表示命令 (#10221 by @thorn)

indentIfBreak(doc, { groupId })ifBreak(indent(doc), doc, { groupId }) 的优化形式。

简化 print 回调函数 (#10557 by @fisker)

插件打印器的 print 方法(即 print 回调)的第三个参数已更新。现在其首个参数可接受字符串或字符串数组。

要打印当前节点,请不带参数调用 print

 function print(path, options, print) {
const parts = [];
path.each((childPath) => {
- parts.push(print(childPath));
+ parts.push(print());
}, "children");
return parts;
}

要打印当前节点的属性,请使用 "property"["property"]

 function print(path, options, print) {
- return path.call(print, "property");
+ return print("property");
}

要打印当前节点的子属性,使用 ["property1", "property2"]

 function print(path, options, print) {
// print `node.child.child`
- return path.call(print, "child", "child");
+ return print(["child", "child"]);
}

另请参阅文档中的示例。

CLI

添加 CLI 选项以防止在未匹配的模式上出错 (#10058@daronmcintosh)

当输入的通配符模式未匹配到任何文件时,Prettier CLI 将不再显示错误。

# Prettier 2.2
$ npx prettier --check "prettier/docs/*.yaml"
Checking formatting...
[error] No files matching the pattern were found: "prettier/docs/*.yaml".
All matched files use Prettier code style!

# Prettier 2.3
$ npx prettier --check --no-error-on-unmatched-pattern "prettier/docs/*.yaml"
Checking formatting...
All matched files use Prettier code style!

新增调试注释附着问题的 CLI 标志 (#10124 by @thorn0)

新增 --debug-print-comments CLI 标志,并为 Playground 添加了相应功能。

实现可往返的 --debug-print-doc 输出 (#10169, #10177 by @thorn0)

本次改进旨在使 --debug-print-doc 的输出更接近实际文档生成代码(Prettier 的中间表示)。理想情况下,其输出内容应能在复制粘贴到 JS 文件后无需修改直接运行。虽然本次 PR 尚未完全达成该目标,但已非常接近。这将显著提升 --debug-print-doc 及 Playground 相关功能的实用性。

--find-config-path 找不到配置文件时打印错误信息 (#10208 by @fisker)

# Prettier 2.2
$ prettier --find-config-path /prettier.js
# Silently failed

# Prettier 2.3
$ prettier --find-config-path /prettier.js
[error] Can not find configure file for "/prettier.js"

优化格式化文件的长文件名显示 (#10217 by @fisker)

# Prettier 2.2
$ prettier tests/flow-repo/config_module_system_node_resolve_dirname --check
Checking formatting...
tests\flow-repo\config_module_system_node_resolve_dirname\custom_resolve_dir\tes
tests\flow-repo\config_module_system_node_resolve_dirname\custom_resolve_dir\tes
tests\flow-repo\config_module_system_node_resolve_dirname\subdir\custom_resolve_
All matched files use Prettier code style!

# Prettier 2.3
$ prettier tests/flow-repo/config_module_system_node_resolve_dirname --check
Checking formatting...
All matched files use Prettier code style!

不再跳过包含特殊词(如 constructor)的文件路径 (#10256 by @ashlkv)

由于经典对象属性检查漏洞(Prettier 2.0.0 引入),名称恰好与 Object.prototype 属性重合的目录此前会被 Prettier CLI 忽略。

# Prettier 2.2
$ prettier "js/constructor/*.js" --write
[error] No matching files. Patterns: js/constructor/*.js

# Prettier 2.3
$ prettier "js/constructor/*.js" --write
js/constructor/test.js 42ms