TypeScript手寫一個(gè)簡(jiǎn)單的eslint插件實(shí)例
引言
看到參考鏈接1以后,覺得用TS寫一個(gè)eslint插件應(yīng)該很簡(jiǎn)單??????,嘗試下來確實(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"]
。接下來設(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é)論:一般來說需要提供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ù)來創(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", } }
接下來配置.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語法,我們引入了multimatch
。但需要指定版本為5.0.0,因?yàn)?code>multimatch6.0.0只支持es module,而我反復(fù)嘗試都無法找到一個(gè)可以生效的jest
配置(transformIgnorePatterns
等配置項(xiàng)的資料都極少,這篇blog看上去操作性很強(qiáng),但嘗試后依舊無效……)。
構(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-07TypeScript類型編程中的extends和infer示例解析
這篇文章主要為大家介紹了TypeScript類型編程中的extends和infer示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08TypeScript快速學(xué)習(xí)入門基礎(chǔ)語法
TypeScript的基礎(chǔ)語法,包括變量聲明、復(fù)合類型(數(shù)組和對(duì)象)、條件控制(if-else和switch)、循環(huán)(for和while)、函數(shù)(基礎(chǔ)和箭頭函數(shù),以及可選參數(shù))、面向?qū)ο筇匦裕杜e、接口、繼承)以及模塊開發(fā)中的導(dǎo)出和導(dǎo)入2024-07-07xterm.js在web端實(shí)現(xiàn)Terminal示例詳解
這篇文章主要為大家介紹了xterm.js在web端實(shí)現(xiàn)Terminal示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11ThreeJS使用紋理貼圖創(chuàng)建一個(gè)我的世界草地方塊
這篇文章主要為大家介紹了ThreeJS使用紋理貼圖創(chuàng)建一個(gè)我的世界草地方塊的實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06TypeScript實(shí)現(xiàn)十大排序算法之冒泡排序示例詳解
這篇文章主要為大家介紹了TypeScript實(shí)現(xiàn)十大排序算法之冒泡排序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02詳解什么是TypeScript里的Constructor?signature
這篇文章主要介紹了什么是TypeScript里的Constructor?signature詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07TypeScript類型any never void和unknown使用場(chǎng)景區(qū)別
這篇文章主要為大家介紹了TypeScript類型any never void和unknown使用場(chǎng)景區(qū)別,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10