TDD方式创建一个Eslint插件,实现检查try catch的rule规则

image.png这几天看到Eslint可能要开始重写了(ΩДΩ) https://github.com/eslint/eslint/eslint/discussions/16557

Completely new codebaseESM with type checkingrun in any runtime: Node, Deno, brower…lint more languageNew public APIsRust-based replacementsAST mutations for autofixing…

又是Rust和AST~

然后突然想起来之前写的eslint插件的文,还没写完,今天用TDD的方式重写一下吧☺插件的功能是给每个方法添加try-catch(代码地址)这里先不考虑像下面这种先是变量声明等语句,然后是try-catch包裹主要逻辑的情况,我们实现的简单一些,只要函数体最外层不是try-catch,就给它提示出来。

function foo () { let var1 = ”; try { … } catch (err) { }}建立文件结构|– README.md|– demo| |– package.json| |– pnpm-lock.yaml| `– src| `– index.js|– index.js|– package.json|– pnpm-lock.yaml`– test `– add.spec.jsmkdir eslint-plugin-try-statement(要求插件命名都要以eslint-plugin-开头)pnpm init 生成package.jsonpnpm i eslint -D 安装eslinttouch index.js 这里是我们插件的逻辑代码新建test/add.spec.js文件,这里是我们的测试用例mkdir demo; pnpm init; pnpm i eslint -Dpnpx eslint –init 生成.eslintrc.js编写测试用例

准备工作就做完了,下面我们写测试用例(你可以先写一个正确的和一个错误的用例,先保证没问题,后面再添加其他用例)

const { RuleTester } = require(‘eslint’);const { rules } = require(‘../index’);const ruleTester = new RuleTester();ruleTester.run(‘try-statement’, rules.add, { // 正确的用例 valid: [ { name: ’empty body’, code: ‘function fetchUsers() {}’ }, { name: ’empty body && starts with try statement’, code: ‘function fetchUsers() { try {} catch(err) {} }’ }, { name: ‘starts with try statement’, code: ‘function fetchUsers() { try { var timeout = 1000; } catch(err) {} }’ } ], // 错误的用例 invalid: [ { name: ‘has no try statement’, code: ‘function fetchUsers1() { var timeout = 1000; var retry = 3; }’, output: ‘function fetchUsers1() { try {\nvar timeout = 1000; var retry = 3;\n} catch(err) {\n} }’, errors:[{ message: ‘function fetchUsers must be embraced by try statement’ }] }, { name: ‘do not fix’, code: ‘function fetchUsers2() { var timeout = 1000; var retry = 3; }’, options: [false], output: ‘function fetchUsers2() { var timeout = 1000; var retry = 3; }’, errors:[{ message: ‘function fetchUsers must be embraced by try statement’ }] } ]})

vaild里的用例是看函数体是否为空,不为空的话是不是全部用try-catch包裹。invalid里的第一个用例是测试插件自动添加try-catch的结果,第二个是测试当插件配置了options参数为false,即不fix的情况。

这里的ruleTester.run是eslint提供的api,跟使用jest断言是比较相似的

test(‘name’, () => { … expect(xxx).toEqual(xxx);});

ruleTester.run的第一个参数是插件的名称,第二个参数是规则的名称,第三个参数是一个包含valid和invalid的对象,它们又分别是一个对象数组

{ valid: [ ], invalid: [ { name: ‘name’, code: ”, // 要执行测试的代码,相当于expect的内容 output: ”, // 执行后的期望输出,相当于toEqual的内容 options: [false], // 同插件使用是配置的options errors:[{ message: ‘function fetchUsers must be embraced by try statement’ }] } ]}初始化插件

先简单写一下插件的代码

module.exports = { rules: { “add”: { meta: { docs: { description: “所有函数都用try-catch包裹”, }, fixable: ‘code’ }, create: function (context) { // 公共变量和函数应该在此定义,要求必须返回对象 console.log(‘eslint-plugin-try-statement add created !!!’); return {} } } }};

node test/add.jest.js看下结果

为了避免每次都要手动执行这个命令,可以安装下mocha, 然后修改下package.json的脚本, 这样就可以监听add.spec.js和它依赖的index.js改动,自动执行测试用例了

image.png

这是只保留第一个正确的测试用例和第一个错误的测试用例的结果,因为我们没有写插件的实现,看到了log打印,所以是符合预期的

image.png

下面我们就来补充index.js的create方法,实现插件的逻辑主要借助AST分析

image.png

image.png可以看到区别了吧,所以我们只需要看body是不是空数组,不是的话是不是只有TryStatement就好了,是不是超级简单~

插件逻辑实现create: function (context) { // visitor AST return { // 返回事件钩子, 这个插件我们值关心函数声明FunctionDeclaration就可以了 // context和node都是提供给我们的参数,可以打印下看看它们的结构 FunctionDeclaration(node) { const functionName = node.id.name; const blockStatementBody = node.body.body; // 通过上图的AST分析,主要操作的是node.body.body这个节点 const hasBody = blockStatementBody.length !== 0; if (hasBody) { const startsWithTry = blockStatementBody[0].type === ‘TryStatement’; const hasCatch = !!blockStatementBody[0].handler; // 如果函数体不为空,但是body数组不是TryStatement语句,这个就是我们需要提示的情况了 // 调用context.report方法进行警告提示,message就是警告信息 if (!startsWithTry) { context.report({ node, message: `function ${functionName} must be embraced by try statement`, }) } else if(!hasCatch) { // 有try但没有catch也提示一下 context.report({ node, message: ‘function ${functionName} must be embraced by try-catch statement’ }) } } } }}

再看下用例执行结果,符合预期

image.png

自动fix

接下来写一个自动fix的逻辑,fix也是context.report对象参数的一个属性,返回这样的结构

fix() { return { range: [], text: ” } }

不过这样写起来有点儿麻烦,我们用它的参数fixer提供的方法

insertTextAfterinsertTextAfterRangeinsertTextBeforeinsertTextBeforeRangereplaceTextreplaceTextRangeremoveremoveRange

有时候可能不想要自动fix,这时候可以通过option是配置来实现单测通过后,就可以在demo中使用下我们的插件了

cd demopnpm install ../ -D

这时候我们看到了依赖里多了我们自定义的插件,并且是指向上一级目录的

image.png

image.png

然后在.eslint.js中配置插件

plugins: [ ‘try-statement’],rules: { ‘try-statement/add’: [‘warn’, false] // 错误级别warn,false代表不自动fix}

fix函数实现

fix(fixer) { // console.log(fixer); const isNeedFix = context.options[0]; // options即传入的配置,这里是[false] if (isNeedFix === false) { console.log(‘** options **’, functionName, context.options) return fixer.insertTextBeforeRange(blockStatementBody[0].range, ”); } // 开头和结尾分别添加try、catch return [ fixer.insertTextBeforeRange(blockStatementBody[0].range, ‘try {\n’), fixer.insertTextAfterRange(blockStatementBody[blockStatementBody.length – 1].range, ‘\n} catch(err) {\n}’) ]}

image.png单测全部通过了,我们再去实际的使用就是demo中去看看效果。因为上一步已经添加了自定义插件,现在只需要重启下eslint就可以了

image.png现在就可以看到错误提示了

image.png如果你开启了保存时格式化,conmand+s,就会看到按照fix方法里写的格式自动fix了

image.png

你还可以修改成不自动fix,验证一下(修改了.eslint.js,记得重启eslint~)

这个插件实现的功能比较简单,感兴趣的话可以写一个自己的eslint插件,来扩展rule,还可以发布到npm供其他人使用。写完之后,你会发现对eslint用法的理解又加深了~

参考

从 ESLint 开启项目格式化


比丘资源网 » TDD方式创建一个Eslint插件,实现检查try catch的rule规则

发表回复

提供最优质的资源集合

立即查看 了解详情