TypeScript手寫一個(gè)簡(jiǎn)單的eslint插件實(shí)例
引言
看到參考鏈接1以后,覺得用TS寫一個(gè)eslint插件應(yīng)該很簡(jiǎn)單??????,嘗試下來(lái)確實(shí)如此。
前置知識(shí)
本文假設(shè)
- 你對(duì)AST遍歷有所了解。
- 你寫過單測(cè)用例。
第一個(gè)eslint規(guī)則:no-console
為了簡(jiǎn)單,我們只使用tsc進(jìn)行構(gòu)建。首先package.json需要設(shè)置入口"main": "dist/index.js",,tsconfig.json需要設(shè)置"outDir": "dist"、"include": ["src"]。接下來(lái)設(shè)計(jì)一下單元測(cè)試和構(gòu)建命令:
"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都能運(yùn)行,我選擇了jest。
當(dāng)我們運(yùn)行yarn lint時(shí),node_modules/@eslint/eslintrc/lib/config-array-factory.js的this._loadPlugin會(huì)加載插件,相當(dāng)于在node環(huán)境運(yùn)行上面指定的入口點(diǎn)dist/index.js。所以我們需要知道@eslint如何描述一個(gè)eslint插件,才能寫出src/index.ts。查看this._loadPlugin可知,plugin.definition的類型應(yīng)為:
type Plugin = {
configs?: Record<string, ConfigData> | undefined;
environments?: Record<string, Environment> | undefined;
processors?: Record<string, Processor> | undefined;
rules?: Record<...> | undefined;
}
結(jié)合參考鏈接1,我們得出結(jié)論:一般來(lái)說(shuō)需要提供rules和configs屬性。rules可以理解為具體的規(guī)則定義;configs可以理解為規(guī)則的集合,可以稱為“最佳實(shí)踐”,最常見的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函數(shù)來(lái)創(chuàng)建一條規(guī)則。它需要傳一個(gè)對(duì)象,我列舉一下這個(gè)對(duì)象常用的幾個(gè)屬性:
meta.schema:配置eslint規(guī)則的時(shí)候可以指定的options參數(shù)。通常傳入的值為{}(不接收參數(shù))和object[]。meta.messages:一個(gè)對(duì)象,{ messageId: text }。create方法:eslint需要建立AST并遍歷,所以要拿到這個(gè)方法的返回值作為遍歷AST的配置。輸入?yún)?shù)是context對(duì)象,常用的方法有:context.options[0]獲取傳入的參數(shù);context.getFilename()獲取當(dāng)前yarn lint正在解析的文件名;context.report函數(shù)向用戶報(bào)錯(cuò),通常這么用:context.report({ node, messageId: 'xxMessageId' }),messageId必須符合meta.messages給出的定義。create方法返回的對(duì)象有點(diǎn)類似于@babel/traverse的traverse方法的第二個(gè)參數(shù),具體寫法看參考鏈接1的項(xiàng)目就行。
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
本地測(cè)試
單元測(cè)試:
yarn test
本地查看效果
首先:
yarn build
在另一個(gè)項(xiàng)目(這里用了相對(duì)路徑,用絕對(duì)路徑也行):
yarn add -D file:../eslint-plugin-use-i18n-hans
注意:每次重新build后都需要在另一個(gè)項(xiàng)目重新yarn add
這樣會(huì)得到:
{
"devDependencies": {
"@hans/eslint-plugin-use-i18n-hans": "file:../eslint-plugin-use-i18n-hans",
}
}
接下來(lái)配置.eslintrc.js:
module.exports = {
plugins: [
'@hans/use-i18n-hans',
],
extends: [
'plugin:@hans/use-i18n-hans/recommended',
],
}
插件名為@hans/use-i18n-hans,使用了configs中的recommended。
最后重啟vscode或運(yùn)行yarn lint就能看到我們的第一個(gè)eslint插件生效了。
<path>/file-encrypt/webpack-plugin-utils.js 16:5 error Remember to delete console.log() @hans/use-i18n-hans/no-console

no-console規(guī)則添加功能:排除用戶指定的文件
修改一下meta.schema,新增輸入?yún)?shù):
schema = [
{
properties: {
excludedFiles: {
type: 'array'
}
}
}
]
和對(duì)應(yīng)的類型定義:
type Options = [{
excludedFiles: string[];
}];
{
create (
context: Readonly<TSESLint.RuleContext<'rememberToDelete', Options>>
) {}
}
然后在create函數(shù)體加幾句判定:
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()文檔:eslint.org/docs/latest… 。其特性:在yarn test時(shí)會(huì)返回file.ts,在作為npm包引入另一個(gè)項(xiàng)目后,可以正常獲取文件的絕對(duì)路徑。
為了支持glob語(yǔ)法,我們引入了multimatch。但需要指定版本為5.0.0,因?yàn)?code>multimatch6.0.0只支持es module,而我反復(fù)嘗試都無(wú)法找到一個(gè)可以生效的jest配置(transformIgnorePatterns等配置項(xiàng)的資料都極少,這篇blog看上去操作性很強(qiáng),但嘗試后依舊無(wú)效……)。
構(gòu)建完成后,我們可以在另一個(gè)項(xiàng)目嘗試配置@hans/use-i18n-hans/no-console規(guī)則:
'@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應(yīng)該能立刻看到報(bào)錯(cuò)的產(chǎn)生和消失。
TODO:是否能夠mock context.getFilename(),讓本地可以寫測(cè)試用例?
發(fā)布npm包
TODO。實(shí)用的eslint規(guī)則寫好后將更新文章~
參考資料
- 值得參考的教程:www.darraghoriordan.com/2021/11/06/…
eslint有編寫自定義規(guī)則的官方文檔:eslint.org/docs/latest…
以上就是TypeScript手寫一個(gè)簡(jiǎn)單的eslint插件實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于TypeScript eslint插件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spartacus中navigation?item?reducer實(shí)現(xiàn)解析
這篇文章主要為大家介紹了Spartacus中navigation?item?reducer實(shí)現(xiàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
TypeScript類型編程中的extends和infer示例解析
這篇文章主要為大家介紹了TypeScript類型編程中的extends和infer示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
TypeScript快速學(xué)習(xí)入門基礎(chǔ)語(yǔ)法
TypeScript的基礎(chǔ)語(yǔ)法,包括變量聲明、復(fù)合類型(數(shù)組和對(duì)象)、條件控制(if-else和switch)、循環(huán)(for和while)、函數(shù)(基礎(chǔ)和箭頭函數(shù),以及可選參數(shù))、面向?qū)ο筇匦裕杜e、接口、繼承)以及模塊開發(fā)中的導(dǎo)出和導(dǎo)入2024-07-07
xterm.js在web端實(shí)現(xiàn)Terminal示例詳解
這篇文章主要為大家介紹了xterm.js在web端實(shí)現(xiàn)Terminal示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
ThreeJS使用紋理貼圖創(chuàng)建一個(gè)我的世界草地方塊
這篇文章主要為大家介紹了ThreeJS使用紋理貼圖創(chuàng)建一個(gè)我的世界草地方塊的實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
TypeScript實(shí)現(xiàn)十大排序算法之冒泡排序示例詳解
這篇文章主要為大家介紹了TypeScript實(shí)現(xiàn)十大排序算法之冒泡排序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
詳解什么是TypeScript里的Constructor?signature
這篇文章主要介紹了什么是TypeScript里的Constructor?signature詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
TypeScript類型any never void和unknown使用場(chǎng)景區(qū)別
這篇文章主要為大家介紹了TypeScript類型any never void和unknown使用場(chǎng)景區(qū)別,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10

