Используйте эту формулу для написания собственных правил линтера.

В тот редкий момент, когда системы были отключены и работа не могла быть выполнена, я думал обо всех способах улучшить нашу кодовую базу. В связи с этим я подумал о том, чтобы ввести специальные правила, обеспечивающие соблюдение передового опыта наших компаний. По общему признанию, создание собственных пользовательских правил линтера было непростой задачей, но я чувствовал, что это еще один инструмент для повышения качества нашего кода.

Я погрузился в нашу обширную кодовую базу, чтобы собрать примеры кода, нарушающего эти принципы, и приступил к рефакторингу кода, отражающего наши лучшие практики. Я сохранил оба образца в отдельных папках, зная, что они могут понадобиться мне позже. Я просмотрел основной репозиторий 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

Заключительные примечания

Будьте терпеливы с собой, пока пытаетесь выполнить первое правило. Начните с базовой функции, как это сделали мы, чтобы вы могли понять, как называется каждый узел и как они формируют синтаксическое дерево. Я хотел бы увидеть, какие правила вы пишете, поэтому, пожалуйста, используйте раздел комментариев, чтобы показать мне, что вы сделали.

Удачного кодирования!