前端手把手教你用TypeScript写一个简单的eslint插件
Posted hans774882968
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端手把手教你用TypeScript写一个简单的eslint插件相关的知识,希望对你有一定的参考价值。
引言
看到参考链接1以后,觉得用TS写一个eslint插件应该很简单🐔⌨️🍚,尝试下来确实如此。
前置知识
本文假设
- 你对AST遍历有所了解。
- 你写过单测用例。
作者:hans774882968以及hans774882968以及hans774882968
第一个eslint规则:no-console
为了简单,我们只使用tsc进行构建。首先package.json
需要设置入口"main": "dist/index.js",
,tsconfig.json
需要设置"outDir": "dist"
、"include": ["src"]
。接下来设计一下单元测试和构建命令:
"scripts":
"clean": "rm -Rf ./dist/",
"build": "yarn clean && mkdir ./dist && tsc",
"test": "jest",
"test:help": "jest --help",
"lint": "eslint \\"src/**/*.js,jsx,ts,tsx\\" \\"test/**/*.js,jsx,ts,tsx\\" \\"*.js,jsx,ts,tsx\\" --fix"
,
用ESLintUtils.RuleTester
写的.test.ts
用mocha
或者jest
都能运行,我选择了jest
。
当我们运行yarn lint
时,node_modules/@eslint/eslintrc/lib/config-array-factory.js
的this._loadPlugin
会加载插件,相当于在node环境运行上面指定的入口点dist/index.js
。所以我们需要知道@eslint
如何描述一个eslint插件,才能写出src/index.ts
。查看this._loadPlugin
可知,plugin.definition
的类型应为:
type Plugin =
configs?: Record<string, ConfigData> | undefined;
environments?: Record<string, Environment> | undefined;
processors?: Record<string, Processor> | undefined;
rules?: Record<...> | undefined;
结合参考链接1,我们得出结论:一般来说需要提供rules
和configs
属性。rules
可以理解为具体的规则定义;configs
可以理解为规则的集合,可以称为“最佳实践”,最常见的configs
是recommended
。
于是写出src/index.ts
:
import rules from './rules';
import configs from './configs';
const configuration =
rules,
configs
;
export = configuration;
src/rules/index.ts
:
import noConsole from './noConsole';
const allRules =
'no-console': noConsole
;
export default allRules;
src/configs/index.ts
:
import all from './all';
import recommended from './recommended';
const allConfigs =
all,
recommended
;
export default allConfigs;
src/configs/all.ts
:
export default
parser: '@typescript-eslint/parser',
parserOptions: sourceType: 'module' ,
rules:
'@hans/use-i18n-hans/no-console': 'error'
;
我们用createRule
函数来创建一条规则。它需要传一个对象,我列举一下这个对象常用的几个属性:
meta.schema
:配置eslint
规则的时候可以指定的options
参数。通常传入的值为(不接收参数)和
object[]
。meta.messages
:一个对象,messageId: text
。create
方法:eslint
需要建立AST并遍历,所以要拿到这个方法的返回值作为遍历AST的配置。输入参数是context
对象,常用的方法有:context.options[0]
获取传入的参数;context.getFilename()
获取当前yarn lint
正在解析的文件名;context.report
函数向用户报错,通常这么用:context.report( node, messageId: 'xxMessageId' )
,messageId
必须符合meta.messages
给出的定义。create
方法返回的对象有点类似于@babel/traverse
的traverse
方法的第二个参数,具体写法看参考链接1的项目就行。
import TSESLint, ASTUtils from '@typescript-eslint/utils';
import createRule from '../utils/createRule';
import path from 'path';
import multimatch from 'multimatch';
// 模仿babel中的写法 import isIdentifier from '@babel/types';
const
isIdentifier
= ASTUtils;
const whiteList = ['memory'];
const rule = createRule(
name: 'no-console',
meta:
docs:
description: 'Remember to delete console.method()',
recommended: 'error',
requiresTypeChecking: false
,
messages:
rememberToDelete: 'Remember to delete console.method()'
,
type: 'problem',
schema:
,
create (
context: Readonly<TSESLint.RuleContext<'rememberToDelete', never[]>>
)
return
MemberExpression (node)
if (isIdentifier(node.object) && node.object.name === 'console' &&
isIdentifier(node.property) && Object.prototype.hasOwnProperty.call(console, node.property.name) &&
!whiteList.includes(node.property.name)
)
context.report( node, messageId: 'rememberToDelete' );
;
);
export default rule;
代码传送门:src/rules/noConsole.ts
本地测试
单元测试:
yarn test
本地查看效果
首先:
yarn build
在另一个项目(这里用了相对路径,用绝对路径也行):
yarn add -D file:../eslint-plugin-use-i18n-hans
注意:每次重新build后都需要在另一个项目重新yarn add
这样会得到:
"devDependencies":
"@hans/eslint-plugin-use-i18n-hans": "file:../eslint-plugin-use-i18n-hans",
接下来配置.eslintrc.js
:
module.exports =
plugins: [
'@hans/use-i18n-hans',
],
extends: [
'plugin:@hans/use-i18n-hans/recommended',
],
插件名为@hans/use-i18n-hans
,使用了configs
中的recommended
。
最后重启vscode或运行yarn lint
就能看到我们的第一个eslint插件生效了。
<path>/file-encrypt/webpack-plugin-utils.js
16:5 error Remember to delete console.log() @hans/use-i18n-hans/no-console
no-console规则添加功能:排除用户指定的文件
修改一下meta.schema
,新增输入参数:
schema = [
properties:
excludedFiles:
type: 'array'
]
和对应的类型定义:
type Options = [
excludedFiles: string[];
];
create (
context: Readonly<TSESLint.RuleContext<'rememberToDelete', Options>>
)
然后在create
函数体加几句判定:
const fileName = context.getFilename();
const options = context.options[0] || ;
const excludedFiles = options;
if (Array.isArray(excludedFiles))
const excludedFilePaths = excludedFiles.map(excludedFile => path.resolve(excludedFile));
if (multimatch([fileName], excludedFilePaths).length > 0)
return ;
context.getFilename()
文档:https://eslint.org/docs/latest/extend/custom-rules#the-context-object 。其特性:在yarn test
时会返回file.ts
,在作为npm包引入另一个项目后,可以正常获取文件的绝对路径。
为了支持glob语法,我们引入了multimatch
。但需要指定版本为5.0.0,因为multimatch6.0.0
只支持es module,而我反复尝试都无法找到一个可以生效的jest
配置(transformIgnorePatterns
等配置项的资料都极少,这篇blog看上去操作性很强,但尝试后依旧无效……)。
构建完成后,我们可以在另一个项目尝试配置@hans/use-i18n-hans/no-console
规则:
'@hans/use-i18n-hans/no-console': ['error',
excludedFiles: [
'add-copyright-plugin.js',
'copyright-print.js',
'webpack-plugin-utils.js',
'src/utils/my-eslint-plugin-tests/no-warn-folder/**/*.js',
'tests/**/*.js',
],
],
.eslintrc.js
取消或添加注释并保存,vscode应该能立刻看到报错的产生和消失。
TODO:是否能够mock context.getFilename()
,让本地可以写测试用例?
发布npm包
TODO。实用的eslint规则写好后将更新文章~
参考资料
- 值得参考的教程:https://www.darraghoriordan.com/2021/11/06/how-to-write-an-eslint-plugin-typescript/
eslint
有编写自定义规则的官方文档:https://eslint.org/docs/latest/extend/custom-rules
以上是关于前端手把手教你用TypeScript写一个简单的eslint插件的主要内容,如果未能解决你的问题,请参考以下文章