Разработать правила eslint

внешний интерфейс Jest ESLint Yeoman
Разработать правила eslint

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

Сегодняшняя тема — поговорить об eslint, как о самом популярном инструменте JavaScript lint, который очень любим всеми, но JSHint постепенно исчез из поля зрения всех и используется все реже.

Обычно используемые расширения eslint — стандартные, airbnb и т. д.

Анатомия расширения eslint

Расширение — это не что иное, как две вещи

  • Настройте некоторую конфигурацию на основе исходного eslint (конкретные параметры правил, глобальные переменные, рабочая среда и т. д.)
  • Настройте свои собственные правила в соответствии с вашими потребностями

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

Первый подробно не описан, на официальном сайте eslint очень подробно написано, в основном каждое правило поддерживает пользовательские параметры, и охват очень широкий, в принципе, все грамматики имеют правила.

Второе пользовательское правило является основным моментом этой статьи, потому что в особых бизнес-сценариях собственная конфигурация eslint больше не может соответствовать бизнес-потребностям, таким как:

  • eslint-plugin-vue
  • eslint-plugin-react
  • eslint-plugin-jest

Используются общие пользовательские правила для особых сценариевeslint-plugin-*Именование может быть удобно записано как

{
  plugins: [
    'vue',
    'react',
    'jest'
  ]
}

Конечно, eslint-config-* тоже самое, но его нужно написать так

{
  extends: 'standard'
}

Далее описывается процесс разработки

Создать проект плагина eslint

Официальная рекомендация - использовать yoman для генерации проектов. Я чувствую, что сгенерированные проекты относительно старомодны. Рекомендуется привыкнуть к моей структуре проекта.

eslint-plugin-skr
  |- __tests__
  |  |- rules
  |  |- utils
  |
  |- lib
  |  |- rules
  |  |- utils
  |  |- index.js
  |
  |- jest.config.js
  |
  |- package.json
  |
  |- README.md

Глядя в целом, я обнаружил, что есть больше файлов конфигурации jest. Да, проекты, созданные yeoman, используют Mocha в качестве тестовой среды по умолчанию. Лично я чувствую, что отладка вызывает затруднения. Он не такой гибкий, как jest, и vscode может легко отладить его.

Найдите много туториалов, дайте ссылку на хэнд патиdebugging-jest-tests

Конфиг файл jest так же выложен.Все это базовые конфигурации, которые не используются для сложных.Тестовая часть будет подробно представлена ​​ниже.

module.exports = {
  testEnvironment: 'node',
  roots: ['__tests__'],
  resetModules: true,
  clearMocks: true,
  verbose: true
}

Все пользовательские правила находятся в lib/rules, и для каждого правила достаточно одного файла.

Ниже приведен простой пример открытия двух вен Жэнь и Ду.

разработать правило

Предварительная подготовка

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

У некоторых возникают вопросы: нужно ли владеть AST?

Мой ответ, конечно, не нужен, простое понимание, хотя бы знать, как выглядит общая структура разобранного синтаксического дерева

Тогда напишите себе название! Напишите супер просто

module.exports = {
  meta: {
    docs: {
      description: '禁止块级注释',
      category: 'Stylistic Issues',
      recommended: true
    }
  },

  create (context) {
    const sourceCode = context.getSourceCode()

    return {
      Program () {
        const comments = sourceCode.getAllComments()

        const blockComments = comments.filter(({ type }) => type === 'Block')

        blockComments.length && context.report({
          message: 'No block comments'
        })
      }
    }
  }
}

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

немного более сложная сцена

ворс, если нужноbarПорядок свойств в объекте при следующем правиле

// good
const bar = {
  meta: {},
  double: num => num * 2
}

// bed
const bar = {
  double: num => num * 2,
  meta: {},
}

Это первый раз, будет немного запутанно, на официальном сайте конкретных примеров нет, решение очень простое, порекомендуйте оружиеastexplorer

Нажмите, не спешите копировать код, чтобы просмотреть результаты AST, сначала выберите espree (библиотека анализа синтаксиса, используемая eslint), как показано ниже.

Эти короткие четыре строки кода будут соответствовать абстрактному синтаксическому дереву, как показано ниже:

Поскольку полное расширение слишком длинное, если вы хотите попробовать его самостоятельно, вы обнаружите, что уровень вложенности особенно глубок.barСвойства требуютProgram.body[0].declarations[0].init.properties

Конечно не каждый раз сверхуProgramНайдите его, как видно из приведенного выше примераcreateметодreturnВозвращается объект, который может определять множество типов обнаружения, например, пример официального сайта:

function checkLastSegment (node) {
  // report problem for function if last code path segment is reachable
}

module.exports = {
  meta: { ... },
  create: function(context) {
    // declare the state of the rule
    return {
      ReturnStatement: function(node) {
        // at a ReturnStatement node while going down
      },
      // at a function expression node while going up:
      "FunctionExpression:exit": checkLastSegment,
      "ArrowFunctionExpression:exit": checkLastSegment,
      onCodePathStart: function (codePath, node) {
        // at the start of analyzing a code path
      },
      onCodePathEnd: function(codePath, node) {
        // at the end of analyzing a code path
      }
    }
  }
}

можно использовать здесьVariableDeclaratorТип используется в качестве цели проверки, а условия фильтрации можно проанализировать из следующего дерева синтаксического анализа.

кVariableDeclaratorобъект как текущийnode

когда переменная названаbar,Прямо сейчасnode.id.name === 'bar', а значением является объект, т.е.node.init.type === 'ObjectExpression', код показан ниже:

module.exports = {
  meta: { ... },
  create (context) {
    return {
      VariableDeclarator (node) {
        const isBarObj = node.id.name === 'bar' &&
          node.init.type === 'ObjectExpression'

        if (!isBarObj) return

        // checker
      }
    }
  }
}

такой успешныйbarПосле объекта вы можете определить порядок атрибутов.Алгоритмов сортировки много, просто выберите понравившийся и используйте его.Я не буду вдаваться в подробности, просто перейду непосредственно к результату:

const ORDER = ['meta', 'double']

function getOrderMap () {
  const orderMap = new Map()

  ORDER.forEach((name, i) => {
    orderMap.set(name, i)
  })

  return orderMap
}

module.exports = {
  create (context) {
    const orderMap = getOrderMap()

    function checkOrder (propertiesNodes) {
      const properties = propertiesNodes
        .filter(property => property.type === 'Property')
        .map(property => property.key)

      properties.forEach((property, i) => {
        const propertiesAbove = properties.slice(0, i)
        const unorderedProperties = propertiesAbove
          .filter(p => orderMap.get(p.name) > orderMap.get(property.name))
          .sort((p1, p2) => orderMap.get(p1.name) > orderMap.get(p2.name))

        const firstUnorderedProperty = unorderedProperties[0]

        if (firstUnorderedProperty) {
          const line = firstUnorderedProperty.loc.start.line

          context.report({
            node: property,
            message: `The "{{name}}" property should be above the "{{firstUnorderedPropertyName}}" property on line {{line}}.`,
            data: {
              name: property.name,
              firstUnorderedPropertyName: firstUnorderedProperty.name,
              line
            }
          })
        }
      })
    }

    return {
      VariableDeclarator (node) {
        const isBarObj = node.id.name === 'bar' &&
          node.init.type === 'ObjectExpression'

        if (!isBarObj) return

        checkOrder(node.init.properties)
      }
    }
  }
}

Кода здесь очень много, его на самом деле достаточно просто терпеливо прочитать, поясню примерно.

getOrderMapМетод преобразует массив в тип карты, и аспект передаетсяgetПолучите индекс, вы также можете обрабатывать массивы с несколькими широтами, например, дваkeyНадежда на один и тот же уровень сортировки, независимо от верха и низа, может быть записана как:

const order = [
  'meta'
  ['double', 'treble']
]

function getOrderMap () {
  const orderMap = new Map()

  ORDER.forEach((name, i) => {
    if (Array.isArray(property)) {
      property.forEach(p => orderMap.set(p, i))
    } else {
      orderMap.set(property, i)
    }
  })

  return orderMap
}

такdoubleа такжеtrebleОн имеет тот же уровень, что удобно для последующего расширения.Конечно, в реальности будет правило сортировки из n признаков, и его также можно легко расширить на это правило, и внутренняя логика сортировки не будет повторяться.

Здесь представлена ​​разработка, и логику lint можно легко вывести с помощью онлайн-инструмента анализа грамматики Amway, описанного выше.

Если правило более сложное, оно нуждается в поддержке большого количества утилит, иначе каждое правило будет казаться беспорядком, что проверит возможность извлечения общего кода.

тестовое задание

Как упоминалось ранее, для тестирования рекомендуется использовать jest.Тесты здесь не такие, как обычные юнит-тесты.Eslint — тест, основанный на результатах.Что это значит?

Есть только два кейса lint, pass и fail, нужно только организовать кейсы pass и fail в два массива, а остальная работа передается eslintRuleTesterПросто смирись с этим

Приведенное выше правило сортировки свойств, тест выглядит следующим образом:

const RuleTester = require('eslint').RuleTester
const rule = require('../../lib/rules/test')

const ruleTester = new RuleTester({
  parserOptions: {
    ecmaVersion: 6
  }
})

ruleTester.run('test rule', rule, {
  valid: [
    `const bar = {
      meta: {},
      double: num => num * 2
    }`
  ],
  invalid: [
    {
      code: `const bar = {
        double: num => num * 2,
        meta: {},
      }`,
      errors: [{
        message: 'The "meta" property should be above the "double" property on line 2.'
      }]
    }
  ]
})

validЕсть надежда, что через код,invalidЕсть коды и сообщения об ошибках, которые вы не хотите передавать Здесь правило действительно выполнено.

Упакованный вывод

Окончательные написанные правила необходимо отправить в npm-пакет для удобства использования в проекте.Я не буду здесь вдаваться в подробности того, как отправить пакет, а просто расскажу о том, как элегантно экспортировать правила.

Перейдите непосредственно к коду:

const requireIndex = require('requireindex')

// import all rules in lib/rules
module.exports.rules = requireIndex(`${__dirname}/rules`)

Здесь используются трехсторонние зависимостиrequireindex, намного проще экспортировать все файлы в папке пакетами.

Конечно, предпосылка состоит в том, чтобы убедиться, что в папке правил есть файлы правил, и не писать в них утилиты.

Суммировать

Цель текста в том, что ресурсов, связанных с кастомными правилами eslint в стране и за границей, немного, и я надеюсь поделиться некоторым опытом написания кастомных правил.

Не тратьте время на изучение AST. Различные библиотеки реализуют AST по-разному. В следующий раз, когда вы будете писать плагин для Babel, вам придется изучить другие правила AST и снова использовать артефакт AST.astexplorer, просто введите код, который необходимо проверить, вastexplorerЗапустите его один раз, а затем суммируйте правила.Логика на самом деле очень проста, просто оцените результаты AST.

На командном уровне я надеюсь, что у всех команд есть своя собственная база правил eslint, которая может значительно снизить стоимость проверки кода и раз и навсегда обеспечить согласованность кода.