[译] A Prettier JavaScript Formatter

javascript/jquery

浏览数:216

2019-3-6

AD:资源代下载服务

原文 http://jlongster.com/A-Prettier-Formatter

原作者 https://twitter.com/jlongster

题图 https://twitter.com/reactinFF/status/846655788349362176

今天我(注: 文章作者是 James Long)发布 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 开始的,它的内部用 “A prettier printer” 当中的 Wadler 算法进行了重写。

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

已有的样式格式化工具缺失了一个极为重要的部分: 最大的行长。当然,你是可以用让 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!

鸣谢: