跳至主内容区

Prettier 2.0「2020」

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

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

更合理的默认配置、更友好的命令行工具、更智能的启发式算法。对了,还有 TypeScript 3.8 支持。

经过长期审慎的考量,我们决定变更 trailingCommaarrowParensendOfLine 选项的默认值。我们让 CLI 更加直观易用。最终,我们停止了对 Node.js 10 以下版本的支持——这些旧版本已成为巨大的维护负担和贡献者障碍。详情请阅读下文。

重要更新

JavaScript

改进方法链断行启发式算法 (#6685 by @mmkal)

此前,任何长度达到或超过三个方法调用的链式调用都会自动折成多行。新算法基于链式调用中参数的复杂度(而非单纯的长度)进行判断。现在,当链式调用的参数难以一目了然(例如包含函数或深度嵌套对象)时才会断行,否则允许保持单行形式。相关背景和示例请参阅历史 issue #3197#4765#1565#4125

为获得最佳效果,请确保 printWidth 选项的值不要设置过高。

// Prettier 1.19
if (
foo
.one()
.two()
.three() ===
bar
.four()
.five()
.six()
) {
// ...
}

// Prettier 2.0
if (foo.one().two().three() === bar.four().five().six()) {
// ...
}

Closure 风格类型转换的终极修复 (#7791 by @thorn0, #7011 by @evilebottnawi)

v1.6.0 起,Prettier 就尝试避免破坏这些 JSDoc 类型断言,但效果不稳定。随着基于 JSDoc 的类型检查日益普及,我们不断收到关于此语法的新错误报告。这些问题的棘手之处在于:表达式必需的圆括号不属于 AST 结构,导致 Prettier 难以检测其存在。

最终,我们通过 Babel 解析器的 createParenthesizedExpressions 选项,使用非标准节点在 AST 中表示圆括号,从而修复了所有已报告问题。

因此,当使用 flowtypescript 解析器时,Prettier 将无法识别 JSDoc 类型转换语法——这种限制是合理的,因为该语法在 Flow 和 TypeScript 文件中基本没有使用场景。

// Input
const nestedAssertions = /** @type {MyType} */
(/** @type {unknown} */
(x));

// Prettier 1.19
const nestedAssertions /** @type {MyType} */ /** @type {unknown} */ = x;

// Prettier 2.0
const nestedAssertions = /** @type {MyType} */ (/** @type {unknown} */ (x));

该语法的参考文档:Closure CompilerTypeScript (配合 --checkJs)

TypeScript

TypeScript 3.8 支持 (#7631 by @thorn0, #7764 by @sosukesuzuki, #7804 by @sosukesuzuki)

Prettier 现已支持 TypeScript 3.8 新增语法:

CLI

将传入的 glob 模式作为文件名检查后再处理为 glob (#7587 by @fisker)

由于 Linux 文件名可包含几乎所有字符,foo*.js[bar].css 这类字符串都是合法文件名。此前如需格式化 [bar].css 文件,必须使用 glob 转义语法:prettier "\[bar].css"(此写法在 Windows 无效)或 prettier "[[]bar].css"。这导致重要场景(如 lint-staged 传递绝对路径时)无法正常工作。现在当 CLI 收到 glob 参数时,会先尝试将其解析为字面文件名。

支持目录扩展(包括当前目录 .)(#7660 by @thorn0)

现在可通过 prettier --write . 格式化当前目录及其子目录下的所有支持文件。支持目录名与文件名、glob 模式混合使用(如 prettier src "test/*.spec.js" foo.js)。

文件处理顺序也有所调整:此前所有文件按字母排序后处理,现在按传入路径顺序处理(每个路径解析出的文件列表仍会排序,但不再进行全局合并排序)。

错误报告机制同步更新:此前仅在所有模式均未匹配文件时报错,2.0 版本开始会对每个未匹配模式单独报错。

重大变更

API

修复配置覆盖规则匹配点文件的问题 (#5813 by @chrisblossom)

此前配置覆盖规则不适用于以点开头的文件。

停止支持 Node 10 以下版本 (#6908 by @fisker)

最低要求 Node 版本提升至 10.13.0,贡献者无需再为兼容 Node 4 调整测试。

trailingComma 默认值改为 es5 (#6963 by @fisker)

在 2.0 版本之前,Prettier 默认会尽可能避免尾随逗号。 这使得生成的 JavaScript 代码能够兼容如今非常老旧的环境(如 IE8),但也意味着错过了一些优势

Prettier 自早期起就提供了配置尾随逗号的选项,而关于更改默认值的提议已经存在超过三年。 最终,在 Prettier v2.0 中,默认值由 none 改为 es5

如果仍希望保持旧的行为,请将 Prettier 配置为 { "trailingComma": "none" }。 随着 JavaScript 生态的进一步成熟,在未来的 Prettier 主要版本中,默认值有可能改为 all(即添加更多尾随逗号)。

插件 API:prettier.util 的变更 (#6993 by @fisker)

  • prettier.util.mapDoc 已被移除。
    请改用 prettier.doc.utils.mapDoc

  • prettier.util.isNextLineEmpty 已更新。
    请使用 isNextLineEmpty(text, node, locEnd) 替代 isNextLineEmpty(text, node, options)

  • prettier.util.isPreviousLineEmpty 已更新。
    请使用 isPreviousLineEmpty(text, node, locStart) 替代 isPreviousLineEmpty(text, node, options)

  • prettier.util.getNextNonSpaceNonCommentCharacterIndex 已更新。
    请使用 getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd) 替代 getNextNonSpaceNonCommentCharacterIndex(text, node, options)

arrowParens 的默认值更改为 always (#7430 by @kachkaev)

自 1.9 版本起,Prettier 就提供了一个选项,用于始终为箭头函数参数添加括号。 在 2.0 版本中,此行为已成为默认设置。

// Input
const fn = (x) => y => x + y;

// Prettier 1.19
const fn = x => y => x + y;

// Prettier 2.0
const fn = (x) => (y) => x + y;

乍看之下,在上面的独立示例中避免使用括号似乎是更好的选择,因为这样能减少视觉干扰。 然而,当 Prettier 移除括号后,添加类型注解、额外参数、默认值或各种其他内容会变得更加困难。 在编辑实际代码库时,一致使用括号能提供更好的开发体验,因此这一变更是合理的。

我们鼓励使用新的默认值,但如果仍希望保持旧的行为,请将 Prettier 配置为 { "arrowParens": "avoid" }

endOfLine 的默认值更改为 lf (#7435 by @kachkaev)

Prettier 的早期版本统一使用 Unix 风格换行符\n,即 LF)格式化所有文件。 在 #472 提交中,这一行为被修改为允许保留 Windows 换行符(\r\n,即 CRLF)。

自 Prettier 1.15 版本起,用户可通过 endOfLine 选项配置换行符风格。 为保持向后兼容,该选项默认值设为 auto,即 Prettier 会保留文件中已存在的换行符风格。

这意味着 Prettier 会将单个文件中的混合换行符统一转换为首行使用的风格。 但不同文件间的换行符仍可能不一致。 此外,不同操作系统的贡献者可能无意中更改已提交文件的换行符,而 Prettier 不会阻止这种行为。 这会导致 git diff 产生大量变更,使文件的逐行历史记录(git blame)难以追溯。

我们建议您采用 endOfLine 的新默认值 lf。 您可能需要查阅选项文档来确保项目仓库配置正确。 这将避免仓库中出现混合换行符,并防止 git blame 功能失效。 若仍需旧版行为,请配置 { "endOfLine": "auto" }

若使用 Travis CI,请注意 .travis.yml 中的 autocrlf 选项

缓存插件搜索结果 (#7485 by @fisker)

此前 Prettier 每次调用 prettier.format 时都会重新扫描文件系统查找插件。现在搜索结果将被缓存,可通过调用 prettier.clearConfigCache() 清除缓存。

移除废弃选项及选项值 (#6993, #7511, #7533, #7535, #7536 by @fisker)

  • 选项:

  • 选项值:

    • parserbabylon(v1.16.0 重命名为 babel),postcss(v1.7.1 重命名为 css),typescript-eslinttypescript 的旧别名)
    • proseWraptrue(v1.9.0 重命名为 always),false(v1.9.0 重命名为 never)
    • trailingCommatrue(v0.19.0 重命名为 es5),false(v0.19.0 重命名为 none)

移除 prettier.getSupportInfoversion 参数 (#7620 by @thorn0)

自 Prettier 1.8.0 起,开发者可以通过向 prettier.getSupportInfo 传递版本号来获取历史版本支持的语言、选项等信息。这个看似有趣但实际用途有限的 API 一直存在维护问题,现已在 Prettier 2.0.0 中移除。

其他变更

JavaScript

始终在 function 关键字后添加空格 (#3903 by @j-f1, @josephfrazier, @sosukesuzuki, @thorn0; #7516 by @bakkot)

此前,函数声明中会在 function 关键字后添加空格,但函数表达式不会。为了保持一致性,现在所有 function 关键字后都会添加空格。唯一例外是生成器声明,此时 function* 被视为完整单词。

// Prettier 1.19
const identity = function(value) {
return value;
};
function identity(value) {
return value;
}
const f = function<T>(value: T) {};
const g = function*() {};

// Prettier 2.0
const identity = function (value) {
return value;
};
function identity(value) {
return value;
}
const f = function <T>(value: T) {};
const g = function* () {};

修复带注释的标签语句格式化不稳定问题 (#6984 by @clement26695)

// Input
loop1:
//test
const i = 3;

// Prettier 1.19 (first output)
loop1: //test
const i = 3;

// Prettier 1.19 (second output)
//test
loop1: const i = 3;

// Prettier 2.0 (first and second outputs)
//test
loop1: const i = 3;

修复模板字面量中逻辑、二元和序列表达式的格式化问题 (#7010 by @evilebottnawi)

// Input
`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${foo || bar}`;
`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${foo | bar}`;
`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${(foo, bar)}`;

// Prettier 1.19
`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${foo ||
bar}`;
`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${foo |
bar}`;
`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${(foo,
bar)}`;

// Prettier 2.0
`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${
foo || bar
}`;
`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${
foo | bar
}`;
`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${
(foo, bar)
}`;

修复逻辑表达式格式化不稳定问题 (#7026 by @thorn0)

// Input
const averredBathersBoxroomBuggyNurl =
bifornCringerMoshedPerplexSawder === 1 ||
(askTrovenaBeenaDependsRowans === 2 || glimseGlyphsHazardNoopsTieTie === 3);

// Prettier 1.19 (first output)
const averredBathersBoxroomBuggyNurl =
bifornCringerMoshedPerplexSawder === 1 ||
askTrovenaBeenaDependsRowans === 2 || glimseGlyphsHazardNoopsTieTie === 3;

// Prettier 1.19 (second output)
const averredBathersBoxroomBuggyNurl =
bifornCringerMoshedPerplexSawder === 1 ||
askTrovenaBeenaDependsRowans === 2 ||
glimseGlyphsHazardNoopsTieTie === 3;

// Prettier 2.0 (first and second outputs)
const averredBathersBoxroomBuggyNurl =
bifornCringerMoshedPerplexSawder === 1 ||
askTrovenaBeenaDependsRowans === 2 ||
glimseGlyphsHazardNoopsTieTie === 3;

throw 格式化为类似 return 的形式 (#7070 by @sosukesuzuki)

// Input
function foo() {
throw this.hasPlugin("dynamicImports") && this.lookahead().type === tt.parenLeft.right;
}

// Prettier 1.19
function foo() {
throw this.hasPlugin("dynamicImports") &&
this.lookahead().type === tt.parenLeft.right;
}


// Prettier 2.0
function foo() {
throw (
this.hasPlugin("dynamicImports") &&
this.lookahead().type === tt.parenLeft.right
);
}

修复嵌套在三元表达式条件中的三元表达式缩进问题 (#7087 by @thorn0)

// Input
const foo = (number % 10 >= 2 && (number % 100 < 10 || number % 100 >= 20) ?
kochabCooieGameOnOboleUnweave : annularCooeedSplicesWalksWayWay)
? anodyneCondosMalateOverateRetinol : averredBathersBoxroomBuggyNurl;

// Prettier 1.19
const foo = (number % 10 >= 2 && (number % 100 < 10 || number % 100 >= 20)
? kochabCooieGameOnOboleUnweave
: annularCooeedSplicesWalksWayWay)
? anodyneCondosMalateOverateRetinol
: averredBathersBoxroomBuggyNurl;

// Prettier 2.0
const foo = (
number % 10 >= 2 && (number % 100 < 10 || number % 100 >= 20)
? kochabCooieGameOnOboleUnweave
: annularCooeedSplicesWalksWayWay
)
? anodyneCondosMalateOverateRetinol
: averredBathersBoxroomBuggyNurl;

调整装饰器的函数组合逻辑 (#7138 by @brainkim)

由于装饰器会修改后续代码行,将装饰器调用的参数拆分为多行会模糊装饰器与其目标之间的关系,导致代码可读性降低。因此,#6033 引入的函数组合逻辑已调整为排除装饰器调用。

// Input
export class Item {
@OneToOne(() => Thing, x => x.item)
thing!: Thing;
}

// Prettier 1.19
export class Item {
@OneToOne(
() => Thing,
x => x.item,
)
thing!: Thing;
}

// Prettier 2.0
export class Item {
@OneToOne(() => Thing, x => x.item)
thing!: Thing;
}

修复带注释的空 return 语句分号位置 (#7140 by @sosukesuzuki)

// Input
return // comment
;

// Prettier 1.19
return // comment;

// Prettier 2.0
return; // comment

在 HTML 模板字面量中保留空白符意义 (#7208 by @saschanaz)

此前 Prettier 会为每个 HTML 模板字符串添加换行,可能导致渲染的 HTML 中出现意外空白。除非指定 --html-whitespace-sensitivity ignore 选项,否则不再出现此问题。

// Input
html`<div>`;
html` <span>TEXT</span> `;

// Prettier 1.19
html`
<div></div>
`;
html`
<span>TEXT</span>
`;

// Prettier 2.0
html`<div></div>`;
html` <span>TEXT</span> `;

移除 yield JSX 时不必要的括号 (#7367 by @cola119)

// Input
function* f() {
yield <div>generator</div>;
}

// Prettier 1.19
function* f() {
yield (<div>generator</div>);
}

// Prettier 2.0
function* f() {
yield <div>generator</div>;
}

在默认导出声明中保留逗号表达式周围的括号 (#7491 by @fisker)

省略这些括号会导致代码无效。

// Input
export default (1, 2);

// Prettier 1.19
export default 1, 2;

// Prettier 2.0
export default (1, 2);

修复可选链表达式周围的括号边界情况 (#7500 by @thorn0)

// Input
(foo?.bar)();
new (foo?.bar)();

// Prettier 1.19
foo?.bar();
new foo?.bar();

// Prettier 2.0
(foo?.bar)();
new (foo?.bar)();

在JSX的条件表达式中不为undefined添加括号 (#7504 by @fisker)

此前除null外的所有表达式都会添加括号,现在undefined也获得相同处理。

// Input
foo ? <span>loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong jsx</span> :
undefined
foo ? <span>loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong jsx</span> :
null

// Prettier 1.19
foo ? (
<span>
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong jsx
</span>
) : (
undefined
);
foo ? (
<span>
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong jsx
</span>
) : null;

// Prettier 2.0
foo ? (
<span>
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong jsx
</span>
) : undefined;
foo ? (
<span>
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong jsx
</span>
) : null;

保持赋值语句/变量声明中的注释位置 (#7709 by @sosukesuzuki)

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

// Prettier 1.19
const foo /* comments */ = bar;

// Prettier 2.0
const foo = /* comments */ bar;

TypeScript

将Babel作为TypeScript的替代解析器 (#6400 by @JounQin & @thorn0)

新增了parser选项值babel-ts,该解析器使用Babel的TypeScript插件。babel-ts支持TypeScript尚未实现的JavaScript特性(如ECMAScript提案中的私有方法和访问器),但其错误恢复能力较弱且实战检验不如typescript解析器充分。虽然Babel的TypeScript插件相当成熟,但两种解析器生成的AST并非100%兼容。我们已尽力处理差异,但仍可能存在格式化不一致甚至错误的情况。呼吁用户协助发现问题并提交issue。长期来看,这将促进统一AST格式,为JavaScript解析器生态带来更稳固的基础。

修复箭头函数返回类型注解中复杂类型的格式化问题 (#6901 by @sosukesuzuki)

// Input
export const getVehicleDescriptor = async (
vehicleId: string
): Promise<
Collections.Parts.PrintedCircuitBoardAssembly["attributes"] & undefined
> => {};

// Prettier 1.19
export const getVehicleDescriptor = async (
vehicleId: string
): Promise<Collections.Parts.PrintedCircuitBoardAssembly["attributes"] &
undefined> => {};

// Prettier 2.0
export const getVehicleDescriptor = async (
vehicleId: string
): Promise<
Collections.Parts.PrintedCircuitBoardAssembly["attributes"] & undefined
> => {};

原样输出纯JSDoc类型而非抛出错误 (#7020 by @thorn0)

此错误恢复相关修复对从Flow迁移至TypeScript的用户尤为实用。

// Input
function fromFlow(arg: ?Maybe) {}

// Prettier 1.19
Error: unknown type: "TSJSDocNullableType"

// Prettier 2.0
function fromFlow(arg: ?Maybe) {}

元组中rest元素后不再打印尾随逗号 (#7075 by @thorn0)

  • rest元素始终是元组类型的最后一个元素,其后不能添加任何内容

  • 虽然TypeScript允许此逗号,但Babel无法解析

  • 在函数参数和数组解构中,此类逗号会导致语法错误,元组中保留它将产生不一致

// Input
type ValidateArgs = [
{
[key: string]: any;
},
string,
...string[],
];

// Prettier 1.19
type ValidateArgs = [
{
[key: string]: any;
},
string,
...string[],
];

// Prettier 2.0
type ValidateArgs = [
{
[key: string]: any;
},
string,
...string[]
];

修复带注释的变量声明中箭头函数的缩进问题 (#7094 by @sosukesuzuki)

该问题可能出现在不使用分号的代码风格中,若变量声明后的语句为避免ASI问题添加了前置分号

// Input
const foo = () => {
return
}

// foo
;[1,2,3].forEach(bar)

// Prettier 1.19
const foo = () => {
return;
};

// foo
[1, 2, 3].forEach(bar);

// Prettier 2.0
const foo = () => {
return;
};

// foo
[1, 2, 3].forEach(bar);

修复函数类型中注释的打印格式 (#7104 by @thorn0)

// Input
type f1 = (
currentRequest: {a: number},
// TODO this is a very very very very long comment that makes it go > 80 columns
) => number;

// Prettier 1.19
type f1 = (currentRequest: {
a: number;
}) => // TODO this is a very very very very long comment that makes it go > 80 columns
number;

// Prettier 2.0
type f1 = (
currentRequest: { a: number }
// TODO this is a very very very very long comment that makes it go > 80 columns
) => number;

修复类函数节点注释的格式问题 (#7144 by @armano2)

// Input
interface foo1 {
bar1/* foo */ (/* baz */) // bat
bar2/* foo */ ? /* bar */ (/* baz */) /* bat */;
bar3/* foo */ (/* baz */) /* bat */
bar4/* foo */ ? /* bar */ (bar: /* baz */ string): /* bat */ string;
/* foo */ (/* bar */): /* baz */ string;
/* foo */ (bar: /* bar */ string): /* baz */ string;
/* foo */ new /* bar */ (a: /* baz */ string): /* bat */ string
/* foo */ new /* bar */ (/* baz */): /* bat */ string
}

type foo7 = /* foo */ (/* bar */) /* baz */ => void
type foo8 = /* foo */ (a: /* bar */ string) /* baz */ => void
let foo9: new /* foo */ (/* bar */) /* baz */ => string;
let foo10: new /* foo */ (a: /* bar */ string) /* baz */ => string;

// Prettier 1.19
interface foo1 {
bar1 /* foo */ /* baz */(); // bat
bar2 /* foo */ /* bar */ /* baz */ /* bat */?();
bar3 /* foo */ /* baz */() /* bat */;
bar4 /* foo */?(/* bar */ bar: /* baz */ string): /* bat */ string;
/* foo */ (): /* bar */ /* baz */ string;
/* foo */ (bar: /* bar */ string): /* baz */ string;
/* foo */ new (/* bar */ a: /* baz */ string): /* bat */ string;
/* foo */ new (): /* bar */ /* baz */ /* bat */ string;
}

type foo7 = /* foo */ () => /* bar */ /* baz */ void;
type foo8 = /* foo */ (a: /* bar */ string) => /* baz */ void;
let foo9: new () => /* foo */ /* bar */ /* baz */ string;
let foo10: new (/* foo */ a: /* bar */ string) => /* baz */ string;

// Prettier 2.0
interface foo1 {
bar1 /* foo */(/* baz */); // bat
bar2 /* foo */ /* bar */?(/* baz */) /* bat */;
bar3 /* foo */(/* baz */) /* bat */;
bar4 /* foo */?(/* bar */ bar: /* baz */ string): /* bat */ string;
/* foo */ (/* bar */): /* baz */ string;
/* foo */ (bar: /* bar */ string): /* baz */ string;
/* foo */ new (/* bar */ a: /* baz */ string): /* bat */ string;
/* foo */ new (/* baz */): /* bar */ /* bat */ string;
}

type foo7 = /* foo */ (/* bar */) => /* baz */ void;
type foo8 = /* foo */ (a: /* bar */ string) => /* baz */ void;
let foo9: new (/* bar */) => /* foo */ /* baz */ string;
let foo10: new (/* foo */ a: /* bar */ string) => /* baz */ string;
// Input
abstract class Test {
abstract foo12 /* foo */ (a: /* bar */ string): /* baz */ void
abstract foo13 /* foo */ (/* bar */) /* baz */
}

// Prettier 1.19
Error: Comment "foo" was not printed. Please report this error!

// Prettier 2.0
abstract class Test {
abstract foo12 /* foo */(a: /* bar */ string): /* baz */ void;
abstract foo13 /* foo */(/* bar */); /* baz */
}

修复省略模板类型的映射类型打印问题 (#7221 by @cola119)

// Input
type A = { [key in B] };

// Prettier 1.19
type A = { [key in B]: };

// Prettier 2.0
type A = { [key in B] };

修复索引签名打印的边界情况 (#7228 by @cola119)

尽管没有类型注解或多个参数的索引签名在 TypeScript 中无效,但 TypeScript 解析器支持此语法。为了与之前的错误恢复工作保持一致,Prettier 现在确保其输出在这些情况下仍可解析。之前的版本会产生无法解析的代码。

// Input
type A = { [key: string] };
type B = { [a: string, b: string]: string; };

// Prettier 1.19
type A = { [key: string]: };
type B = { [a: stringb: string]: string };

// Prettier 2.0
type A = { [key: string] };
type B = { [a: string, b: string]: string };

修复空类型参数中注释的打印问题 (#7729 by @sosukesuzuki)

// Input
const a: T</* comment */> = 1;

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

// Prettier 2.0
const a: T</* comment */> = 1;

Flow

添加对 symbol 的支持 (#7472 by @fisker)

flow@0.114.0 引入了一种新的 AST 节点类型,现在 Prettier 已支持。

// Input
const x: symbol = Symbol();

// Prettier after updating Flow, but without this fix
Error: unknown type: "SymbolTypeAnnotation"

// Prettier 2.0
const x: symbol = Symbol();

添加对装饰器的支持 (#7482 by @fisker)

// Input
/* @flow */
@decorator4
class Foo {
@decorator1
method1() {}

@decorator2
@decorator3
method2() {}
}

// Prettier 1.19
SyntaxError: Unexpected token `@`, expected the token `class` (2:1)

// Prettier 2.0
/* @flow */
@decorator4
class Foo {
@decorator1
method1() {}

@decorator2
@decorator3
method2() {}
}

修复私有类字段声明 (#7484 by @fisker)

// Input
class Foo {
#privateProp: number;
}

// Prettier 1.19
class Foo {
privateProp: number;
}

// Prettier 2.0
class Foo {
#privateProp: number;
}

CSS

不再将 CSS 选择器中的元素名转换为小写 (#6947 by @ark120202)

此前,Prettier 已保留未知元素名的大小写,但会将 HTML 元素名转换为小写。当 CSS 应用于区分大小写的文档且存在与 HTML 中同名的元素(如 NativeScript 中的情况)时,这会导致问题。现在,Prettier 始终保留原始大小写。

/* Input */
Label {
}

/* Prettier 1.19 */
label {
}

/* Prettier 2.0 */
Label {
}

SCSS

修复在映射的最后一个注释后添加多余逗号的问题 (#6918 by @fisker)

此前,当 trailingComma 设置为 es5 时,会在 SCSS 映射的最后一个注释后添加一个多余的逗号。

// Input
$my-map: (
'foo': 1, // Comment
'bar': 2, // Comment
);

// Prettier 1.19
$my-map: (
"foo": 1,
// Comment
"bar": 2,
// Comment,
);

// Prettier 2.0
$my-map: (
"foo": 1,
// Comment
"bar": 2,
// Comment
);

修复 SCSS 拼接中的空格问题 (#7211 by @sasurau4)

// Input
a {
background-image: url($test-path + 'static/test.jpg');
}

// Prettier 1.19
a {
background-image: url($test-path+"static/test.jpg");
}

// Prettier 2.0
a {
background-image: url($test-path + "static/test.jpg");
}

Less

通过更新 postcss-less 修复多个长期存在的问题 (#6981 by @fisker, #7021 by @evilebottnawi, @thorn0)

!important 被移出 mixin 调用参数的问题 (#3544)。

在传递给 mixin 调用的规则集中,注释导致重复分号的问题 (#3096)。

::before 在 mixin 调用参数中被破坏的问题 (#5791)。

HTML

pre 标签中的注释导致后续闭合标签格式错误的问题 (#5959 by @selvazhagan)

<!-- Input -->
<details>
<pre>
<!-- TEST -->
</pre></details>

<!-- Prettier 1.19 -->
<details>
<pre>
<!-- TEST -->
</pre></details</details
>

<!-- Prettier 2.0 -->
<details>
<pre>
<!-- TEST -->
</pre>
</details>

不再将冒号视为标签名中的命名空间前缀分隔符 (#7273 by @fisker)

在 HTML5 中,冒号在标签名中已无特殊含义。此前 Prettier 以 XML 方式处理冒号(视为命名空间前缀分隔符),现在已修正。实际效果是:对于祖先标签名含冒号的标签,现在会视为常规 HTML 标签——如果是已知标准标签,其名称可转为小写且可对其空白敏感性做出假设;Prettier 未知的自定义元素则保留名称大小写,若 --html-whitespace-sensitivity 设为 css 则视为行内元素。

<!-- Input -->
<with:colon>
<div> looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block </div>
<DIV> block </DIV><DIV> block </DIV> <DIV> block </DIV><div> block </div><div> block </div>
<pre> pre pr
e</pre>
<textarea> pre-wrap pr
e-wrap </textarea>
<span> looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline </span>
<span> inline </span><span> inline </span> <span> inline </span><span> inline </span>
</with:colon>

<!-- Prettier 1.19 -->
<with:colon>
<div>
looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block
</div>
<DIV> block </DIV><DIV> block </DIV> <DIV> block </DIV><div> block </div
><div> block </div>
<pre> pre pr e</pre>
<textarea> pre-wrap pr e-wrap </textarea>
<span>
looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline
</span>
<span> inline </span><span> inline </span> <span> inline </span
><span> inline </span>
</with:colon>

<!-- Prettier 2.0 -->
<with:colon>
<div>
looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block
</div>
<div>block</div>
<div>block</div>
<div>block</div>
<div>block</div>
<div>block</div>
<pre>
pre pr
e</pre
>
<textarea>
pre-wrap pr
e-wrap </textarea
>
<span>
looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline
</span>
<span> inline </span><span> inline </span> <span> inline </span
><span> inline </span>
</with:colon>

不再因 HTML 格式错误而抛出异常 (#7293 by @fisker)

<!-- Input -->
<div><span>
<

<!-- Prettier 1.19 -->
TypeError: Cannot read property 'start' of null

<!-- Prettier 2.0 -->
<div><span> < </span></div>

修复 srcset 解析错误 (#7295 by @fisker)

<!-- Input -->
<img

srcset="
/media/examples/surfer-240-200.jpg
">

<!-- Prettier 1.19 -->
Error: Mixed descriptor in srcset is not supported

<!-- Prettier 2.0 -->
<img srcset="/media/examples/surfer-240-200.jpg" />

修复 pre 标签内未闭合标签导致的异常抛出问题 (#7392 by @fisker)

<!-- Input -->
<pre><br /></pre>
<pre><hr></pre>

<!-- Prettier 1.19 -->
TypeError: Cannot read property 'start' of null

<!-- Prettier 2.0 -->
<pre><br /></pre>
<pre><hr /></pre>

修复自闭合标签格式不一致的问题 (#7395 by @fisker)

<!-- Input -->
<span><input type="checkbox"/> </span>
<span><span><input type="checkbox"/></span></span>
<span><input type="checkbox"/></span>

<!-- Prettier 1.19 -->
<span><input type="checkbox" /> </span>
<span
><span><input type="checkbox"/></span
></span>
<span><input type="checkbox"/></span>

<!-- Prettier 2.0 -->
<span><input type="checkbox" /> </span>
<span
><span><input type="checkbox" /></span
></span>
<span><input type="checkbox" /></span>

修复 table 标签后意外添加空行的问题 (#7461 by @ikatyang)

<!-- Input -->
<table><tr>
</tr>
</table><div>Should not insert empty line before this div</div>

<!-- Prettier 1.19 -->
<table>
<tr></tr>
</table>

<div>Should not insert empty line before this div</div>

<!-- Prettier 2.0 -->
<table>
<tr></tr>
</table>
<div>Should not insert empty line before this div</div>

格式化 HTML class 属性的值 (#7555 by @fisker)

<!-- Input -->
<div class=" foo
bar baz"></div>
<div class="
another element with so many classes
even can not fit one line
really a lot and lot of classes
"></div>

<!-- Prettier 1.19 -->
<div
class=" foo
bar baz"
></div>
<div
class="
another element with so many classes
even can not fit one line
really a lot and lot of classes
"
></div>

<!-- Prettier 2.0 -->
<div class="foo bar baz"></div>
<div
class="another element with so many classes even can not fit one line really a lot and lot of classes"
></div>

格式化 HTML style 属性的值 (#7556 by @fisker)

<!-- Input -->
<div style=" color : red;
display :inline ">
</div>
<div style=" color : red;
display :inline; height: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0; ">
</div>

<!-- Prettier 1.19 -->
<div
style=" color : red;
display :inline "
></div>
<div
style=" color : red;
display :inline; height: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0; "
></div>


<!-- Prettier 2.0 -->
<div style="color: red; display: inline;"></div>
<div
style="
color: red;
display: inline;
height: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
"
></div>

支持 <!-- prettier ignore --> 忽略文本内容 (#7654 by @graemeworthy)

此前该指令仅对标签生效。现可用于保护宏命令和预处理器指令不被格式化破坏。

<!-- Input -->
<!-- prettier-ignore -->
A super long string that has been marked as ignore because it was probably generated by some script.

<!-- Prettier 1.19 -->
<!-- prettier-ignore -->
A super long string that has been marked as ignore because it was probably
generated by some script.

<!-- Prettier 2.0 -->
<!-- prettier-ignore -->
A super long string that has been marked as ignore because it was probably generated by some script.
<!-- Input -->
<!-- prettier-ignore -->
| Dogs | Cats | Weasels | Bats | Pigs | Mice | Hedgehogs | Capybaras | Rats | Tigers |
| ---- | ---- | ------- | ---- | ---- | ---- | --------- | --------- | ---- | ------ |
| 1 | 1 | 0 | 0 | 1 | 1 | 5 | 16 | 4 | 0 |

<!-- Prettier 1.19 -->
<!-- prettier-ignore -->
| Dogs | Cats | Weasels | Bats | Pigs | Mice | Hedgehogs | Capybaras | Rats |
Tigers | | ---- | ---- | ------- | ---- | ---- | ---- | --------- | --------- |
---- | ------ | | 1 | 1 | 0 | 0 | 1 | 1 | 5 | 16 | 4 | 0 |

<!-- Prettier 2.0 -->
<!-- prettier-ignore -->
| Dogs | Cats | Weasels | Bats | Pigs | Mice | Hedgehogs | Capybaras | Rats | Tigers |
| ---- | ---- | ------- | ---- | ---- | ---- | --------- | --------- | ---- | ------ |
| 1 | 1 | 0 | 0 | 1 | 1 | 5 | 16 | 4 | 0 |

Vue

支持格式化包含 JSX 脚本的 Vue 单文件组件 (#7180 by @sosukesuzuki)

<!-- Input -->
<script lang="jsx">
export default {
data: () => ({
message: 'hello with jsx'
}),
render(h) {



return <div>{this.message}</div>
}
}
</script>

<!-- Prettier 1.19 -->
<script lang="jsx">
export default {
data: () => ({
message: 'hello with jsx'
}),
render(h) {



return <div>{this.message}</div>
}
}
</script>

<!-- Prettier 2.0 -->
<script lang="jsx">
export default {
data: () => ({
message: "hello with jsx"
}),
render(h) {
return <div>{this.message}</div>;
}
};
</script>

避免将属性中唯一的字符串字面量换行打印 (#7479 by @fisker)

<!-- Input -->
<template>
<MyComponent
:attr1="`loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong ${template} literal value`"
:attr2="'loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string literal value'"/>
</template>

<!-- Prettier 1.19 -->
<template>
<MyComponent
:attr1="
`loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong ${template} literal value`
"
:attr2="
'loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string literal value'
"
/>
</template>

<!-- Prettier 2.0 -->
<template>
<MyComponent
:attr1="`loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong ${template} literal value`"
:attr2="'loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string literal value'"
/>
</template>

修复 Vue 表达式的缩进问题 (#7781 by @fisker)

<!-- Input -->
<template>
<MyComponent v-if="
long_long_long_long_long_long_long_condition_1 && long_long_long_long_long_long_long_condition_2 &&
long_long_long_long_long_long_long_condition_3 &&
long_long_long_long_long_long_long_condition_4
"
:attr="
`loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog string 1` +
`loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog string 2`
"
/>
</template>

<!-- Prettier 1.19 -->
<template>
<MyComponent
v-if="
long_long_long_long_long_long_long_condition_1 &&
long_long_long_long_long_long_long_condition_2 &&
long_long_long_long_long_long_long_condition_3 &&
long_long_long_long_long_long_long_condition_4
"
:attr="
`loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog string 1` +
`loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog string 2`
"
/>
</template>

<!-- Prettier 2.0 -->
<template>
<MyComponent
v-if="
long_long_long_long_long_long_long_condition_1 &&
long_long_long_long_long_long_long_condition_2 &&
long_long_long_long_long_long_long_condition_3 &&
long_long_long_long_long_long_long_condition_4
"
:attr="
`loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog string 1` +
`loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog string 2`
"
/>
</template>

Angular

为 AngularJS 1.x 常用指令提供非官方基础支持 (#6869 by @thorn0)

尽管旧版 AngularJS 和新版 Angular 的表达式语言存在语法不兼容问题(如一次性绑定和 | 运算符优先级),但两种语言整体兼容性足以让遗留项目和混合型 AngularJS 项目受益于 Prettier。此前当 Prettier 使用 Angular 解析器格式化 AngularJS 模板时,仅会格式化插值表达式。现在,部分最常用的 AngularJS 指令也能被格式化了,包括:ng-ifng-showng-hideng-classng-style

<!-- Input -->
<div ng-if="$ctrl .shouldShowWarning&&!$ctrl.loading ">Warning!</div>

<!-- Prettier 1.19 -->
<div ng-if="$ctrl .shouldShowWarning&&!$ctrl.loading ">Warning!</div>

<!-- Prettier 2.0 -->
<div ng-if="$ctrl.shouldShowWarning && !$ctrl.loading">Warning!</div>

修复 i18n 属性的格式化问题 (#7371 by @thorn0)

Prettier 1.19 已添加i18n 属性的格式化支持,但将右引号换行显示会破坏自定义 ID。此问题现已修复。

<!-- Input -->
<div i18n-prop="Special-property|This is a special property with much important information@@MyTextId"
prop="My Text"></div>

<!-- Prettier 1.19 -->
<div
i18n-prop="
Special-property|This is a special property with much important
information@@MyTextId
"
prop="My Text"
></div>

<!-- Prettier 2.0 -->
<div
i18n-prop="
Special-property|This is a special property with much important
information@@MyTextId"
prop="My Text"
></div>

Handlebars (alpha)

修复 ConcatStatement 中的多余换行问题 (#7051 by @dcyriller)

{{!-- Input --}}
<a href="a-very-long-href-from-a-third-party-marketing-platform{{id}}longer-than-eighty-chars">Link</a>

{{!-- Prettier 1.19 --}}
<a
href="a-very-long-href-from-a-third-party-marketing-platform
{{id}}
longer-than-eighty-chars"
>
Link
</a>

{{!-- Prettier 2.0 --}}
<a
href="a-very-long-href-from-a-third-party-marketing-platform{{id}}longer-than-eighty-chars"
>
Link
</a>

以及

{{!-- Input --}}
<div class="hello {{if goodbye true}} {{if goodbye false}} {{if goodbye true}} {{if goodbye false}} {{if goodbye true}}">
Hello
</div>

{{!-- Prettier 1.19 --}}
<div
class="hello
{{if goodbye true}}

{{if goodbye false}}

{{if goodbye true}}

{{if goodbye false}}

{{if goodbye true}}"
>
Hello
</div>

{{!-- Prettier 2.0 --}}
<div
class="hello {{if goodbye true}} {{if goodbye false}} {{if goodbye true}} {{if
goodbye
false
}} {{if goodbye true}}"
>
Hello
</div>

修复 mustache 标签下坠问题 (#7052 by @dcyriller)

{{!-- Input --}}
<GlimmerComponent @progress={{aPropertyEndingAfterEightiethColumnToHighlightAWeirdClosingParenIssue}} />

{{!-- Prettier 1.19 --}}
<GlimmerComponent
@progress={{aPropertyEndingAfterEightiethColumnToHighlightAWeirdClosingParenIssue
}}
/>

{{!-- Prettier 2.0 --}}
<GlimmerComponent
@progress={{aPropertyEndingAfterEightiethColumnToHighlightAWeirdClosingParenIssue}}
/>

改进 MustacheStatement 的打印效果 (#7157 by @dcyriller)

{{!-- Input --}}
<p>Hi here is your name, as it will be displayed {{firstName}} {{lastName}} , welcome!</p>

{{!-- Prettier 1.19 --}}
<p>
Hi here is your name, as it will be displayed {{firstName}} {{lastName
}} , welcome!
</p>

{{!-- Prettier 2.0 --}}
<p>
Hi here is your name, as it will be displayed {{firstName}} {{
lastName
}} , welcome!
</p>

新增 prettier-ignore 支持 (#7275 by @chadian)

{{! Input }}
{{! prettier-ignore }}
<div>
"hello! my parent was ignored"
{{#my-crazy-component "shall" be="preserved"}}
<This

is="preserved"
/>
{{/my-crazy-component}}
</div>

{{#a-normal-component isRestoredTo = "order" }}
<ThisWillBeNormal backTo = "normal" />
{{/a-normal-component}}

{{! Prettier 1.19 }}
{{! prettier-ignore }}
<div>
"hello! my parent was ignored"
{{#my-crazy-component "shall" be="preserved"}}
<This is="preserved" />
{{/my-crazy-component}}
</div>

{{#a-normal-component isRestoredTo="order"}}
<ThisWillBeNormal backTo="normal" />
{{/a-normal-component}}

{{! Prettier 2.0 }}
{{! prettier-ignore }}
<div>
"hello! my parent was ignored"
{{#my-crazy-component "shall" be="preserved"}}
<This

is="preserved"
/>
{{/my-crazy-component}}
</div>

{{#a-normal-component isRestoredTo='order'}}
<ThisWillBeNormal backTo='normal' />
{{/a-normal-component}}

支持在 HTML 中打印内联 Handlebars (#7306 by @dcyriller)

<!-- Input -->
<script type="text/x-handlebars-template">
{{component arg1='hey' arg2=(helper this.arg7 this.arg4) arg3=anotherone arg6=this.arg8}}
</script>

<!-- Prettier 1.19 -->
<script type="text/x-handlebars-template">
{{component arg1='hey' arg2=(helper this.arg7 this.arg4) arg3=anotherone arg6=this.arg8}}
</script>

<!-- Prettier 2.0 -->
<script type="text/x-handlebars-template">
{{component
arg1='hey'
arg2=(helper this.arg7 this.arg4)
arg3=anotherone
arg6=this.arg8
}}
</script>

修复 AttrNode 属性值被剥离的问题 (#7552 by @bantic)

{{!-- Input --}}
<ul class="abc
def">
</ul>

{{!-- Prettier 1.19 --}}
<ul class></ul>

{{!-- Prettier 2.0 --}}
<ul class="abc
def">
</ul>

保留空格控制字符 (#7575 by @mahirshah)

{{!-- Input --}}
{{~#if bar}}
if1
{{~else~}}
else
{{~/if~}}

{{!-- Prettier 1.19 --}}
{{#if bar}}
if1
{{else}}
else
{{/if}}

{{!-- Prettier 2.0 --}}
{{~#if bar}}
if1
{{~else~}}
else
{{~/if~}}

GraphQL

改进接口分隔符的检测逻辑 (#7305 by @fisker)

尽管使用逗号分隔多个接口实现是已弃用的语法,但为支持遗留用例,Prettier 会保留原始分隔符而不会擅自将逗号替换为 & 符号。然而此前该逻辑存在缺陷,可能导致输出中出现错误的分隔符。此问题现已修复。

# Input
type Type1 implements A, B
# { & <-- Removing this comment changes the separator in 1.19
{a: a}

type Type2 implements A, B & C{a: a}

# Prettier 1.19
type Type1 implements A & B {
# { & <-- Removing this comment changes the separator in 1.19
a: a
}

type Type2 implements A & B & C {
a: a
}

# Prettier 2.0
type Type1 implements A, B {
# { & <-- Removing this comment changes the separator in 1.19
a: a
}

type Type2 implements A, B & C {
a: a
}

Markdown

正确处理零基列表 (#6852 by @evilebottnawi)

<!-- Input -->
0. List
1. List
2. List

<!-- Prettier 1.19 -->
0. List
1. List
1. List

<!-- Prettier 2.0 -->
0. List
1. List
2. List

修复列表项后起始标签导致的 HTML 格式错误 (#7181#7220 by @sasurau4)

此前,当 Prettier 格式化紧跟在列表项后的 HTML 标签时,会错误插入缩进导致开闭标签关系断裂。现在 Prettier 不再进行任何更改。

<!-- Input -->
- A list item.
<details><summary>Summary</summary>
<p>

- A list item.

</p>
</details>

- A list item
<blockquote>asdf</blockquote>

<!-- Prettier 1.19 -->
- A list item.

<details><summary>Summary</summary>
<p>

- A list item.

</p>
</details>

- A list item
<blockquote>asdf</blockquote>

<!-- Prettier 2.0 -->
- A list item.
<details><summary>Summary</summary>
<p>

- A list item.

</p>
</details>

- A list item
<blockquote>asdf</blockquote>

修复多行脚注的格式化问题 (#7203 by @sasurau4)

<!-- Input -->
Here's a statement[^footnote].

[^footnote]:
Here's a multi-line footnote walking back the above statement, and showing
how it's all totally bollocks.

<!-- Prettier 1.19 -->
Here's a statement[^footnote].

[^footnote]:

Here's a multi-line footnote walking back the above statement, and showing
how it's all totally bollocks.

<!-- Prettier 2.0 -->
Here's a statement[^footnote].

[^footnote]:
Here's a multi-line footnote walking back the above statement, and showing
how it's all totally bollocks.

MDX

新增对 JSX 片段的支持 (#6398 by @JounQin)

<!-- Input -->
<>
test <World /> test
</> 123

<!-- Prettier 1.19 -->
<>
test <World /> test
</> 123

<!-- Prettier 2.0 -->
<>
test <World /> test
</> 123

修复 Prettier 1.19 引入的 JSX 解析错误 (#6949 by @Tigge & @thorn0)

当遇到无法解析为 HTML 的 JSX 元素(例如 <Tag value={{a: 'b'}}>test</Tag>)时,MDX 解析会出现错误

<!-- Input -->

<Tag value={ {a : 'b' } }>test</ Tag>

<Foo bg={ 'red' } >
<div style={{ display: 'block'} }>
<Bar >hi </Bar>
{ hello }
{ /* another comment */}
</div>
</Foo>

<!-- Prettier 1.19 -->

SyntaxError: Unexpected closing tag "Tag". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags (1:35)
> 1 | <Tag value={ {a : 'b' } }>test</ Tag>

<!-- Prettier 2.0 -->

<Tag value={{ a: "b" }}>test</Tag>

<Foo bg={"red"}>
<div style={{ display: "block" }}>
<Bar>hi </Bar>
{hello}
{/* another comment */}
</div>
</Foo>

CLI

支持文件扩展名 .cjs.yaml.sed (#7210 by @sosukesuzuki)

# Prettier 1.19
$ prettier test.cjs
test.cjs[error] No parser could be inferred for file: test.cjs

# Prettier 2.0
$ prettier test.cjs
"use strict";
console.log("Hello, World!");

在子目录执行时遵守 --ignore-path 参数 (#7588 by @heylookltsme)

现在过滤被忽略文件时,文件名将相对于 --ignore-path 指定的路径(而非当前工作目录)进行解析

移除 --stdin 参数 (#7668 by @thorn0)

该 CLI 参数从未被正式文档记录,其设计功能是让 Prettier CLI 从标准输入读取内容,但 Prettier CLI 在未提供文件路径或 glob 模式时默认就会这样做。因此该参数是冗余的。移除后若在命令中使用此参数,将看到警告:"Ignored unknown option"。此警告仅为提示信息,不影响命令正常执行和退出码。