编写命令行工具升级eslint配置

- 技术 programming

    前段时间,我们组对旧的 eslint 规则做了一次改造,推出了新的 eslint 规则。为了让从旧往新的过度更顺畅,同时让使用者的改造意愿更强,我们提供了命令行无缝升级,只需要执行 idux-cli init 即可完成配置升级或初始化。组长把这个叫做 开发者体验


    配置升级的本质是:将无用的旧配置去掉,有用配置保留,再将旧配置和新规则进行合并生成新配置文件。

    首先有一个标准的 eslint 配置作为升级后的目标配置,大概如下(用xxx替换了一些缩写信息):

module.exports = {
    root: true,
    parserOptions: { // ts的项目 或者 ts+vue的项目启用
        parser: '@typescript-eslint/parser'
    },
    extends: [
      '@xxx/base', // 基础规则,必须启用
      '@xxx/vue', // 使用 vue 需要启用
      '@xxx/vue2', // 使用 vue2 需要启用
      '@xxx/vue3', // 使用 vue3 需要启用
      '@xxx/typescript', // 使用 typescript 需要启用
      '@xxx/i18n', // 老版本国际化需要启用
      '@xxx/jsformat', // 格式化相关
      '@xxx/vueformat', // 格式化相关
      '@xxx/tsformat', // 格式化相关
    ],
    env: {
      
    },
    globals: {
      
    },
    rules: {
      // Customize your rules
    },
  };

    从配置里可以看出来,新配置的生成和以下几个因素有关:

    同时 eslint 配置文件有 .eslintrc.[js,json,yml,yaml] 等多种后缀文件,甚至还可以定义在项目的 package.json 文件中,我们对所有的文件格式都提供支持。

整个过程伪代码如下:

init () {
		
	// 命令行询问格式化控制是否由 eslint 控制
	let formatrControlByEslint = await formatControlByEslint();

    // step1: 生成eslintrc
    genEslintRc(formatrControlByEslint);

    // step2: 格式化如果由prettier控制,则生成 prettierrc
    if (!formatrControlByEslint) {
        prettierrc();
    }

    // step4: 删除package.json中旧的eslint依赖
    deleteOldPkgsInPackageJson();
}

genEslintRc 要做以下几件事:

如何判断技术栈?

如何分析旧 eslint 配置?

    最开始,我是通过 require .eslintrc.js 文件来对旧配置进行处理,但是后面发现这种方式是不对的。因为在配置中,可能存在三元表达式,如果 require 此文件,则得到的是表达式执行后的结果,而我的目标是保留原始代码。因此只能通过 操作抽象语法树来处理

    起手就是三板斧: esprima 来解析 AST,estraverse 来操作 AST,escodegen 来生成代码。伪代码如下:

import * as estraverse from 'estraverse';
import * as esprima from 'esprima';
import * as escodegen from 'escodegen';

// 解析ast, fileContent为读取出来的 .eslintrc.js 文件内容
let parseAst = esprima.parseScript(fileContent);

/**
 * 操作抽象语法树
 * @Params {Object} ast 解析出来的ast
 * @Params {Object} 根据技术栈构造的eslint一些配置项
 */
const eslintAst = (ast, newEslintConfig) => {
	estraverse.replace(ast, {
		enter (astNode) {
			// 对节点做增删改查处理,构造出想要的eslint配置
		}
	});
}

// 生成代码,得到配置
let newAst = eslintAst(parseAst, newEslintConfig);
escodegen.generate(newAst);

    这里将这三者放在一起讲,很显然他们的处理是相似的。将 yml 或 yaml 处理为 json 数据来进行操作,最后再把处理好的 JSON 转为相应后缀的文件格式即可。这个工作可以使用 js-yaml 来完成。

    后续生成新的配置则是对 JSON 进行操作,问题就变得很简单了。

其他