TypeScript手寫一個簡單的eslint插件實例
引言
看到參考鏈接1以后,覺得用TS寫一個eslint插件應該很簡單??????,嘗試下來確實如此。
前置知識
本文假設
- 你對AST遍歷有所了解。
- 你寫過單測用例。
第一個eslint規(guī)則: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環(huán)境運行上面指定的入口點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可以理解為具體的規(guī)則定義;configs可以理解為規(guī)則的集合,可以稱為“最佳實踐”,最常見的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函數來創(chuàng)建一條規(guī)則。它需要傳一個對象,我列舉一下這個對象常用的幾個屬性:
meta.schema:配置eslint規(guī)則的時候可以指定的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規(guī)則添加功能:排除用戶指定的文件
修改一下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()文檔:eslint.org/docs/latest… 。其特性:在yarn test時會返回file.ts,在作為npm包引入另一個項目后,可以正常獲取文件的絕對路徑。
為了支持glob語法,我們引入了multimatch。但需要指定版本為5.0.0,因為multimatch6.0.0只支持es module,而我反復嘗試都無法找到一個可以生效的jest配置(transformIgnorePatterns等配置項的資料都極少,這篇blog看上去操作性很強,但嘗試后依舊無效……)。
構建完成后,我們可以在另一個項目嘗試配置@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應該能立刻看到報錯的產生和消失。
TODO:是否能夠mock context.getFilename(),讓本地可以寫測試用例?
發(fā)布npm包
TODO。實用的eslint規(guī)則寫好后將更新文章~
參考資料
- 值得參考的教程:www.darraghoriordan.com/2021/11/06/…
eslint有編寫自定義規(guī)則的官方文檔:eslint.org/docs/latest…
以上就是TypeScript手寫一個簡單的eslint插件實例的詳細內容,更多關于TypeScript eslint插件的資料請關注腳本之家其它相關文章!
相關文章
Spartacus中navigation?item?reducer實現(xiàn)解析
這篇文章主要為大家介紹了Spartacus中navigation?item?reducer實現(xiàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07
TypeScript類型編程中的extends和infer示例解析
這篇文章主要為大家介紹了TypeScript類型編程中的extends和infer示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
xterm.js在web端實現(xiàn)Terminal示例詳解
這篇文章主要為大家介紹了xterm.js在web端實現(xiàn)Terminal示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
ThreeJS使用紋理貼圖創(chuàng)建一個我的世界草地方塊
這篇文章主要為大家介紹了ThreeJS使用紋理貼圖創(chuàng)建一個我的世界草地方塊的實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06
TypeScript實現(xiàn)十大排序算法之冒泡排序示例詳解
這篇文章主要為大家介紹了TypeScript實現(xiàn)十大排序算法之冒泡排序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02
詳解什么是TypeScript里的Constructor?signature
這篇文章主要介紹了什么是TypeScript里的Constructor?signature詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07
TypeScript類型any never void和unknown使用場景區(qū)別
這篇文章主要為大家介紹了TypeScript類型any never void和unknown使用場景區(qū)別,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10

