---
title: 是莽撞人就来单挑：关于自动格式化 R 代码的求助
date: '2017-02-26'
slug: formatR
---

2021-03-22 更新

:   下面的第一个愿望已经在 [formatR 1.9](https://github.com/yihui/formatR/blob/master/NEWS.md) 中实现。

<!-- -->

2021-05-27 更新

:   下面的第二个愿望已经在 [formatR 1.11](https://github.com/yihui/formatR/blob/master/NEWS.md) 中实现。


[2010 年](/cn/2010/03/new-r-package-formatr/)我发布了一个叫 [formatR](/formatr) 的 R 包，它可以把乱糟糟的 R 代码自动清理成大致整齐的代码（例如自动加空格、换行等），这个包也是我当年做 knitr 的小小动力之一。虽然 formatR 是个小麻雀包，但它的开发过程中也有三个里程碑事件：

1. 最早的时候，怡轩想出了一个非常聪明的黑魔法，让这个整理过程能尽量保留代码注释；

1. 后来	Kohske Takahashi 大人提供了一个[很漂亮的黑魔法](https://github.com/yihui/formatR/commit/a2692d59f)，可以让赋值的等号自动被替换为左箭头；

1. 等啊等，终于等到 R 里面出现了 `getParseData()` 函数，让我终于不用红着脸用正则表达式去解析代码中的注释了，而是可以大大方方用官方 API 去解析；

然而，我对这个包还有两个小小的夙愿一直没能实现。虽不至于让我晚上睡不着觉，但若能实现，那真真是极好的。第一个愿望是，我希望能（尽量）控制代码的最大宽度。目前 `formatR::tidy_source()` 的宽度参数 `width.cutoff` 源自 `deparse()` 函数，而这个参数的大意是尝试在多少个字符宽度**之后**尝试断行，而我希望能在多少个字符宽度**之前**断行。因为如果之后断行，万一那个位置上恰好有个很长的变量名不能被折断，那就要等到这个变量名之后才能尝试换行，于是代码可能会超出右边界很多，这对需要 PDF 印刷品输出的写作来说是个灾难，调整起来很费神。

第二个愿望是如果小括号里的参数太多的话，就在小括号之后直接断行（原因在[前面的日志](/cn/2017/01/blank-area/)中解释过），比如：

```r
my_sum = function(
  a = 1, b = 2, c = 3, d = 4, e = 5,
  f = 6, g = 7
) {
  return(a + b + c)
}

my_sum(
  a = 1, b = 2, c = 3, d = 4, e = 5,
  f = 6, g = 7
)
```

目前 `tidy_source()` 函数只能做到：

```r
my_sum = function(a = 1, b = 2, c = 3, d = 4, e = 5,
  f = 6, g = 7) {
  return(a + b + c)
}

my_sum(a = 1, b = 2, c = 3, d = 4, e = 5,
  f = 6, g = 7)
```

当然这个不算糟糕，我可以忍。这两个愿望我更迫切需要实现第一个。客官们可能会想能不能给 R 里面的 `deparse()` 函数打个补丁，让它既支持最大宽度也支持最小宽度，如果能打补丁并且 R 核心团队愿意接受的话，那也是极好的，但显然我没有这个技能，因为 `deparse()` 的源代码是基于 C 的，我一看[一千多行](https://github.com/wch/r-source/blob/trunk/src/main/deparse.c)就两眼蒙圈，但我有一定的把握去游说一位核心团队成员接受它。如果想靠自己造轮子的话，那么应该就是两条可能的路[^1]：一是 `getParseData()`（注意[它有坑](https://github.com/yihui/formatR/commit/045e42b)会截断长字符串），二是用 [sourcetools](https://github.com/kevinushey/sourcetools) 包，这个我没用过，但看起来也许比 `getParseData()` 更容易操纵一点，而且作者是敝司的同事，有问题可以直接找他解决。

若客官们还有闲情逸致，可以顺便把这两个功能也一并实现了（[#57](https://github.com/yihui/formatR/issues/57)、[#62](https://github.com/yihui/formatR/issues/62)）。好了，黑客骚年们，我在[世上最大的同姓交友网站](https://github.com)上等你们的合并请求。

[^1]: 其实还有第三条非常黑的路，就是暴力尝试不同宽度，看能否找到一个合适的，这个我当年我迫于 `R CMD check` 的压力早已经在 Rd2roxygen 包中[试过了](https://github.com/yihui/Rd2roxygen/blob/b16a03009/R/build.R#L154-L164)。所以不要跟我比黑招，我琢磨起黑招来我自己都感到害怕。除非是被逼急了，别一言不合就暴力求解。
