ESLint 自动修复问题之如何保留最后修改人信息

在推行代码规范的时候,绝大多数情况下都会遭到不小的阻力,一来每个人的代码习惯不一致,要人轻易改变习惯确实也不是一朝一夕的事情,二来一般都会带来额外的开发成本和其它困扰。我们不禁在想,推行代码规范的困难点在哪里,以及如何解决这些痛点,让各个角色更容易接受呢?

归纳起来,推行规范的过程中,最常听到的几点担忧有:

  1. 是否带来额外的开发成本
  2. 规范的执行力度有强制性吗
  3. 已经开发的分支,合入的时候遇到大量冲突谁来解决
  4. 存量代码如何解决
  5. 被修复的代码,后面一看最后修改人全部都是运行 eslint --fix 的那个人怎么办

根据经验,有以下的解决办法:

  1. 规范已经集成在编辑器里,开发的时候就已经能够发现问题,对于是否有分号的这种问题,工具能修复的,就不用花大力气去讨论谁是谁非的问题。并且配置好,在 commit 的时候就尽可能地自动修复问题,除非是真正的问题,并且工具不能自动修复的才需要人工修复。
  2. 将代码 MR 加入流程自动扫描,并且设置质量红线,有引入新问题的,就禁止合入
  3. 先合并修复分支,冲突部分全部选择自己的改动,在解决冲突后要 commit,因为配置了 lint-staged,那么这些冲突的文件在合并上去的时候又会被自动修复,这样就达到了合并和修复的双重目的
  4. 存量代码要不要解决,答案当然是要的,但是就会带来第 5 个问题,修复的代码丢失了最后修改人的记录怎么办,所以终究还是要解决的是第 5 个问题。

那么我们考察市面上是否有解决方案

难道真的没有解决办法了吗?

我们研究了 ESLint 的修复流程

发现是不是只要定制化输出报告就可以针对指定人员做修复?

那么让我们看一下报告都有哪些信息。

  • line: 待修复的代码开始行
  • endLine: 结束行
  • range: 修复的代码位置
  • text: 变更后的代码片段

那么是否只要判断 line 和 endLine 在文件里相应位置的作者信息,用一个数组的 filter 就可以解决了?然后发现实际上并不生效并且还有错误。因为:

  • 修复只是将 output 里的代码替换掉整个文件
  • messages 里的项是线性叠加的,后面一项是依赖于前面一项的。也就是当前项的 line 和 endLine 是根据上一项修复的结果。如果前面有一项有行列变动的话,那么当前的修复也跟原文件是对应不上的。

问题好像又回到了无法解决的原点。在仔细研究了 Node.js API 文档后,我们发现有趣的一句话:

fix - This can be a boolean or a function which will be provided each linting message and should return a boolean. True indicates that fixes should be included with the output report, and that errors and warnings should not be listed if they can be fixed. However, the files on disk will not be changed. To persist changes to disk, call outputFixes().

意思是 fix 参数是支持函数的,messages 只是包含每个收集到的可修复信息,但是会视 fix 返回值来决定是否将可修复信息真的应用到修复上,也就是是否会在 output 上体现。

这就给我们带来了可操作的空间,在 fix 函数里去判断是否是相关联作者,如果是就返回 true,如果不是就返回 false。大概的流程是这样的:

以及看一下相关代码

// @ts-ignore eslint 的提示是错的,fix 可以是 boolean,也可以是 () => boolean
baseConfig.fix = (info: IMessage) => {
    messages.push(info);
 
    const { fix, line } = info;
 
    // 不可修复,直接返回
    if (!fix) {
        return false;
    }
 
    let shouldFix: boolean = false;
 
    // 处理的文件变化了
    if (lastFile !== currentFile) {
        lastFile = currentFile;
 
        // 生成未修复前的文件每一行的最后修改人
        authByEachLine = getAuthByEachLine(currentFile);
 
        // 读取文件内容
        sourceCode = fs.readFileSync(currentFile, "utf8");
 
        // 更新记录当前文件的基本信息
        bom = sourceCode.startsWith(BOM) ? BOM : "";
        sourceCode = bom ? sourceCode.slice(1) : sourceCode;
        lastPos = Number.NEGATIVE_INFINITY;
        output = bom;
    }
 
    const [codeStart, codeEnd] = fix.range;
 
    // 获取将要被改变的源码信息
    const changedCode = sourceCode.slice(codeStart, codeEnd);
 
    const originLineCount = fix.text.split("\r?\n").length;
    const changeLineCount = changedCode.split("\r?\n").length;
 
    const changeAuth = authByEachLine[line] || unknown;
 
    // 行数有变动,更新 authByEachLine
    if (originLineCount !== changeLineCount) {
        authByEachLine.splice(
            line,
            originLineCount,
            ...new Array(changeLineCount).fill(changeAuth)
        );
    }
 
    // 第一次找到了当次要做修复的作者, 要包含当次 fix 信息
    if (!filterAuth) {
        filterAuth = changeAuth;
 
        shouldFix = true;
    } else if (changeAuth === filterAuth) {
        // 符合当次修复的作者,要包含当次 fix 信息
        shouldFix = true;
    }
 
    if (shouldFix) {
        // 运用修复,更新文件源码
        shouldFix = attemptFix(fix);
    }
 
    // 确实被修复了,更新统计信息
    if (shouldFix) {
        if (info.severity === 2) {
            fixableErrorCount += 1;
        } else if (info.severity === 1) {
            fixableWarningCount += 1;
        }
    }
 
    return shouldFix;
};
 

在我们过滤了指定人员的修复报告后,只需要循环按人员来修复就可以了,用 git commit -m "xxx" --auth "auth " 来保存修改人信息

最后是我们达到的效果,比如有个文件的内容和修改人信息如下:

经过工具修复后:

可以看到 14 - 16 行,27 - 29 行即使有行变动,依然能完整保留最后修改人的信息。

git blame 工具仍维持着原修改人

编辑器里看到的也是经过我们处理过的最后修改人

git log 上看到的也都是按每个修改人去做的自动修复记录

在 git blame --line-porcelain 能同时看到修改人和提交人

至此,我们可以说我们已经有了解决这个问题的完整方案,并且切实可行。我们将工具开源到 github 上,制作发布了 npm 包,欢迎试用和提问题。

AlloyLint

AlloyLint

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章