基本原理
Prettier 是一个有态度的代码格式化程序。本文档解释了它的一些选择。
¥Prettier is an opinionated code formatter. This document explains some of its choices.
Prettier 关心的是什么
¥What Prettier is concerned about
正确性
¥Correctness
Prettier 的第一个要求是输出与格式化前行为完全相同的有效代码。请报告 Prettier 未能遵循这些正确性规则的任何代码 - 这是一个需要修复的错误!
¥The first requirement of Prettier is to output valid code that has the exact same behavior as before formatting. Please report any code where Prettier fails to follow these correctness rules — that’s a bug which needs to be fixed!
字符串
¥Strings
双引号还是单引号?Prettier 会选择转义次数最少的那个。"It's gettin' better!"
,不是 'It\'s gettin\' better!'
。如果出现平局或字符串不包含任何引号,Prettier 默认使用双引号(但可以通过 singleQuote 选项更改)。
¥Double or single quotes? Prettier chooses the one which results in the fewest number of escapes. "It's gettin' better!"
, not 'It\'s gettin\' better!'
. In case of a tie or the string not containing any quotes, Prettier defaults to double quotes (but that can be changed via the singleQuote option).
JSX 有自己的引号选项:jsxSingleQuote。JSX 源于 HTML,其中属性引号的主要用法是双引号。浏览器开发工具也遵循此约定,始终显示带有双引号的 HTML,即使源代码使用单引号也是如此。一个单独的选项允许对 JS 使用单引号,对 "HTML" (JSX) 使用双引号。
¥JSX has its own option for quotes: jsxSingleQuote. JSX takes its roots from HTML, where the dominant use of quotes for attributes is double quotes. Browser developer tools also follow this convention by always displaying HTML with double quotes, even if the source code uses single quotes. A separate option allows using single quotes for JS and double quotes for "HTML" (JSX).
Prettier 维护字符串转义的方式。例如,"🙂"
不会被格式化为 "\uD83D\uDE42"
,反之亦然。
¥Prettier maintains the way your string is escaped. For example, "🙂"
won’t be formatted into "\uD83D\uDE42"
and vice versa.
空行
¥Empty lines
事实证明,空行很难自动生成。Prettier 采用的方法是按照原始源代码中的方式保留空行。还有两个附加规则:
¥It turns out that empty lines are very hard to automatically generate. The approach that Prettier takes is to preserve empty lines the way they were in the original source code. There are two additional rules:
Prettier 将多个空行折叠成一个空行。
¥Prettier collapses multiple blank lines into a single blank line.
块(和整个文件)开头和结尾的空行被删除。(不过,文件总是以一个换行符结尾。)
¥Empty lines at the start and end of blocks (and whole files) are removed. (Files always end with a single newline, though.)
多行对象
¥Multi-line objects
默认情况下,Prettier 的打印算法会在适合的情况下在一行中打印表达式。不过,对象在 JavaScript 中有很多不同的用途,有时保持多行确实有助于提高可读性。例如,参见 对象列表、嵌套配置、stylesheets 和 键控方法。我们无法为所有这些情况找到好的规则,因此如果原始源代码中的 {
和第一个键之间有一个换行符,Prettier 会保持对象多行。这样做的结果是长的单行对象会自动展开,但短的多行对象永远不会折叠。
¥By default, Prettier’s printing algorithm prints expressions on a single line if they fit. Objects are used for a lot of different things in JavaScript, though, and sometimes it really helps readability if they stay multiline. See object lists, nested configs, stylesheets and keyed methods, for example. We haven’t been able to find a good rule for all those cases, so Prettier instead keeps objects multiline if there’s a newline between the {
and the first key in the original source code. A consequence of this is that long singleline objects are automatically expanded, but short multiline objects are never collapsed.
提示:如果你有一个多行对象,你想合并成一行:
¥Tip: If you have a multiline object that you’d like to join up into a single line:
const user = {
name: "John Doe",
age: 30,
};
...你需要做的就是删除 {
之后的换行符:
¥…all you need to do is remove the newline after {
:
const user = { name: "John Doe",
age: 30
};
…然后运行 Prettier:
¥…and then run Prettier:
const user = { name: "John Doe", age: 30 };
如果你想再次使用多行,请在 {
之后添加一个换行符:
¥And if you’d like to go multiline again, add in a newline after {
:
const user = {
name: "John Doe", age: 30 };
...然后运行 Prettier:
¥…and run Prettier:
const user = {
name: "John Doe",
age: 30,
};
♻️ 关于格式化可逆性的说明
¥♻️ A note on formatting reversibility
对象字面量的半手动格式化实际上是一种解决方法,而不是功能。它的实现只是因为当时没有找到好的启发式方法并且需要紧急修复。然而,作为一般策略,Prettier 避免了这样的不可逆格式化,因此团队仍在寻找能够完全消除这种行为或至少减少应用这种行为的情况的启发式方法。
¥The semi-manual formatting for object literals is in fact a workaround, not a feature. It was implemented only because at the time a good heuristic wasn’t found and an urgent fix was needed. However, as a general strategy, Prettier avoids non-reversible formatting like that, so the team is still looking for heuristics that would allow either to remove this behavior completely or at least to reduce the number of situations where it’s applied.
What does reversible 意思是?一旦对象字面量变为多行,Prettier 就不会将其折叠回去。如果在 Prettier 格式的代码中,我们向对象字面量添加一个属性,运行 Prettier,然后改变主意,删除添加的属性,然后再次运行 Prettier,我们最终可能会得到与初始格式不同的格式。这种无用的更改甚至可能包含在提交中,这正是 Prettier 旨在防止的情况。
¥mean? Once an object literal becomes multiline, Prettier won’t collapse it back. If in Prettier-formatted code, we add a property to an object literal, run Prettier, then change our mind, remove the added property, and then run Prettier again, we might end up with a formatting not identical to the initial one. This useless change might even get included in a commit, which is exactly the kind of situation Prettier was created to prevent.
装饰器
¥Decorators
就像对象一样,装饰器用于很多不同的事情。有时,将装饰器写在同一行上方是有意义的。我们还没有找到一个很好的规则来解决这个问题,所以 Prettier 让你的装饰器像你写的那样定位(如果它们适合在线)。这并不理想,但却是解决难题的实用方法。
¥Just like with objects, decorators are used for a lot of different things. Sometimes it makes sense to write decorators above the line they're decorating, sometimes it’s nicer if they're on the same line. We haven’t been able to find a good rule for this, so Prettier keeps your decorator positioned like you wrote them (if they fit on the line). This isn’t ideal, but a pragmatic solution to a difficult problem.
@Component({
selector: "hero-button",
template: `<button>{{ label }}</button>`,
})
class HeroButtonComponent {
// These decorators were written inline and fit on the line so they stay
// inline.
@Output() change = new EventEmitter();
@Input() label: string;
// These were written multiline, so they stay multiline.
@readonly
@nonenumerable
NODE_TYPE: 2;
}
有一个例外:类。我们认为为它们内联装饰器是没有意义的,所以它们总是被移到它们自己的行中。
¥There’s one exception: classes. We don’t think it ever makes sense to inline the decorators for them, so they are always moved to their own line.
// Before running Prettier:
@observer class OrderLine {
@observable price: number = 0;
}
// After running Prettier:
@observer
class OrderLine {
@observable price: number = 0;
}
注意:Prettier 1.14.x 及更早版本尝试自动移动装饰器,因此如果你在代码上运行了较旧的 Prettier 版本,则可能需要在这里和那里手动加入一些装饰器以避免不一致:
¥Note: Prettier 1.14.x and older tried to automatically move your decorators, so if you've run an older Prettier version on your code you might need to manually join up some decorators here and there to avoid inconsistencies:
@observer
class OrderLine {
@observable price: number = 0;
@observable
amount: number = 0;
}
最后一件事:TC39 有 尚未决定装饰器是在 export
之前还是之后。与此同时,Prettier 支持:
¥One final thing: TC39 has not yet decided if decorators come before or after export
. In the meantime, Prettier supports both:
@decorator export class Foo {}
export @decorator class Foo {}
模板字面量
¥Template literals
模板字面量可以包含插值。不幸的是,决定是否适合在插值中插入换行符取决于模板的语义内容 - 例如,在自然语言句子中间引入换行符通常是不可取的。由于 Prettier 本身没有足够的信息来做出此决定,因此它使用类似于用于对象的启发式方法:如果插值中已经存在换行符,它只会将插值表达式拆分为多行。
¥Template literals can contain interpolations. Deciding whether it's appropriate to insert a linebreak within an interpolation unfortunately depends on the semantic content of the template - for example, introducing a linebreak in the middle of a natural-language sentence is usually undesirable. Since Prettier doesn't have enough information to make this decision itself, it uses a heuristic similar to that used for objects: it will only split an interpolation expression across multiple lines if there was already a linebreak within that interpolation.
这意味着像下面这样的字面量不会被分成多行,即使它超出了打印宽度:
¥This means that a literal like the following will not be broken onto multiple lines, even if it exceeds the print width:
`this is a long message which contains an interpolation: ${format(data)} <- like this`;
如果你希望 Prettier 拆分插值,则需要确保 ${...}
中的某处有换行符。否则,无论多长,它都会将所有内容保留在一行上。
¥If you want Prettier to split up an interpolation, you'll need to ensure there's a linebreak somewhere within the ${...}
. Otherwise it will keep everything on a single line, no matter how long it is.
团队不希望以这种方式依赖原始格式,但这是我们目前最好的启发式方法。
¥The team would prefer not to depend on the original formatting in this way, but it's the best heuristic we have at the moment.
分号
¥Semicolons
这是关于使用 noSemi 选项的。
¥This is about using the noSemi option.
考虑这段代码:
¥Consider this piece of code:
if (shouldAddLines) {
[-1, 1].forEach(delta => addLine(delta * 20))
}
虽然上面的代码在没有分号的情况下工作得很好,但 Prettier 实际上把它变成了:
¥While the above code works just fine without semicolons, Prettier actually turns it into:
if (shouldAddLines) {
;[-1, 1].forEach(delta => addLine(delta * 20))
}
这是为了帮助你避免错误。想象一下 Prettier 不插入那个分号并添加这一行:
¥This is to help you avoid mistakes. Imagine Prettier not inserting that semicolon and adding this line:
if (shouldAddLines) {
+ console.log('Do we even get here??')
[-1, 1].forEach(delta => addLine(delta * 20))
}
哎呀!上面的意思其实是:
¥Oops! The above actually means:
if (shouldAddLines) {
console.log('Do we even get here??')[-1, 1].forEach(delta => addLine(delta * 20))
}
在 [
前面有一个分号,这样的问题永远不会发生。它使线条独立于其他线条,因此你无需考虑 ASI 规则即可移动和添加线条。
¥With a semicolon in front of that [
such issues never happen. It makes the line independent of other lines so you can move and add lines without having to think about ASI rules.
这种做法在使用无分号样式的 standard 中也很常见。
¥This practice is also common in standard which uses a semicolon-free style.
请注意,如果你的程序当前存在与分号相关的错误,Prettier 不会为你自动修复该错误。请记住,Prettier 只是重新格式化代码,它不会改变代码的行为。以这段有 bug 的代码为例,开发者忘记在 (
之前放置一个分号:
¥Note that if your program currently has a semicolon-related bug in it, Prettier will not auto-fix the bug for you. Remember, Prettier only reformats code, it does not change the behavior of the code. Take this buggy piece of code as an example, where the developer forgot to place a semicolon before the (
:
console.log('Running a background task')
(async () => {
await doBackgroundWork()
})()
如果你将它输入 Prettier,它不会改变这段代码的行为,相反,它会以一种方式重新格式化它,以显示这段代码在运行时的实际行为。
¥If you feed this into Prettier, it will not alter the behavior of this code, instead, it will reformat it in a way that shows how this code will actually behave when ran.
console.log("Running a background task")(async () => {
await doBackgroundWork();
})();
打印宽度
¥Print width
printWidth 选项更像是 Prettier 的指南,而不是硬性规定。它不是允许的行长度上限。这是一种向 Prettier 大致说明你希望线路多长的方式。Prettier 会制作更短和更长的线条,但一般会努力满足指定的打印宽度。
¥The printWidth option is more of a guideline to Prettier than a hard rule. It is not the upper allowed line length limit. It is a way to say to Prettier roughly how long you’d like lines to be. Prettier will make both shorter and longer lines, but generally strive to meet the specified print width.
有一些边缘情况,例如非常长的字符串字面量、正则表达式、注释和变量名,它们不能跨行中断(不使用代码转换 which Prettier 不行)。或者,如果你将代码嵌套 50 层,那么你的行当然大部分都是缩进:)
¥There are some edge cases, such as really long string literals, regexps, comments and variable names, which cannot be broken across lines (without using code transforms which Prettier doesn’t do). Or if you nest your code 50 levels deep your lines are of course going to be mostly indentation :)
除此之外,在某些情况下,Prettier 会故意超出打印宽度。
¥Apart from that, there are a few cases where Prettier intentionally exceeds the print width.
导入
¥Imports
Prettier 可以将长的 import
语句分成几行:
¥Prettier can break long import
statements across several lines:
import {
CollectionDashboard,
DashboardPlaceholder,
} from "../components/collections/collection-dashboard/main";
下面的示例不适合打印宽度,但 Prettier 还是将它打印在一行中:
¥The following example doesn’t fit within the print width, but Prettier prints it in a single line anyway:
import { CollectionDashboard } from "../components/collections/collection-dashboard/main";
某些人可能会出乎意料,但我们这样做是因为将 import
与单个元素保持在一行中是一种常见的请求。这同样适用于 require
调用。
¥This might be unexpected by some, but we do it this way since it was a common request to keep import
s with single elements in a single line. The same applies for require
calls.
测试函数
¥Testing functions
另一个常见的要求是将冗长的测试描述保持在一行中,即使它变得太长了。在这种情况下,将参数换行并没有太大帮助。
¥Another common request was to keep lengthy test descriptions in one line, even if it gets too long. In such cases, wrapping the arguments to new lines doesn’t help much.
describe("NodeRegistry", () => {
it("makes no request if there are no nodes to prefetch, even if the cache is stale", async () => {
// The above line exceeds the print width but stayed on one line anyway.
});
});
Prettier 对 describe
、it
、test
等常用测试框架功能都有特例。
¥Prettier has special cases for common testing framework functions such as describe
, it
and test
.
JSX
当涉及 JSX 时,Prettier 打印的内容与其他 JS 稍有不同:
¥Prettier prints things a little differently compared to other JS when JSX is involved:
function greet(user) {
return user
? `Welcome back, ${user.name}!`
: "Greetings, traveler! Sign up today!";
}
function Greet({ user }) {
return (
<div>
{user ? (
<p>Welcome back, {user.name}!</p>
) : (
<p>Greetings, traveler! Sign up today!</p>
)}
</div>
);
}
有两个原因。
¥There are two reasons.
首先,很多人已经将他们的 JSX 括在括号中,尤其是在 return
语句中。Prettier 遵循这种常见的风格。
¥First off, lots of people already wrapped their JSX in parentheses, especially in return
statements. Prettier follows this common style.
其次,备用格式可以更轻松地编辑 JSX。很容易在后面留下分号。与 Common JS 不同,JSX 中剩余的分号最终会以纯文本形式显示在你的页面上。
¥Secondly, the alternate formatting makes it easier to edit the JSX. It is easy to leave a semicolon behind. As opposed to normal JS, a leftover semicolon in JSX can end up as plain text showing on your page.
<div>
<p>Greetings, traveler! Sign up today!</p>; {/* <-- Oops! */}
</div>
注释
¥Comments
说到注释的内容,Prettier 确实无能为力。注释可以包含从散文到注释掉的代码和 ASCII 图的所有内容。因为它们可以包含任何东西,Prettier 不知道如何格式化或封装它们。所以他们保持原样。唯一的例外是 JSDoc 风格的注释(每行以 *
开头的块注释),Prettier 可以修复其缩进。
¥When it comes to the content of comments, Prettier can’t do much really. Comments can contain everything from prose to commented out code and ASCII diagrams. Since they can contain anything, Prettier can’t know how to format or wrap them. So they are left as-is. The only exception to this are JSDoc-style comments (block comments where every line starts with a *
), which Prettier can fix the indentation of.
然后是将注释放在哪里的问题。事实证明这是一个非常困难的问题。Prettier 尽最大努力使你的注释大致保持在原来的位置,但这并非易事,因为注释几乎可以放在任何地方。
¥Then there’s the question of where to put the comments. Turns out this is a really difficult problem. Prettier tries its best to keep your comments roughly where they were, but it’s no easy task because comments can be placed almost anywhere.
通常,将注释放在自己的行上而不是行尾时会获得最佳结果。比 // eslint-disable-line
更喜欢 // eslint-disable-next-line
。
¥Generally, you get the best results when placing comments on their own lines, instead of at the end of lines. Prefer // eslint-disable-next-line
over // eslint-disable-line
.
请注意,由于 Prettier 将表达式分成多行,因此有时可能需要手动移动 eslint-disable-next-line
和 $FlowFixMe
等“神奇注释”。
¥Note that “magic comments” such as eslint-disable-next-line
and $FlowFixMe
might sometimes need to be manually moved due to Prettier breaking an expression into multiple lines.
想象一下这段代码:
¥Imagine this piece of code:
// eslint-disable-next-line no-eval
const result = safeToEval ? eval(input) : fallback(input);
然后你需要添加另一个条件:
¥Then you need to add another condition:
// eslint-disable-next-line no-eval
const result = safeToEval && settings.allowNativeEval ? eval(input) : fallback(input);
Prettier 会将上面的内容变成:
¥Prettier will turn the above into:
// eslint-disable-next-line no-eval
const result =
safeToEval && settings.allowNativeEval ? eval(input) : fallback(input);
这意味着 eslint-disable-next-line
注释不再有效。在这种情况下,你需要移动注释:
¥Which means that the eslint-disable-next-line
comment is no longer effective. In this case you need to move the comment:
const result =
// eslint-disable-next-line no-eval
safeToEval && settings.allowNativeEval ? eval(input) : fallback(input);
如果可能,更喜欢在行范围(例如 eslint-disable
和 eslint-enable
)或语句级别(例如 /* istanbul ignore next */
)上运行的注释,它们更安全。可以使用 eslint-plugin-eslint-comments
禁止使用 eslint-disable-line
和 eslint-disable-next-line
注释。
¥If possible, prefer comments that operate on line ranges (e.g. eslint-disable
and eslint-enable
) or on the statement level (e.g. /* istanbul ignore next */
), they are even safer. It’s possible to disallow using eslint-disable-line
and eslint-disable-next-line
comments using eslint-plugin-eslint-comments
.
关于非标准语法的免责声明
¥Disclaimer about non-standard syntax
Prettier 通常能够识别和格式化非标准语法,例如 ECMAScript 早期提案和任何规范未定义的 Markdown 语法扩展。对这种语法的支持被认为是尽力而为和实验性的。不兼容性可能会在任何版本中引入,不应被视为重大更改。
¥Prettier is often able to recognize and format non-standard syntax such as ECMAScript early-stage proposals and Markdown syntax extensions not defined by any specification. The support for such syntax is considered best-effort and experimental. Incompatibilities may be introduced in any release and should not be viewed as breaking changes.
关于机器生成文件的免责声明
¥Disclaimer about machine-generated files
某些文件(如 package.json
或 composer.lock
)是机器生成的,并由包管理器定期更新。如果 Prettier 使用与其他文件相同的 JSON 格式规则,它会定期与这些其他工具发生冲突。为了避免这种不便,Prettier 将改用基于 JSON.stringify
的格式化程序来处理此类文件。你可能会注意到这些差异,例如删除了垂直空格,但这是预期的行为。
¥Some files, like package.json
or composer.lock
, are machine-generated and regularly updated by the package manager. If Prettier were to use the same JSON formatting rules as with other files, it would regularly conflict with these other tools. To avoid this inconvenience, Prettier will use a formatter based on JSON.stringify
on such files instead. You may notice these differences, such as the removal of vertical whitespace, but this is an intended behavior.
Prettier 不关心什么
¥What Prettier is not concerned about
Prettier 只打印代码。它不会改变它。这是为了限制 Prettier 的范围。让我们专注于打印并把它做好!
¥Prettier only prints code. It does not transform it. This is to limit the scope of Prettier. Let’s focus on the printing and do it really well!
以下是一些超出 Prettier 范围的示例:
¥Here are a few examples of things that are out of scope for Prettier:
将单引号或双引号字符串转换为模板字面量,反之亦然。
¥Turning single- or double-quoted strings into template literals or vice versa.
使用
+
将长字符串字面量分成适合打印宽度的部分。¥Using
+
to break long string literals into parts that fit the print width.添加/删除可选的
{}
和return
。¥Adding/removing
{}
andreturn
where they are optional.将
?:
转换为if
-else
语句。¥Turning
?:
intoif
-else
statements.排序/移动导入、对象键、类成员、JSX 键、CSS 属性或其他任何东西。除了作为一种转换而不仅仅是打印(如上所述)之外,排序还可能因为副作用(例如对于导入)而变得不安全,并且难以验证最重要的 correctness 目标。
¥Sorting/moving imports, object keys, class members, JSX keys, CSS properties or anything else. Apart from being a transform rather than just printing (as mentioned above), sorting is potentially unsafe because of side effects (for imports, as an example) and makes it difficult to verify the most important correctness goal.