4

原文 http://jlongster.com/A-Pretti...

今天我发布 Prettier, 一个 JavaScript 格式化工具. 它的灵感来源于 refmt, 它对于 ES2017, JSX 和 Flow 的语言特性有着高级的支持. 通过将 JavaScript 解析为 AST 并且基于 AST 美化和打印, Prettier 会丢掉几乎全部的原始的代码风格, 从而保证 JavaScript 代码风格的一致性. 跟 ESLint 不一样的在于它没有大量的 options 和 rules 需要管理. 不过同时有一点也很重要, 一切都是确定好的.
我很高兴的随着离开 Mozilla 之后我有时间做自己的开源工作了, 这是我 2017 年的开始.

下面是一个在线运行的 Demo. 注意语法是支持 JSX 和 Flow 的. 你可以在下面的编辑器里输入任何代码, 代码会被自动格式化.
行长的最大值是 60. 两个编辑器当中上面一个是原始输入, 下面是格式化之后的版本.

//                                           60 chars -->   |
function makeComponent() : int {
  return {
    longCall() {
      complicatedFunction(importantArgument(), secondaryArgument())
      weirdStyle({ prop: 1 },
        1, 2, 3);
    },
    render() {
      const user = {
        name: "James"
      };
    return <div>
        hello ${name}! JSX is <strong>supported</strong>
      </div>;
    }
  };
}
//                                           60 chars -->   |
function makeComponent(): int {
  return {
    longCall() {
      complicatedFunction(
        importantArgument(),
        secondaryArgument()
      );
      weirdStyle({ prop: 1 }, 1, 2, 3);
    },
    render() {
      const user = { name: "James" };
      return (
        <div>
           hello ${name}! JSX is <strong>supported</strong>
        </div>
      );
    }
  };
}

(上面的 Demo 运行在 Prettier 0.0.8)

很多人知道我在写 React 代码的时候通常不会写 JSX. 一个月之前我想试试了, 我猜意识到挡在我前面的是 Emacs 对 JSX 支持不足的问题. Emacs 对代码缩进本来有不错的支持. 我从来不需要手动多缩进什么东西. 但是对于 JSX 却不起作用, 我看了下其他的编辑器, 也看到了类似地问题(其他的编辑器在强制纠正缩进规则这方面基本上做的更差).

大概在同时我用了一段时间 Reason, Reason 提供了 refmt 工具用来自动格式化代码. 我就被迷住了. refmt 屏蔽了写代码当中很多让人分心的因素. 你可以按自己的习惯随便写, 然后格式化掉. 我意识到这不仅可以解决 JSX 的问题, 它也可以对任何编辑器提供强制整个团队代码样式的一致性的工具.

如果说计算机擅长做某个事情的话, 计算机会擅长解析代码和分析代码. 所以我准备把这个事情做出来, 这样就有了 Prettier. 我并不打算从底层开始写, 所以 Prettier 是从 fork recast 的 printer 开始的, 内部用 Wadler 在 "A prettier printer" 的算法进行了重写.

为什么选这套算法? 首先要看下为什么已有的样式格式化工具并没有实际的效验.

已有的样式格式化工具缺失了一个极为重要的部分: 最大的行长. 当然, 你是可以用让 ESLint 在行长过大时警告的(ESLint 不会知道怎样修复它). 最大行长是格式化工具决定性的一个部分, 特别是用在布局和折叠代码.

比如看下面的代码:

foo(arg1, arg2, arg3);

看上去格式化的方式是对的. 然而我们都会遇到这样的情况:

foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis(), isThereSeriouslyAnotherOne());

我之前的格式突然就不正常了, 因为太长了. 你多半会这样来处理:

foo(
  reallyLongArg(),
  omgSoManyParameters(),
  IShouldRefactorThis(),
  isThereSeriouslyAnotherOne()
);

这个例子清楚地展出了最大行长对于我们想要的代码的样式有着直接的影响. 目前的样式工具无视了这一点, 也就意味着在这个麻烦的场景当中它们毫无帮助. 团队里的每个人会按照他们自己不一样的规则调整代码的样式, 因而我们也就失去了我们想要的一致性.

Wadler 算法的论文描述了基于约束的代码的局部系统. 它会"测算"代码的长度, 如果超过了最大行长, 就会折行.

即便我们不顾行长, 在各种 linter 工具里也有很多办法偷懒. 我所知道的最严格的 linter 也会让这样代码的代码通过:

foo({ num: 3 },
  1, 2)

foo(
  { num: 3 },
  1, 2)

foo(
  { num: 3 },
  1,
  2
)

Prettier 通过解析代码和基于 AST 重新生成满足自己的规则的代码, 计算考虑了最大行长, 必要时进行代码的折行, 最终屏蔽了各种过于自由的样式.

关于模式

为了让 Prettier 变得实用我做了大量的工作. 目前输出的代码已经不错了, 我估计后面还有很多让大家能觉得更好的调整.

我们尽量让代码能遵循特定的模式. 比如这个写法在 JavaScript 很流行:

myPromise
  .then(() => {
    // ...
  })
  .then(() => {
    // ...
  })
  .catch(() => {
    // ..
  });

简单的 printer 会把它折叠成下面这样:

myPromise.then(() => {
  // ...
}).then(() => {
  // ...
}).catch(() => {
  // ..
});

不过在这场, 我们检测到"链式调用"的模式然后特意把每个 .then 生成在独立的一行.

如果你用到了某个 Prettier 没有格式化好的模式, 请提交一个 Issue, 我们来讨论一下如何检测这个规则以及如何有针对性地处理你的场景.

默契的团队

在团队中工作时, 很需要减少摩擦, 特别是在大型团队里. 尽管无法完全避免摩擦, 我们更多能做的是通过工具变得更容易在一起协作.

你可能觉得配置一下 ESLint 不会消耗太多时间, 或者说团队里不会话很多时间争论语法. 根据我的经验, 实际上不是. 即便你配置了大量的 ESLint 规则, 它实际上还是无法捕捉到全部的样式的差异. 团队仍然会努力去强制一套统一的样式, 而这显得很让人分心.

语法的细节其实没那么重要. 就让 Prettier 这样的工具来做格式和排版就好了, 程序员应该关注在那些真正的问题上.

自由度

结果好像的用了 Prettier 之后你写代码更加自由了, 怎么写都可以, 因为随后一格式化马上就纠正回来了!

不想关心分号的问题? 当然, 直接写就好了:

function foo() {
  var x = 5
  var y = 6
  var z = 7
  return x + y + z
}

把这段代码贴到上面去, 你会看到 Prettier 已经帮你把分号插入好了.

处理特别复杂的问题的时候想要写点脏代码的? 当然可以, 全写在一行都可以. 用自己习惯写的脏的语法就好了. 然后只要一个快捷键就能把代码格式化掉.

试一试 Prettier!

鸣谢:


题叶
17.3k 声望2.6k 粉丝

Calcit 语言作者