Prettier 1.4:TypeScript 与 CSS 支持
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
本次发布为 Prettier 引入了 TypeScript、CSS、Less 和 SCSS 语言支持!
TypeScript 支持
这是 Prettier 用户呼声最高的功能。现在通过 1.4.0 版本,您可以使用 Prettier 格式化 .ts 和 .tsx 文件了!
Prettier 的工作原理是利用解析器生成代码的 AST 表示并重新输出。Babylon(支持 Babel 的解析器)和 Flow 在生成 JavaScript 部分的 AST 时基本遵循 estree 格式,同时为 Flow 特有的语法创建特殊节点。
与 Flow 类似,TypeScript 为其特有语法引入了特殊节点。遗憾的是,它在其余 JavaScript 语法部分并未遵循 estree 格式。这给 Prettier 带来了挑战,因为我们需要几乎完全分叉代码才能支持 TypeScript 的格式化。
AST 的兼容性问题并非新挑战,ESLint 项目也曾受此困扰。由于 AST 结构不同,所有 ESLint 规则都无法直接工作。幸运的是,@JamesHenry 和 @soda0289 开发了 typescript-eslint-parser 项目,它能将 TypeScript AST 转换为 estree 格式——这正是 Prettier 所需的解决方案!
该项目集成至 Prettier 后,@azz、@despairblue 和 @Pajn 实现了所有 TypeScript 专用节点,并确保 TypeScript 测试套件的 13k 用例全部通过。这项艰巨工作终于完成,功能已可供使用 :)
我们在 GitHub 上最大的 TypeScript 项目中测试了 Prettier,确保其正确输出代码。目前尚未深入优化代码格式化效果,如发现异常情况,请提交 issue 反馈!
CSS、Less 与 SCSS 支持
虽然 TypeScript 是开源社区最期待的功能,但 CSS 支持却是 Facebook 工程师最迫切的需求。当您习惯在某语言中使用代码美化工具后,自然会希望在所有地方都实现自动化!
CSS 语言的复杂度远低于 JavaScript,其支持实现仅耗时数日。我们采用 @ai 开发的 postcss 作为底层解析器,它支持解析 CSS、Less 和 SCSS。同时我们还依赖以下解析器:
当前 postcss 暂不支持 Sass 或 Stylus 解析。若有开发者愿意实现相关打印功能,我们很乐意提供支持。
请注意:当前 Prettier 仅进行基础代码格式化,尚未支持类似 JavaScript 的 singleQuote 等选项,也未实现颜色值或数字的标准化处理。
编辑器集成
项目第一阶段的目标是确保 Prettier 输出正确且美观的代码。现在基础功能已趋完善,我们将着力优化编辑器集成体验。最新版本支持两大实用特性:保持光标位置稳定,以及支持局部格式化(而非全文件处理)。
请注意,我们刚刚在 Prettier 核心代码中实现了这些功能支持,目前还没有任何编辑器集成使用它们。此外,我们尚未在实际使用场景中充分测试,因此很可能还需要修复一些粗糙的边缘问题!
新增 cursorOffset 选项用于光标位置转换(#1637)by @josephfrazier
目前我们让编辑器自行判断光标应该位于何处,虽然它们做得还不错。但既然我们负责代码的打印输出,我们就能提供更精确的位置信息!
新增 --range-start/end 选项以仅格式化部分代码(#1609)by @josephfrazier
这是用户呼声极高的功能。当前 Prettier 只能格式化整个文件,现在则可以选择格式化特定代码段了。
其实现原理是通过向上遍历 AST 来找到最近的完整语句。这意味着你无需精确选择有效范围——只需大致框选想要重新格式化的代码区域,它就能智能处理!
新增 filepath 选项以支持文件类型推断(#1835)by @mitermayer
由于现在支持 CSS 和 TypeScript 格式化,为每个文件指定解析器变得很不方便。现在只需传入文件路径,Prettier 就能通过扩展名自动选择正确的解析器。
重要更新
JSX 内部文本内容自动换行(#1120, #1671, #1827, #1829)by @karl
用户使用 Prettier 处理 JSX 时最大的痛点就是文本打印问题。旧版会在文本前添加难看的 {" "},且对超长文本不做处理。现在我们将每个单词视为独立标记,实现了自然的文本流式排版。
这是 @karl 完成的杰出工作——他不仅实现了功能,还在 Prettier 内部引入了新的基础原语,能够智能处理元素序列的打印并在触及边界时自动换行。
// Before
<div>
Please state your
{" "}
<b>name</b>
{" "}
and
{" "}
<b>occupation</b>
{" "}
for the board of directors.
</div>
// After
<div>
Please state your <b>name</b> and <b>occupation</b> for the board of
directors.
</div>
移除箭头函数返回 JSX 时的括号(#1733)by @xixixao
编写函数式组件的开发者会为此欣喜——我们不再为返回 JSX 的箭头函数添加额外括号。
// Before
const render1 = ({ styles }) => (
<div style={styles}>
Keep the wrapping parens. Put each key on its own line.
</div>
);
// After
const render1 = ({ styles }) =>
<div style={styles}>
Keep the wrapping parens. Put each key on its own line.
</div>;
改进模板字面量打印(#1664, #1714)by @josephfrazier
模板字面量的打印始终是 Prettier 的难点。1.3.0 版本已显著改进,而本次更新后,我们相信已妥善处理了所有常见场景。
我们曾添加移除空行的工具来解决某些问题,但这偶尔会导致怪异结果,现已移除。另一项优化是:不再在 ${ 起始处缩进,而是在包含 ${ 的整行起始处进行缩进。
如果此版本后您对模板字符串的输出仍有疑问,请随时告知我们!
// Before
const Bar = styled.div`
color: ${props => (props.highlight.length > 0 ? palette([
'text',
'dark',
'tertiary'
])(props) : palette([
'text',
'dark',
'primary'
])(props))} !important;
`
// After
const Bar = styled.div`
color: ${props =>
props.highlight.length > 0
? palette(["text", "dark", "tertiary"])(props)
: palette(["text", "dark", "primary"])(props)} !important;
`
对赋值和对象值采用相同的换行规则 (#1721)
我们为赋值后换行(如 a = ...)精心设计了大量逻辑。现在这些规则也应用于对象值,将显著改善多行布尔逻辑和大规模条件表达式的格式。这也是我们创建统一打印器的优秀实践案例。
// Before
const o = {
somethingThatsAReallyLongPropName: this.props.cardType ===
AwesomizerCardEnum.SEEFIRST,
};
// After
const o = {
somethingThatsAReallyLongPropName:
this.props.cardType === AwesomizerCardEnum.SEEFIRST,
};
在 !() 内部缩进条件表达式 (#1731)
此前持续有用户反馈其渲染方式问题,我们将其列为"可能较难解决,稍后处理"的事项。结果发现实现起来非常简单,现在就来满足大家的需求!
// Before
const anyTestFailures = !(aggregatedResults.numFailedTests === 0 &&
aggregatedResults.numRuntimeErrorTestSuites === 0);
// After
const anyTestFailures = !(
aggregatedResults.numFailedTests === 0 &&
aggregatedResults.numRuntimeErrorTestSuites === 0
);
格式化修复
尽可能将循环体放在同一行 (#1498)
我们已为 if 语句实现此特性,为保持一致性,现在循环语句也采用相同处理方式。
// Before
for (a in b)
var c = {};
// After
for (a in b) var c = {};
修复 Flow Union 类型的空行问题 (#1511) by @existentialism
我们不应该进行双重缩进 ;)
// Before
type Foo = Promise<
| { ok: true, bar: string, baz: SomeOtherLongType }
| { ok: false, bar: SomeOtherLongType }
>;
// After
type Foo = Promise<
{ ok: true, bar: string, baz: SomeOtherLongType } |
{ ok: false, bar: SomeOtherLongType }
>;
对含行尾注释的单参数不再添加括号 (#1518)
原先检测箭头函数是否省略括号时,仅检查是否存在注释。现在改为只处理行内注释(如 (/* comment */ num)),忽略行尾注释。
// Before
KEYPAD_NUMBERS.map((num) => ( // Buttons 0-9
<div />
));
KEYPAD_NUMBERS.map(num => ( // Buttons 0-9
<div />
));
不再缩进嵌套三元表达式 (#1822)
这避免了看起来像是4字符缩进(实际应为2字符)的问题。虽然多行条件会失去对齐效果,但考虑到嵌套三元表达式的条件通常较简短,我们认为这是更合理的取舍。
// Before
aaaaaaaaaaaaaaa
? bbbbbbbbbbbbbbbbbb
: ccccccccccccccc
? ddddddddddddddd
: eeeeeeeeeeeeeee ? fffffffffffffff : gggggggggggggggg;
// After
aaaaaaaaaaaaaaa
? bbbbbbbbbbbbbbbbbb
: ccccccccccccccc
? ddddddddddddddd
: eeeeeeeeeeeeeee ? fffffffffffffff : gggggggggggggggg;
在 JSX 属性内部内联链式条件表达式 (#1519)
由于后续不可能出现其他内容,我们无需通过缩进来消除歧义。
// Before
<div
src={
!isEnabled &&
diffUpdateMessageInput != null &&
this.state.isUpdateMessageEmpty
}
/>;
// After
<div
src={
!isEnabled &&
diffUpdateMessageInput != null &&
this.state.isUpdateMessageEmpty
}
/>;
取消字符串中不必要的转义字符 (#1575) by @josephfrazier
我们持续优化字符串处理,本次新增功能将移除冗余的反斜杠 \。
// Before
a = 'hol\a';
// After
a = 'hola';
修复布尔表达式中空对象的格式 (#1590) by @dmitrika
虽然单独成行的 { 在布尔表达式中显得突兀,但内联空对象会导致异常行为。因此我们保持空对象独立成行。
const x = firstItemWithAVeryLongNameThatKeepsGoing ||
secondItemWithALongNameAsWell || {};
// After
const x =
firstItemWithAVeryLongNameThatKeepsGoing ||
secondItemWithALongNameAsWell ||
{};
移除 For 语句中序列表达式的括号 (#1597) by @k15a
现在 for 循环内多重赋值时不再添加冗余括号。
// Before
for ((i = 0), (len = arr.length); i < len; i++) {
// After
for (i = 0, len = arr.length; i < len; i++) {
当参数有前置注释时不再内联箭头函数 (#1660)
现在箭头函数内部的块注释再也不会导致格式混乱了!
// Before
export const bem = block => /**
* @param {String} [element] - the BEM Element within that block; if undefined, selects the block itself.
*/
element => /**
* @param {?String} [modifier] - the BEM Modifier for the Block or Element; if undefined, selects the Block or Element unmodified.
*/
modifier =>
// After
export const bem = block =>
/**
* @param {String} [element] - the BEM Element within that block; if undefined, selects the block itself.
*/
element =>
/**
* @param {?String} [modifier] - the BEM Modifier for the Block or Element; if undefined, selects the Block or Element unmodified.
*/
modifier =>
修复导入语句尾部的注释问题 (#1677)
这是我们需要特殊处理注释逻辑的又一场景!
// Before
import {
ExecutionResult,
DocumentNode,
/* tslint:disable */
SelectionSetNode,
} /* tslint:enable */ from 'graphql';
// After
import {
DocumentNode,
/* tslint:disable */
SelectionSetNode,
/* tslint:enable */
} from 'graphql';
处理成员链中的注释 (#1686, #1691)
此前我们已处理过部分注释位置问题,但随着新场景不断出现,现转为采用更通用的处理方案。希望未来不会再出现此类问题。
// Before
const configModel = this.baseConfigurationService.getCache().consolidated // global/default values (do NOT modify)
.merge(this.cachedWorkspaceConfig);
// After
const configModel = this.baseConfigurationService
.getCache()
.consolidated // global/default values (do NOT modify)
.merge(this.cachedWorkspaceConfig);
对嵌套箭头函数使用 expandLast 策略 (#1720)
// Before
f(action => next =>
next(action));
// After
f(action => next =>
next(action),
);
将 JSX 注释置于括号内 (#1712)
此改动主要影响 Facebook 工程师——当更新 Flow 版本时系统会自动添加 $FlowFixMe 注释。现在这些注释不再会被打乱。
// Before
const aDiv = /* $FlowFixMe */
(
<div className="foo">
Foo bar
</div>
);
// After
const aDiv = (
/* $FlowFixMe */
<div className="foo">
Foo bar
</div>
);
强制在多变量声明时换行 (#1723)
这是高频需求项。此前我们仅在行宽超过 80 列时才换行,现在只要存在至少一个带赋值的变量声明,无论行宽如何都会换行。
// Before
var numberValue1 = 1, numberValue2 = 2;
// After
var numberValue1 = 1,
numberValue2 = 2;
内联处理 | null 和 | void 类型 (#1734)
Flow 联合类型展开显示在包含多个对象时效果良好,但用于可空性检查时则显得怪异。现在我们对 | null 和 | void 采用内联处理。
// Before
interface RelayProps {
articles:
| Array<
| {
__id: string,
}
| null
>
| null
}
// After
interface RelayProps {
articles: Array<{
__id: string,
} | null> | null,
}
在 implements 处换行而非 extends 处 (#1730)
不再在 extends 处强制换行,这将使带继承的类声明在需要换行时更自然。
// Before
class MyContractSelectionWidget
extends React.Component<
void,
MyContractSelectionWidgetPropsType,
void
> {
method() {}
}
// After
class MyContractSelectionWidget extends React.Component<
void,
MyContractSelectionWidgetPropsType,
void
> {
method() {}
}
内联处理单导入语句 (#1729)
与长 require 调用不换行的处理一致,现在当 import 语句仅导入单个内容时也不再换行。
// Before
import somethingSuperLongsomethingSuperLong
from "somethingSuperLongsomethingSuperLongsomethingSuperLong";
// After
import somethingSuperLongsomethingSuperLong from "somethingSuperLongsomethingSuperLongsomethingSuperLong";
为 SequenceExpression 添加换行能力 (#1749)
你知道吗?如果代码中不含语句,可以用 () 替代 {},用 , 替代 ;?现在你知道了。有人会在箭头函数返回值时利用此特性。虽然不推荐这么做,但 Prettier 实现支持很简单,那就加上吧 ¯\_(ツ)_/¯
// Before
const f = (argument1, argument2, argument3) =>
(doSomethingWithArgument(argument1), doSomethingWithArgument(
argument2
), argument1);
// After
const f = (argument1, argument2, argument3) => (
doSomethingWithArgument(argument1),
doSomethingWithArgument(argument2),
argument1
);
不再强制为空循环体换行 (#1815)
空循环体的 {} 不再被强制拆分为两行。
// Before
while (true) {
}
// After
while (true) {}
保留带注释的 switch case 间空行 (#1708)
// Before
switch (true) {
case true:
// Good luck getting here
case false:
}
// After
switch (true) {
case true:
// Good luck getting here
case false:
}
正确性保障
移除 ast-types 依赖 (#1743, #1744, #1745, #1746, #1747)
以往我们通过遍历 AST(使用 ast-types 的定义)来确定注释位置。当某些字段未声明时,这偶尔会导致问题——我们无法找到对应节点,导致注释被打印到错误位置或抛出异常。事实证明我们无需维护这种映射关系,只需遍历对象即可:只要节点拥有 type 字段,就将其视为有效节点。
// Before
Error: did not recognize object of type "ObjectTypeSpreadProperty"
// After
type X = {...Y/**/};
type X = {/**/...Y};
保留非常规 Unicode 空白符 (#1658, #1165) 由 @karl 和 @yamafaktory 贡献
此前若在 JSX 文本中使用不可见字符,Prettier 会将其替换为常规空格。虽然不确定这种使用场景的实际意义,但现在我们会原样保留这些字符!
防止模板字面量尾部注释逃逸 (#1580, #1713, #1598, #1713) 由 @josephfrazier 和 @k15a 贡献
我们曾用复杂且不稳定的逻辑处理模板字面量内的注释。针对 JSX {} 表达式,我们引入了优雅的解决方案:在 } 结束前设置边界——若存在未打印的注释,则一次性输出所有注释,添加换行符后再打印 }。现在我们将此逻辑应用于模板字面量处理 :)
// Before
`${0} // comment`;
// After
`${
0
// comment
}`;
正确添加 await 表达式括号 (#1513, #1595, #1593) 由 @bakkot 和 @existentialism 贡献
由于我们采用枚举节点组合的方式(而非自动化方案)决定括号添加规则,仍存在少量特殊组合未被覆盖。本次更新通过精准添加必要括号并移除冗余括号,显著增强了 await 表达式的处理鲁棒性。
// Before
(await spellcheck) && spellcheck.setChecking(false);
new A((await x));
// After
await (spellcheck && spellcheck.setChecking(false));
new A(await x);
保留 Flow ObjectTypeProperty 中的 getter/setter 信息 (#1585) 由 @josephfrazier 贡献
又一项长尾问题的修复成果!
// Before
type T = { method: () => void };
// After
type T = { get method(): void }
为带泛型的单参数类型添加括号 (#1814)
另一类隐蔽的括号缺失问题被成功解决!
// Before
type ExtractType = <A>B<C> => D
// After
type ExtractType = <A>(B<C>) => D
Babylon 解析器支持非严格模式回退 (#1587, #1608) 由 @josephfrazier 贡献
为解析所有 JavaScript 代码,Babylon 解析器需判断文件是否采用严格模式。我们默认启用严格模式(覆盖多数场景),但当遇到类似 0775 的八进制字面量时会导致解析失败。现在遇到严格模式解析失败时,我们将自动切换至非严格模式重试。同时我们允许在 Node 文件中使用函数外部的 return 语句(符合 Node 规范)。
// Before
SyntaxError
// After
return 0775;
命令行工具
允许 --write 与 --list-different 同时使用 (#1633)
当编写提交钩子时,通过单条命令同时告知用户实际变更内容非常有用。
CLI 执行时默认忽略 node_modules (#1683) by @thymikee
用户很容易误操作格式化 node_modules/ 目录(通常无需处理)。现已默认禁用此行为,若确实需要可通过 --with-node-modules 选项启用。
支持 glob 模式匹配点文件 (#1844) by @jhgg
我们在使用的 glob 解析库中启用了点文件遍历功能,这意味着 * 模式现在可匹配 .eslintrc 这类文件。

