Узнайте, как ESLint создает правила тестирования, чтобы обеспечить правильное использование компонентов дизайн-системы в производстве.

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

Чтобы убедиться, что ваша дизайн-система используется по назначению, и уменьшить количество возможных ошибок, вы можете захотеть, чтобы ваши пользователи придерживались ваших лучших практик. Ниже приведены два примера возможных передовых практик:

  • Избегайте встроенных стилей в ваших элементах
  • Убедитесь, что всплывающие подсказки не содержат интерактивного контента.

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

Скорее всего, вы не единственный человек, разрабатывающий дизайн-систему, и вы точно не будете присутствовать, когда кто-то ее использует. Как вы можете быть уверены, что все следуют лучшим практикам вашей дизайн-системы? Вы можете скрестить пальцы и быть уверенными, что ваши конечные пользователи прочитают документацию, прислушаются к вашим предупреждениям и всегда будут соблюдать ваши правила.

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

Отличный способ побудить ваших потребителей следовать вашим рекомендациям — использовать ESLint, инструмент статического анализа для поиска проблем в вашем коде.

По умолчанию ESLint поставляется с несколькими общими рекомендациями, которые называются rules и будут отображать красные волнистые линии в вашей IDE, если правила были нарушены. Некоторые из этих правил включают в себя:

  • Отсутствие повторяющихся ключей в объектах
  • Нет недостижимого кода
  • Нет неиспользуемых переменных

Однако правила, которые вы включаете в свой проект, не обязательно должны исходить непосредственно из ESLint. Популярные библиотеки, такие как Cypress, Lodash и React, имеют конфигурации ESLint, которые каждый может использовать в своих проектах, чтобы пользователи придерживались лучших практик. Если вы бесстрашный исследователь языка JavaScript, вы можете пойти дальше и создать собственные правила, характерные для вашей системы дизайна, которые вы можете экспортировать, чтобы другие люди могли использовать их в своих проектах.

В этой статье мы потратим немного времени на то, чтобы понять, как такие инструменты, как ESLint, анализируют JavaScript в структуру данных, называемую абстрактным синтаксическим деревом (AST). Затем мы коснемся того, как работают правила ESLint и как преобразовать наши шаблоны Lit в HTML. Наконец, мы начнем создавать наши первые правила. Мы даже будем использовать встроенный инструмент тестирования ESLint, чтобы убедиться, что наши правила работают в различных условиях.

Предварительным условием для этой статьи является некоторое знание JavaScript + HTML. Небольшой опыт использования ESLint и Lit может пригодиться, но не обязателен.

Что такое абстрактное синтаксическое дерево?

Для тех, кто раньше не замарал руки компиляторами, осмысление того, как человекочитаемый язык, который мы пишем в нашей IDE, понимается (и преобразуется) такими инструментами, как Webpack, Prettier и Babel, может чувствовать себя как магия.

Под капотом, когда такой инструмент, как ESLint, хочет начать выполнять действия с вашим JavaScript, он анализирует ваш код. Синтаксический анализ — это процесс преобразования написанного вами JavaScript в древовидное представление кода, абстрактное синтаксическое дерево (AST).

Этот процесс разбора разделен на две части: разметка и построение дерева.

Токенизация берет код и разбивает его на вещи, называемые токенами, которые описывают изолированные части синтаксиса.

Токены для программы JavaScript, например:

будет выглядеть примерно так:

Вторым шагом в процессе синтаксического анализа является построение дерева, при котором токены переформатируются в AST. AST описывает каждую часть синтаксиса и ее связь с другими.

Мы использовали js-токены как быстрый способ токенизации JS для этого примера, но мы не будем заниматься токенизацией непосредственно в этой статье.

Мы можем визуализировать эту связь, разобрав следующий оператор JavaScript:

Он будет преобразован в AST со следующей структурой:

Такие инструменты, как Babel и Prettier, превращают ваш написанный JavaScript в AST для анализа и преобразования написанного нами кода. Babel использует AST для преобразования нашего кода в удобную для браузера версию JavaScript, а Prettier использует AST для переформатирования вашего кода.

Любопытство с помощью AST Explorer

Чтобы по-настоящему изучить, как выглядит AST, поиграйте с AST explorer. Ознакомьтесь с проводником AST, так как мы собираемся использовать его загрузки позже в этой статье.

Напишите простое утверждение, подобное следующему:

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

Если вы наведете курсор на VariableDeclaration, вы увидите, что весь оператор слева выделен. Если мы углубимся в массив declarations, вы увидите дополнительный узел VariableDeclarator. Если мы продолжим, то в конце концов достигнем дна. В случае нашего оператора hello world это значение переменной Identifier и значение переменной Literal.

Давайте вернемся к нашему компоненту из предыдущего:

Если вы пройдётесь по дереву в проводнике AST, вы увидите, что структура соответствует нашему изображению ранее. Обратите особое внимание на узел TaggedTemplateExpression и узел TemplateLiteral. Это те, которые пригодятся, когда мы будем писать наши правила ESLint.

Наш вызов функции html — это выражение, но оно немного отличается от других определений функций. Давайте посмотрим, как AST отличается от выражения, подобного следующему:

Если мы наведем указатель мыши на heyThere() ExpressionStatement, мы увидим, что свойства соответствуют нашему html ExpressionStatement. Основное отличие состоит в том, что значение свойства expression выглядит иначе. На этот раз выражение представляет собой CallExpression, набор свойств которого отличается от нашего TaggedTemplateExpression.

Если мы оглянемся назад на наш TaggedTemplateExpression, то увидим, что у нас есть такие свойства, как тег и квази.

Тег дает нам некоторые сведения об имени функции. В данном случае это html.

Это означает, что при написании нашего правила ESlint мы сможем сделать что-то вроде этого:

Наконец, если вы посмотрите на объект TaggedTemplateExpression, вы увидите свойство с именем quasi. Это свойство содержит два наших примечательных свойства expressions и quasis. Возьмем следующее выражение:

Зеленое подчеркивание, второе, будет жить в массиве expressions и предоставляет ссылку на имя переменной. Как и quasis, элементы в массиве расположены в том порядке, в котором они определены. Это позволяет очень легко согласовать литерал вашего шаблона позже.

Вот вопрос к вам, что произойдет, если первый символ литерала нашего шаблона является выражением? Как это представлено в нашем AST? Попробуйте следующий фрагмент в проводнике AST:

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

При доступе к значению в ваших квазивы увидите строку, доступную как raw или cooked. Эти значения определяют, будут ли escape-последовательности игнорироваться или интерпретироваться. Аксель Раушмайер подробнее рассказывает об этом в этой статье.

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

Если вам интересно узнать немного больше обо всем процессе компиляции, Super Tiny Compiler — это действительно интересный способ создать собственный компилятор JavaScript, используя всего пару сотен строк кода.

Как работают правила Эслинта?

Шаблон посетителя

К счастью, нам не нужно делать какие-либо преобразования при написании правил ESLint, вместо этого мы пишем в нашем коде проверки для конкретных типов узлов. Эти узлы являются фрагментами AST нашего кода.

После того как ESLint преобразовывает ваш код в AST, он проходит по вашему дереву, посещая каждый узел на своем пути. Те, кто знаком с шаблонами проектирования программирования, могут узнать этот шаблон как шаблон visitor.

Шаблон посетителя — это способ запуска некоторой новой логики для объекта без изменения объекта. ESLint использует шаблон посетителя, чтобы отделить код, используемый для проверки вашего кода, от AST.

Давайте посмотрим на паттерн посетителя в действии.

Как видите, я реализовал посетителя, используя 3 блока кода:

  1. ast.js: AST для const name = 'andrico'
  2. traverser.js: Алгоритм, который проходит через узлы нашего AST.
  3. visitors.js: объект методов, в котором данный метод срабатывает, как только обходчик достигает соответствующего узла. В нашем случае, когда обходчик достигает узла VariableDeclarator, он запускает нашу функцию посетителя.

Давайте еще немного разберем traverser:

Мы начинаем с index.js с создания экземпляра нашего класса Traverser и прохождения через наш AST и наших посетителей. Под капотом наш класс Traverser хранит наш AST и посетителей как переменные экземпляра, которые мы можем использовать позже.

Затем мы вызываем метод экземпляра traverse. Если вы перейдете к файлу traverser.js, вы увидите, что когда мы вызываем traverse 5, могут происходить следующие вещи:

  1. Узел null, что произойдет, когда мы вручную вызовем метод traverse без каких-либо аргументов. Когда это происходит, мы запускаем функцию обхода, используя AST, который мы сохранили во время инициализации класса.
  2. узел имеет тип Program, который будет иметь место для узлов верхнего уровня в нашем AST. Когда это происходит, мы рекурсивно вызываем метод обхода дочерних узлов.
  3. Тип узла соответствует функции посетителя. Когда это происходит, мы запускаем нашу функцию посетителя и передаем узел в качестве аргумента.
  4. У узла есть дополнительные объявления, поэтому мы продолжаем вызывать нашу функцию обхода для этих дочерних объявлений.
  5. Наш узел не удовлетворяет ни одному из этих условий, что приведет к завершению работы нашего метода обхода.

В контексте нашего примера const name = 'andrico' наша функция обхода продолжит свой путь через AST, пока не достигнет VariableDeclarator, где она вызовет посетителя, которого мы определили в visitors.js. В этом посетителе мы проверяем, является ли значение Andrico, и если это так, мы регистрируем сообщение о том, что это недопустимое имя.

Откройте консоль в CodeSandbox и посмотрите, что она выводит. Попробуйте изменить чек в своем посетителе и посмотрите, что произойдет, если что.

Хорошая новость заключается в том, что ESLint обрабатывает логику обхода для нашего JavaScript. Другая хорошая новость заключается в том, что нам нужно реализовать логику обхода для нашего проанализированного HTML 😄

Как выглядит правило Эслинта?

Написание правила ESLint не требует ничего особенного, это просто старый добрый объект JavaScript. Верхний уровень объекта может получить два свойства: meta и create.

meta предоставляет метаданные для правила.

Свойство create — это функция, возвращающая объект посетителей, который ESLint вызывает при посещении каждого узла. Это следует тому же принципу, что и фрагменты в codeandbox. И так же, как в демо в нашей codeandbox, имя каждой функции посетителя — это имя узла, который мы хотим посетить.

На самом деле, мы можем даже перепрофилировать псевдокод из более раннего и украсить его специальным шаблоном ESLint:

Функция create также предоставляет объект контекста, который предоставляет некоторые дополнительные помощники и информацию о текущем правиле. Помощник, который нас сейчас больше всего интересует, — это метод report(). Мы можем вызывать report всякий раз, когда хотим, чтобы ошибка ESLint отображалась в консоли или в среде IDE.

Context.report принимает объект с несколькими свойствами, но нас больше всего интересуют следующие:

  • сообщение: описание проблемы
  • узел: узел AST, связанный с проблемой

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

Имея базовое понимание AST JavaScript, шаблона посетителя и анатомии правила ESLint, единственное, что осталось охватить, — это то, как преобразовать строку нашего шаблона в HTML, прежде чем мы сможем начать создавать наши правила.

Для более глубокого изучения анатомии правила ESLint нет лучшего места, чем официальная документация.

Как мы можем преобразовать наши шаблоны в HTML?

При использовании ESLint у нас есть роскошь ESLint, предоставляющая нам проанализированный JavaScript AST. И хотя ESLint не может анализировать наш HTML, мы можем использовать такую ​​библиотеку, как parse5, для преобразования допустимой строки HTML в структуру данных, мало чем отличающуюся от нашего JavaScript AST.

Проводник AST, на изучение которого мы потратили так много времени, даже имеет настройки для отображения HTML AST.

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

Если мы углубимся в AST и найдем наш div, мы увидим, что нам представлена ​​некоторая полезная информация. Наиболее заметными являются:

tagName: имя элемента html. (в данном случае div).

attrs: массив атрибутов, представленный парой ключ-значение. Свойство attrs нашего div содержит один элемент. Элемент имеет name из style и value из display:inline;.

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

Вот как мы можем анализировать наши шаблоны JavaScript с помощью библиотеки parse5:

Благодаря таким инструментам, как parse 5 и ESLint, мы можем снять большую часть сложной обработки и сосредоточиться на написании кода для наших конкретных правил.

Идти дальше

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

Первоначально опубликовано на https://backlight.dev и написано@AndricoKaroulla 16 ноября 2021 г.

Ресурсы:

Песочницы:

Ресурсы: