В этой статье я покажу несколько способов создания собственного плагина для Eslint.
Первый способ создать правило — создать js-файл с правилом и экспортировать его.
Структура для обоих случаев:
Итак, внутри папки плагинов нам нужно создать индексный файл:
// index.js const noDeprecatedMethod = require('./no-deprecated-method.js'); module.exports = { rules: { 'no-deprecated-method': noDeprecatedMethod, }, };
Здесь мы будем импортировать правила, которые нам нужны и как они будут называться внутри eslintrc.
Простой пример пользовательского правила с заменой deprecatedMethod на newMethod будет выглядеть так:
module.exports = { meta: { messages: { avoidName: "Avoid using variables named '{{ name }}'", // Custom error message for the rule }, type: 'problem', // The type of ESLint rule (problem, suggestion, etc.) fixable: 'code', // Indicates that this rule can automatically fix code issues }, create(context) { return { Identifier(node) { if (node.name === 'deprecatedMethod') { context.report({ node, // The AST node that triggered the error messageId: 'avoidName', // The identifier for the custom error message data: { name: 'deprecatedMethod', // Data to be used in the error message }, fix(fixer) { // The fixer function for automatically fixing the reported error return fixer.replaceText(node, 'newMethod'); // Replaces 'deprecatedMethod' with 'newMethod' }, }); } }, }; }, };
Здесь мы определяем сообщения об ошибках, находим узлы по имени и заменяем их на newMethod.
И теперь нам нужно определить путь к нашему плагину в package.json.
"devDependencies": { "eslint-plugin-no-deprecated": "file:plugins/no-deprecated", }
Также нам нужно добавить наш плагин в eslintrc:
{ "root": true, "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "plugin:storybook/recommended" ], "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint", "no-deprecated", // Adding our custom plugin ], "rules": { "no-deprecated/no-deprecated-method": 2, // Setting our custom rule type (as error in this case) }, "env": { "browser": true, "node": true }, }
Не забывайте, что мы добавили путь в пакет json как «eslint-plugin-no-deprecated», но внутри плагинов мы отбрасываем часть «eslint-plugin» и указываем только «no-deprecated».
Вот как будет выглядеть ошибка:
Второй способ аналогичен предыдущему, но в комплекте с машинописным текстом.
Для начала мы можем создать проект vanilla ts с помощью:
yarn create vite
Удалите все, что находится внутри папки src, и измените пакет json.
{ "name": "eslint-plugin-custom-rule", "version": "1.0.0", "main": "cjs/index.js", // Changing main js for bundled files "typings": "cjs/index.d.ts", // Changing typing for bundled files "private": true, "dependencies": { "@typescript-eslint/utils": "^6.1.0" }, "devDependencies": { "@typescript-eslint/parser": "^6.1.0", "@typescript-eslint/rule-tester": "^6.1.0", "eslint": "^8.45.0", "typescript": "^5.1.6", "vitest": "^0.33.0" }, "scripts": { "build": "yarn tsc -b", // Creatating build "test": "vitest" } }
То же самое, что и в примере js, нам нужно создать индексный файл с правилами:
import { TSESLint } from '@typescript-eslint/utils'; import groupUpImportsByType from './group-up-imports-by-type'; export const rules = { 'new-line-import-group': groupUpImportsByType, } satisfies Record<string, TSESLint.RuleModule<string, Array<unknown>>>;
Для набора текста мы можем использовать пакет @typescript-eslint/utils.
Пользовательское правило для группировки импорта по типу будет выглядеть следующим образом:
import { TSESLint, TSESTree } from '@typescript-eslint/utils'; // Define custom message identifiers for ESLint messages type MessageIds = 'messageIdNoLine'; // Define the ESLint rule and its properties const groupUpImportsByType: TSESLint.RuleModule<MessageIds> = { defaultOptions: [], // Default options for the rule (none in this case) meta: { type: 'problem', // The type of ESLint rule (problem, suggestion, etc.) fixable: 'code', // Indicates that this rule can automatically fix code issues messages: { messageIdNoLine: 'No new line before multiline import', // Custom error message }, schema: [], // Configuration schema for this rule (none in this case) }, create(context: TSESLint.RuleContext<MessageIds, []>) { let lastGroupType = ''; // Initialize a variable to track the last import group return { ImportDeclaration(node: TSESTree.ImportDeclaration) { const importPath = node.source.value; // Get the path of the import statement let currentGroupType = ''; // Initialize a variable to track the current import group type // Determine the import group type based on the import path if (importPath.startsWith('.')) { currentGroupType = 'local folder files'; } else if (importPath.startsWith('@')) { currentGroupType = 'internal modules'; } else { currentGroupType = 'external modules'; } const sourceCode = context.getSourceCode(); const prevNodeToken = sourceCode.getTokenBefore(node); // Get the token before the current import statement // Check if there is a token before the current import statement if (!prevNodeToken) { return; // If there is no token before, exit the function } // Calculate the index of the previous token's location in the source code const prevNodeIndex = sourceCode.getIndexFromLoc(prevNodeToken.loc.start); // Get the node (AST node) corresponding to the previous token's location const prevNode = sourceCode.getNodeByRangeIndex(prevNodeIndex); // Check if the previous node is an 'ImportDeclaration' node const isPrevNodeImportType = prevNode?.type === 'ImportDeclaration'; // If the previous node is not an 'ImportDeclaration', like 'Punctuator', etc..., exit the function if (!isPrevNodeImportType) { return; } // Calculate whether a newline is needed before the current import statement const isNewlineNeeded = node.loc.start.line - 1 === prevNode.loc.end.line; // Check if a new line is needed before the import statement and report an error if necessary if (lastGroupType !== '' && lastGroupType !== currentGroupType) { if (isNewlineNeeded) { context.report({ node, // The AST node that triggered the error messageId: 'messageIdNoLine', // The custom message identifier ('messageIdNoLine' in this case) fix(fixer: TSESLint.RuleFixer) { // A function to provide a fix for the reported error return fixer.insertTextBefore(node, '\n'); // The fixer inserts a newline before the 'node' }, }); } } lastGroupType = currentGroupType; // Update the last import group type }, }; }, }; export default groupUpImportsByType;
Здесь мы получаем importPath и создаем тип, для которого добавим пустую строку перед группировкой импорта.
Прежде чем добавлять правило в пакет json, нам нужно запустить сборку пряжи, чтобы создать пакет.
Таким же образом добавим правило в пакет json и elsintrc:
{ "name": "i18next-react-config", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite --open", "build": "tsc && vite build", "lint": "eslint --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "test": "jest --coverage", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" }, "dependencies": { ... }, "devDependencies": { ... "eslint-plugin-no-deprecated": "file:plugins/no-deprecated", "eslint-plugin-group-imports": "file:plugins/group-imports" } } { "root": true, "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "plugin:storybook/recommended" ], "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint", "no-deprecated", "group-imports" ], "rules": { "no-deprecated/no-deprecated-method": 2, "group-imports/new-line-import-group": 2, }, "env": { "browser": true, "node": true } }
Вот как будет выглядеть ошибка импорта до исправления:
После исправления eslint:
Также может быть задержка с WebStorm/IntelliJIdea, когда пользовательское правило не применяется, но работает с командой терминала. В этом случае мы можем вручную добавить пользовательское правило:
Подсказка: в большинстве случаев нам это не нужно, поможет перезагрузка WebStorm.
Документация для пользовательских правил eslint: https://eslint.org/docs/latest/extend/custom-rules
Фрагмент, в котором вы можете отладить свое пользовательское правило:
https://astexplorer.net /#/gist/f121a2a9edea666731e75aae1d013c9d/latest
Вот и все, надеюсь, вам было интересно.
Если у вас есть какие-либо предложения, вы можете добавить комментарий.
Исходный код: https://github.com/pryvalovbogdan/i18next-react-config/tree/add-custom-eslint-rule
Подписывайтесь, если вам интересны такие примеры.