Skip to content

与 Prettier 的区别

在某些情况下,Biome 有意决定以与 Prettier 的输出不匹配的方式格式化代码。这些分歧如下所述。

¥In some cases, Biome has intentionally decided to format code in a way that doesn’t match Prettier’s output. These divergences are explained below.

Prettier 不会取消引用某些有效的 JavaScript 标识符对象属性。

Section titled Prettier 不会取消引用某些有效的 JavaScript 标识符对象属性。

¥Prettier doesn’t unquote some object properties that are valid JavaScript identifiers.

Prettier 和 Biome 取消引用有效的 JavaScript 标识符的对象和类属性。Prettier 取消引用仅有效的 ES5 标识符

¥Prettier and Biome unquote object and class properties that are valid JavaScript identifiers. Prettier unquotes only valid ES5 identifiers.

这是 ES2015 现已广泛使用的生态系统中的遗留限制。因此,我们决定在这里通过取消引用 ES2015+ 中所有有效的 JavaScript 标识符来分道扬镳。

¥This is a legacy restriction in an ecosystem where ES2015 is now widespread. Thus, we decided to diverge here by un-quoting all valid JavaScript identifiers in ES2015+.

一种可能的解决方法是引入配置来设置项目使用的 ECMAScript 版本。然后我们可以根据该版本调整取消引用的行为。将 ECMAScript 版本设置为 ES5 可以匹配 Prettier 的行为。

¥A possible workaround would be to introduce a configuration to set the ECMAScript version a project uses. We could then adjust the un-quoting behaviour based on that version. Setting the ECMAScript version to ES5 could match Prettier’s behaviour.

example.js
const obj = {
'a': true,
b: true,
"𐊧": true,
}

差异

¥Diff

example.js
const obj = {
a: true,
b: true,
"𐊧": true,
𐊧: true,
};

Prettier 在计算键中的赋值行为不一致。

Section titled Prettier 在计算键中的赋值行为不一致。

¥Prettier has an inconsistent behavior for assignment in computed keys.

Prettier 和 Biome 将一些赋值表达式括在括号之间,特别是在条件语句中。这允许 Biome 识别应该是比较的表达式。

¥Prettier and Biome enclose some assignment expressions between parentheses, particularly in conditionals. This allows Biome to identify an expression that should be a comparison.

Prettier 的行为不一致,因为它为对象属性的计算键中的赋值添加了括号,而没有为类属性的计算键添加括号,如下例所示:

¥Prettier has inconsistent behaviour because it adds parentheses for an assignment in a computed key of an object property and doesn’t for a computed key of a class property, as demonstrated by the following example:

输入

¥Input

example.js
a = {
[x = 0]: 1,
}
class C {
[x = 0] = 1
}

差异

¥Diff

example.js
a = {
[(x = 0)]: 1,
[x = 0]: 1,
};
class C {
[x = 0] = 1;
}

游乐场链接

¥Playground link

为了保持一致,我们决定分开并省略括号。或者,我们可以将任何赋值括在对象或类的计算键中。

¥To be consistent, we decided to diverge and omit the parentheses. Alternatively, we could enclose any assignment in a computed key of an object or of a class.

Prettier 会在箭头函数的类型参数中添加尾随逗号,即使这不是必需的。

Section titled Prettier 会在箭头函数的类型参数中添加尾随逗号,即使这不是必需的。

¥Prettier adds a trailing comma to type parameters of arrow functions even when it is not required.

在某些特定情况下,箭头函数的类型参数列表需要尾随逗号以将其与 JSX 元素区分开来。当提供默认类型时,不需要此尾随逗号。在这里,我们与 Prettier 不同,因为我们认为它更好地尊重了 Prettier 的初衷,即仅在需要时添加尾随逗号。

¥In some specific cases, a type parameter list of an arrow function requires a trailing comma to distinguish it from a JSX element. When a default type is provided, this trailing comma is not required. Here, we diverge from Prettier because we think it better respects the original intent of Prettier, which was to add a trailing comma only when required.

输入

¥Input

example.tsx
<T = unknown>() => {};

差异

¥Diff

example.tsx
<T = unknown,>() => {};
<T = unknown>() => {};

Prettier 对于带括号的非空断言可选链的行为不一致

Section titled Prettier 对于带括号的非空断言可选链的行为不一致

¥Prettier has an inconsistent behavior for parenthesized non-null-asserted optional chains

在 TypeScript 中,非空断言运算符 ! 允许断言值非空。当应用于可选链时,断言适用于整个链,无论是否存在括号,从而使 (a.?.b)!a.?.b! 等效。

¥In TypeScript, the non-null assertion operator ! allows asserting that a value is non-null. When applied on an optional chain, the assertion applies to the entire chain regardless of the presence of parentheses, making equivalent (a.?.b)! and a.?.b!.

根据 Prettier,前面的代码示例已经格式良好。Prettier 用于强制括号的存在或不存在。这看起来像是错失了规范代码的机会。

¥The previous code examples are already well-formatted, according to Prettier. Prettier is used to enforce the presence or the absence of parentheses. This looks like a missed opportunity to normalize the code.

此外,即使括号括住非空断言,Prettier 也不会删除括号。相反,它将运算符移到括号外。

¥Moreover, Prettier doesn’t remove the parentheses even when they enclose the non-null assertion. Instead, it moves the operator outside the parentheses.

输入:

¥Input:

example.ts
a.?.b!
(a.?.b)!
(a.?.b!)

差异

¥Diff

example.ts
a.?.b!
(a.?.b)!
a.?.b!
(a.?.b!)
a.?.b!

Prettier 格式化无效语法

Section titled Prettier 格式化无效语法

¥Prettier formats invalid syntaxes

Prettier 基于 Babel 的 JavaScript 和 TypeScript 解析非常松散,允许多个错误 将被忽略。Biome 的解析器有意比 Prettier 解析器更严格。它正确识别了以下语法错误:

¥Prettier’s Babel-based parsing for JavaScript and TypeScript is very loose and allows multiple errors to be ignored. Biome’s parser is intentionally stricter than the Prettier parser. It correctly identifies the following syntax errors:

  • 函数不能有重复的修饰符

    ¥A function cannot have duplicate modifiers

  • 属性修饰符的顺序无效

    ¥invalid order of properties’ modifiers

  • 函数声明不允许有主体

    ¥Function declarations are not allowed to have bodies

  • 非抽象类不能具有抽象属性

    ¥non-abstract classes cannot have abstract properties

  • 无法分配可选链

    ¥An optional chain cannot be assigned

  • 不能在接口的类型参数上设置 const 修饰符

    ¥The const modifier cannot be set on a type parameter of an interface

  • 顶层返回

    ¥top-level return

  • 等。

    ¥etc.

在 Prettier 中,这些错误不被视为解析错误,AST 仍然使用适当的节点构建 “correctly”。格式化时,Prettier 将这些节点视为正常节点并相应地格式化它们。

¥In Prettier, these errors aren’t considered parse errors, and the AST is still built “correctly” with the appropriate nodes. When formatting, Prettier treats these nodes as normal and formats them accordingly.

在 Biome 中,解析错误会导致 Bogus 节点,这些节点可能包含任意数量的有效节点、无效节点和/或原始字符。格式化时,Biome 将伪节点视为有效的纯文本,将它们逐字打印到结果代码中而不进行任何格式化,因为尝试格式化它们可能不正确并导致语义变化。

¥In Biome, the parsing errors result in Bogus nodes, which may contain any number of valid nodes, invalid nodes, and/or raw characters. When formatting, Biome treats bogus nodes as effectively plain text, printing them out verbatim into the resulting code without any formatting since attempting to format them could be incorrect and cause semantic changes.

对于类属性,Prettier 的当前解析策略还使用布尔字段作为修饰符,这意味着每种修饰符只能存在一个(可访问性修饰符存储为单个字符串)。打印时,Prettier 会查看布尔值列表并决定再次打印哪些修饰符。Biome 反而保留了一个修饰符列表,这意味着重复项会保留并可以进行分析(因此会出现有关重复修饰符和排序的解析错误消息)。打印出虚假节点时,此列表保持不变,打印出未格式化的文本会导致这些修饰符继续存在。

¥For class properties, Prettier’s current parsing strategy also uses boolean fields for modifiers, meaning only one of each kind of modifier can ever be present (accessibility modifiers are stored as a single string). When printing, Prettier looks at the list of booleans and decides which modifiers to print out again. Biome instead keeps a list of modifiers, meaning duplicates are kept around and can be analyzed (hence the parsing error messages about duplicate modifiers and ordering). When printing out the bogus nodes, this list is kept intact, and printing out the unformatted text results in those modifiers continuing to exist.

Biome 可以通过多种方式解决这个问题。一种可能性是在格式化时尝试解释 Bogus 节点并从中构造有效节点。如果可以构建有效节点,则它会像平常一样格式化该节点,否则,它会像当前一样逐字打印虚假文本。但是,这很混乱,并在格式化程序中引入了一种没有意义的解析逻辑形式。

¥There are ways that Biome can address this. One possibility is to try to interpret the Bogus nodes when formatting and construct valid nodes out of them. If a valid node can be built, then it would just format that node like normal, otherwise, it prints the bogus text verbatim as it does currently. However, this is messy and introduces a form of parsing logic into the formatter that is not meaningful.

另一种选择是将某种形式的 “语法有效的虚假节点” 引入解析器,它接受这些类型的纯语义错误(重复修饰符、非抽象类中的抽象属性)。

¥Another option is to introduce some form of “syntactically-valid bogus node” into the parser, which accepts these kinds of purely semantic errors (duplicate modifiers, abstract properties in non-abstract classes).

它将继续像平常一样构建节点(有效地匹配 Prettier 中的行为),但将它们存储在一种新的虚假节点中,包括诊断信息。格式化时,这些特定的伪节点只会尝试格式化内部节点,然后在出现错误时回退(现有的 format_or_verbatim 实用程序已经这样做了)。这使解析和格式化逻辑彼此分离,但给解析器带来了更多复杂性,允许将无效状态视为半有效。

¥It would continue to build the nodes like normal (effectively matching the behavior in Prettier) but store them inside of a new kind of bogus node, including the diagnostics along with it. When formatting, these particular bogus nodes would just attempt to format the inner node and then fallback if there’s an error (the existing format_or_verbatim utility would do this already). This keeps the parsing and formatting logic separate from each other but introduces more complexity to the parser, allowing invalid states to be considered semi-valid.

类属性上的重复修饰符

Section titled 类属性上的重复修饰符

¥Duplicate modifiers on class properties

输入

¥Input

example.ts
// Multiple accessibility modifiers
class Foo {
private public a = 1;
}
// Declare function with body
declare function foo ( ) { }
// Invalid use of abstract
class Bar {
abstract foo ;
}
// Duplicate Readonly
class Read {
readonly readonly x: number;
}

差异

¥Diff

example.ts
// Multiple accessibility modifiers
class Foo {
private public a = 1;
private a = 1;
}
// Declare function with body
declare function foo ( ) { }
declare function foo() {};
// Invalid use of abstract
class Bar {
abstract foo ;
abstract foo;
}
// Duplicate Readonly
class Read {
readonly readonly x: number;
readonly x: number;
}

¥Assignment to an optional chain

输入

¥Input

example.js
(a?.b) = c;

差异

¥Diff

example.js
a?.b = c;
(a?.b) = c;

接口类型参数的修饰符不正确

Section titled 接口类型参数的修饰符不正确

¥Incorrect modifier for the type parameters of an interface

输入

¥Input

example.js
interface L<in const T> {}

差异

¥Diff

example.js
interface L<const in T> {}
interface L<in const T> {}

¥Top-level return

example.js
return someVeryLongStringA && someVeryLongStringB && someVeryLongStringC && someVeryLongStringD
example.js
return someVeryLongStringA && someVeryLongStringB && someVeryLongStringC && someVeryLongStringD
return (
someVeryLongStringA &&
someVeryLongStringB &&
someVeryLongStringC &&
someVeryLongStringD
);

¥Erroneous self-increment and self-decrement

输入

¥Input

example.js
(1)++;
example.js
1++;
(1)++;

在非抽象类中使用 abstract 修饰符

Section titled 在非抽象类中使用 abstract 修饰符

¥Use of abstract modifier in non-abstract classes

输入

¥Input

example.js
class C {
abstract f() : number;
}

差异

¥Diff

example.js
class C {
abstract f(): number;
abstract f() : number;
}

Prettier 在 TypeScript 和 Babel 解析之间存在不一致

Section titled Prettier 在 TypeScript 和 Babel 解析之间存在不一致

¥Prettier has inconsistencies between TypeScript and Babel parsing

Prettier 支持多种不同的 JavaScript 和 TypeScript 代码解析器,所有这些解析器都旨在与 estree 规范 兼容。大多数情况下,Prettier 使用 Babel 作为 JavaScript 代码的默认解析器,但在解析 TypeScript 时,它会首先尝试使用 TypeScript 自己的解析器,之后才会启用 TypeScript 并回退到 Babel。虽然 TypeScript 解析器通常与 estree 兼容,但它并不完全兼容,并且 这可能会导致一些不一致 会影响 Prettier 创建的输出。一般来说,这些都被认为是 Prettier 本身的错误,因为无论使用哪个解析器,输出都应该相同。

¥Prettier supports a number of different parsers for JavaScript and TypeScript code, all of which are meant to be compatible with the estree spec. Most of the time, Prettier uses Babel as the default parser for JavaScript code, but when parsing TypeScript, it will try to use TypeScript’s own parser first and only fall back to Babel with TypeScript enabled afterward. While the TypeScript parser is generally compatible with estree, it’s not exact, and this can lead to some inconsistencies that affect the output that Prettier creates. In general, these are considered bugs in Prettier itself, since the output should be the same regardless of which parser is used.

Biome 实现了自己的解析功能,可以处理所有形式的 JavaScript 和 TypeScript 代码,这意味着两者之间不应该存在任何不一致。但是,在将 TypeScript 代码库从 Prettier 迁移到 Biome 时,由于 Prettier 解析器之间的差异,某些格式可能会发生变化。

¥Biome implements its own parsing that handles all forms of JavaScript and TypeScript code, meaning there should not be any inconsistencies between the two. However, when migrating a TypeScript codebase from Prettier to Biome, it’s possible that some formatting will appear to have changed because of those discrepancies between parsers from Prettier.

这些情况不被视为 Biome 中的错误或不兼容性。如果使用 Prettier 中的 typescript 解析器设置格式化的代码仅出现不同,但在使用 babel 和/或 babel-ts 时匹配,则 Biome 认为输出是兼容的。

¥These cases are not considered bugs or incompatibilities in Biome. If formatted code only appears different using the typescript parser setting in Prettier, but matches when using babel and/or babel-ts, then Biome considers the output to be compatible.

例如,请考虑这种情况,使用 Biome 和 Prettier 3.1.0 以及 typescript 解析器进行格式化:

¥As an example, consider this case, formatted using Biome and Prettier 3.1.0 with the typescript parser:

输入

¥Input

example.js
function someFunctionName(
someLongBreakingParameterName,
anotherLongParameterName,
) {
return isEqual(a?.map(([t, _]) => t?.id), b?.map(([t, _]) => t?.id));
}

差异

¥Diff

example.js
function someFunctionName(
someLongBreakingParameterName,
anotherLongParameterName,
) {
return isEqual(a?.map(([t, _]) => t?.id), b?.map(([t, _]) => t?.id));
return isEqual(
a?.map(([t, _]) => t?.id),
b?.map(([t, _]) => t?.id),
);
}

带有 TypeScript 解析器的 Prettier 选择在一行上编写 isEqual 调用,而 Biome 将 Prettier 的输出与 babelbabel-ts 解析器相匹配。因此,这不被视为与 Biome 不兼容,而是被视为 Prettier 中的错误。

¥Prettier with the TypeScript parser chooses to write the isEqual call on a single line, while Biome matches the output of Prettier with the babel and babel-ts parsers. As such, this is not considered an incompatibility with Biome and is instead considered a bug in Prettier.