Используйте эту формулу для написания собственных правил линтера.
В тот редкий момент, когда системы были отключены и работа не могла быть выполнена, я думал обо всех способах улучшить нашу кодовую базу. В связи с этим я подумал о том, чтобы ввести специальные правила, обеспечивающие соблюдение передового опыта наших компаний. По общему признанию, создание собственных пользовательских правил линтера было непростой задачей, но я чувствовал, что это еще один инструмент для повышения качества нашего кода.
Я погрузился в нашу обширную кодовую базу, чтобы собрать примеры кода, нарушающего эти принципы, и приступил к рефакторингу кода, отражающего наши лучшие практики. Я сохранил оба образца в отдельных папках, зная, что они могут понадобиться мне позже. Я просмотрел основной репозиторий ESLint, чтобы получить представление о структуре кода и прочитать документацию ESLint для пользовательских плагинов. Взволнованный написанием своего правила, я запустил свой VSCode и создал нужные мне каталоги. Готовый к написанию кода, я начал с простой логики линтинга после настройки основных файлов. Сюрприз! Это не сработало. После нескольких часов разочарования мне удалось получить надежное рабочее правило, которое я мог расширить. Однако эти усилия не были напрасными, поскольку я узнал так много, что в итоге написал четыре сложных и запутанных правила, охватывающих множество вариантов для разработчиков в моей компании.
Простой, но масштабируемый
Я не хочу, чтобы вы страдали так же, как я, поэтому я предлагаю рецепт, следуя которому, вы сможете установить любое правило, какое душе угодно. Предлагаемое мной решение может показаться чрезмерным для простого правила, как в примере, которым я поделюсь, но оно хорошо масштабируется по мере увеличения количества правил.
Установка пакета
По соглашению ESLint добавьте к пакету префикс eslint-plugin-
(т. е. eslint-plugin-awesome), чтобы использовать его в своих плагинах eslintConfig как [«awesome»]. Затем создайте файл package.json (yarn init -y
), добавив следующие зависимости:
yarn add @babel/core @babel/eslint-parser eslint jest jest-cli
Примечание. Если вам нужна поддержка декораторов или свойств класса, добавьте следующее:
yarn add @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators
Настройка папки
Придерживайтесь одного и того же имени файла и соглашения об именовании файлов kebab для правил, документов и тестов, чтобы быстро находить связанные файлы для каждого правила.
├── index.js ├── package.json ├── src │ ├── rules │ │ └── no-to-string.js │ ├── docs │ │ └── no-to-string.md │ └── utils │ ├── eslint-options.js │ └── helper.js |__tests__ └── no-to-string.js
Настройка файла ввода
В index.js укажите, какие правила доступны в вашем подключаемом модуле ESLint. Сделайте этот файл простым, перечислив имя правила в случае кебаба и импортировав определение правила в случае верблюда из другого файла.
// index.js const noToString = require('./src/rules/no-to-string'); module.exports = { rules: { 'no-to-string': noToString } };
Правило
Определение правила в самом минимальном состоянии требует имя и функцию создать. Функция create ищет указанную вами часть AST. В нашем примере мы статически анализируем наш код для идентификатора с именем «toString». Если бы я удалил [name=”toString”]
, я бы получил линтер-сообщение для каждого идентификатора, независимо от того, был ли он вызван toString. Это не только расстраивает пользователя, который ошибочно думает, что что-то не так с его кодом, но и неэффективно и увеличивает время, необходимое для анализа ваших файлов.
const lintToString = context => node => { context.report({ node, message: 'Do not use toString()' }); }; module.exports = { name: 'no-to-string', create(context) { return { 'Identifier[name="toString"]': lintToString(context) }; } };
Хорошо, давай, спроси меня, я знаю, ты думаешь: откуда ты знаешь, что искать? Во-первых, это практика, после написания и переписывания большого количества правил вы начинаете изучать, какие элементы вы ищете. Во-вторых, я использовал AST Explorer, который позволяет просматривать код в виде абстрактного синтаксического дерева (AST).
AST представляет собой древовидное представление абстрактной синтаксической структуры исходного кода, написанного на языке программирования. ~ Википедия
Хотите узнать, как выглядит настоящее определение правила? Из-за проприетарного характера моего кода я не могу поделиться этим, но я поделюсь простым примером, чтобы показать вам, как применить вышеизложенные идеи при написании «настоящего» правила для определения кода с помощью функции MobX toJS().
Изучите приведенный ниже пример, сравнив его с деревом выше.
- В чем разница? Название.
- В чем сходство? Родительским элементом является CallExpression.
Работа с ложными срабатываниями
Сделав еще один шаг вперед, я хотел исключить любые экземпляры toJS(), появляющиеся в импорте, поэтому перед узлом Identifier я поставил CallExpression (т. е. CallExpression Identifier[name=”toJS”]
)
Окончательное определение правила
// rules/no-to-js.js const messages = { invalid: 'Do not use toJS().' }; const noToJS = context => node => { if (node) { context.report({ node, message: messages.invalid }); } }; module.exports = { name: 'no-to-js', type: 'problem', meta: { docs: {description: 'Disallows use of toJS function'}, messages }, create(context) { return { 'CallExpression Identifier[name="toJS"]': noToJS(context) }; } };
Проверка правила локально
Отлично, мы написали правило! Но нам все еще нужно убедиться, что когда линтер обнаруживает недопустимый код, он выводит строку в messages.invalid.
. Для удобства ESLint предоставляет функцию RuleTester для проверки вашего правила как на допустимый, так и на недопустимый код, но для этого все еще требуется небольшая настройка. Если ваш код использует последние функции ES, обновите параметры ESLint и передайте их в RuleTester.
// utils/eslint-options.js const eslintOptions = { parser: '@babel/eslint-parser', parserOptions: { ecmaVersion: 2020, sourceType: 'module' } }; module.exports = {eslintOptions};
Файл теста правила
Возьмите столько примеров правильного и неправильного кода из вашей кодовой базы, сколько сможете найти, а затем включите их в свой тестовый файл.
// __tests__/no-to-js.js const {RuleTester} = require('eslint'); const rule = require('../src/rules/no-to-js'); const {eslintOptions} = require('../src/utils/eslint-options'); const ruleName = 'no-to-js'; const ruleTester = new RuleTester(eslintOptions); const validCode1 = ` import {toJS} from 'mobx'; class Hello {} `; const invalidCode1 = ` import {toJS} from 'mobx'; class Hello { constructor() { toJS(this); } } `; ruleTester.run(ruleName, rule, { valid: [{ code: validCode1 }], invalid: [{ code: invalidCode1, errors: ['Do not use toJS().'] }] });
Поскольку мы уже установили jest
, запуститеyarn test
в eslint-plugin-awesome, а затем продолжайте корректировать логику линтинга вашего правила, пока тест не пройдет. После успешного запуска проверьте правило на новом или существующем проекте.
Тестирование правила в репозитории
- Создайте новый проект под названием my-project.
yarn add mobx @babel/core @babel/eslint-parser eslint @babel/preset-env
- Добавьте babel и eslintConfig в ваш package.json
// package.json "babel": { "presets": [ "@babel/preset-env" ] }, "eslintConfig": { "parser": "@babel/eslint-parser", "plugins": [ "awesome"], "rules": { "awesome/no-to-js": 1 } }
Если вам нужна поддержка декораторов, добавьте parserOptions в конфигурацию lint.
"eslintConfig": { ... "parserOptions": { "ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": { "legacyDecorators": true } },
- Убедитесь, что
eslint
установлен, и добавьте скрипт lint.
// package.json "scripts": { "lint": "eslint ." },
- Добавьте файл index.js со следующим кодом:
//index.js const { toJS } = require('mobx'); class Hello { constructor() { toJS(this); console.log(`Hi ‘js’`, toJS(this)); } }
Ссылка на модуль для тестирования
- В eslint-plugin-awesome запустите
yarn link
- В мой-проект запустите
yarn link eslint-plugin-awesome
- В мой-проект запустите
yarn lint
, чтобы увидеть вывод, похожий на следующие строки:
my-project/index.js
Предупреждение 4:5 Не используйте toJS() awesome/no-to-js
Предупреждение 5:28 Не используйте toJS() awesome/no-to-js
Заключительные примечания
Будьте терпеливы с собой, пока пытаетесь выполнить первое правило. Начните с базовой функции, как это сделали мы, чтобы вы могли понять, как называется каждый узел и как они формируют синтаксическое дерево. Я хотел бы увидеть, какие правила вы пишете, поэтому, пожалуйста, используйте раздел комментариев, чтобы показать мне, что вы сделали.
Удачного кодирования!