readable — еще один линтер для PHP

    Хочу представить линтер для PHP readable. Сейчас в нем 16 правил, которые должны улучшить читабельность кода. К основным преимуществам можно отнести:


    • скорость — меньше секунды на 1000 файлов
    • настройка baseline — можно не исправлять все ошибки в проекте сразу, а создать конфигурацию с текущими ошибками. И игнорировать их, но реагировать на новые.
    • правила писать проще чем в аналогах (субъективно)


    Важные два дисклаймера:


    1. Я основной контрибьютор, поэтому могу быть субъективным. Изначально, readable написан для бельгийской компании officient. Они уже некоторое время его используют у себя внутри, и решили сделать readable открытым проектом.
    2. Он написан на JS.

    Установка и запуск


    readable устанавливаеться через npm:


    $ npm install @officient/readable --save-dev

    После установки, надо создать конфигурационный файл:


    $ npx readable --init

    И можно запускать:


    $ npx readable

    Правила


    Сейчас в readable 16 правил:


    1. namespace-max-files — максимальное количество файлов в неймспейсе
    2. argument-override — запрет изменения значения аргументов функции
    3. file-max-size — максимальное количество строк в файле
    4. empty-catch — запрет на пустой catch блок
    5. class-comment — перед объявлением класса должен быть какой-нибудь комментарий, что этот класс делает
    6. forbidden-functions — список запрещенных функций (eval, print_r ...)
    7. missing-braces — обязательные скобки в блоке после if, for ...
    8. variable-length — минимальная длина имени переменной (можно добавлять исключения $id, $i)
    9. function-max-size — максимальная длина функции
    10. loop-max-size — максимальная длина цикла
    11. forbidden-function-prefix — запрет на определенные слова в начале имени функции. Например, checkSomething — возвращает булево значение, или бросает исключение?
    12. if-assigment — присваивание внутри условия if
    13. complex-if — смешивание && и || внутри условия if
    14. ternary-max-length — максимальная длина строки с тернарным оператором
    15. loop-max-nest — максимальная вложенность циклов
    16. max-nest — максимальная вложенность блоков

    Создание правил и внутренняя особенность


    readable при проверки файла не строит синтаксическое дерево, а просто разбивает входной файл на массив токенов. Это — существенное отличие от аналогов. Возможно, это где-то ограничивает возможности, но с другой стороны:


    1. readable быстрее. Так как, построение дерева из токенов довольно ресурсоёмкая задача.
    2. Правила писать субъективно проще. Потому что, не надо разбираться в конкретном представлении синтаксического дерева.

    Давайте для примера возьмем правило loop-max-size. Весь его код:


    const loops = ['for', 'foreach'];
    
    module.exports = {
      check(maxLines, tokens, report) {
        tokens.matchAll(loops, (token) => {
          const end = token.copy().step().stepToClosing(); // skip ()
          end.step().stepToClosing();
          const lines = (end.current().line - token.current().line);
          if (lines > maxLines) {
            report(`Loop is longer than ${maxLines} lines [${lines}].`, token.current());
          }
        });
      },
    };

    В правиле указано:


    1. найти все for и foreach.
    2. Взять следующий токен .step(), это будет открывающая скобка (. Дойти до закрывающей скобки .stepToClosing().
    3. Повторить второй шаг но уже для тела цикла ({ и }).
    4. Проверить длину тела цикла.

    Вместо заключения


    Я использую readable в своих проектах как дополнительный линтер для кода. Мне все нравится, особенно baseline (справедливости ради, такое есть и в Psalm). У него есть возможность занять нишу именно вспомогательного линтера.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 15

      +8

      Все нравится кроме того что написан на js?
      Получается php-разработчику, работающему исключительно с php, придется разворачивать чуждые окружение и тулзы (node.js, npm).

        0
        Я озвучивал заказчику свои сомнения по этому поводу. Но, у них внутри все вспомогательные инструменты для сборки, развертывания и т.д. на яваскрипте. То, есть у них везде есть нода, и есть специалисты по яваскрипту. Для их процессов, и для компаний со схожими процессами будет нормально.
          +1
          Я так докатился до переписывания проекта и ухода от php в ноду :)
        +1

        Вам не кажется, что это будет ограничивать широкое применение вашего инструмента?

          0
          Ответил выше сразу на оба вопроса.
          +6

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


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


          Ну а про выбор языка Вам и так сказали, но ИМХО это уже не так принципиально по сравнению с тем, что Вы ограничиваете парсинг только токенами.

            0
            Я сверялся с eslint, flake8 и phpcs — и около 90% их проверок можно сделать без построения дерева. И да, readable — в первую очередь для проверки стиля. С терминами линтер не-линтер очень расплывчатая граница.
            0

            Как подключить к Sublime Text?

              0
              Пока, к сожалению, никак. Сейчас они разрабатывают плагин для VS code, и думаю о саблайме тоже думают.
              0
              Хочу задать глупый вопрос, чем так плоха функция print_r, что ее стоит запрещать?
                0

                С одной стороны она нужна только для отладки.


                А с другой стороны у меня в реальном продакшн коде она используется для формирования исключений, вида: "foo requires an argument $arg of type string, but (bool)true passed", где значения для скаляров как раз принтятся через print_r($arg, true).


                Так что да, линтер должен реагировать только на print_r(...) или print_r(..., false), это явная отладочная штука.

                  +1
                  В 95% случаев — это остатки вывода какойто отладочной информации. Но выше привели пример правильного использования, но и то при условии что это в лог пишет, который не публичный.
                  +1

                  Пожалуйста, поставляя подобные инструменты публике — поставляйте и, скажем, docker-образ с ним для быстрого запуска на проекте с целью оценить "как эта штука работает" в одну/две команды (особенно, если используется не "родной" язык). Да, PR с докер-файлом уже сделал, вам остается только настроить автоматическую сборку.


                  И ещё, из пожеланий:


                  • Наверное, не лишним будет держать package-lock.json для проекта (если рассматривать его именно как проект)
                  • Отчеты в форматах json и junit прям очень-очень просятся
                  • Кастомизация "под себя" не выглядит простой

                  И не совсем понятно, зачем это всё, если есть PHP-CS-Fixer, но вы большие молодцы, так как любые великие дела с чего-то да начинаются :)

                    +1
                    1. Спасибо за докер. Сейчас протестирую и смерджу.
                    2. package-lock.json в пакетах все равно игнорируется
                    3. Про json — хорошее замечание.
                    4. Кастомизация — создание новых правил? или настройка текущих?
                      +1

                      Да, для пакетов package-lock.json не должен быть, но если рассматривать проект как самостоятельную тулзу — то он очень желателен, так как и версии зависимостей у разработчиков будут одинаковые, сборка будет происходить быстрее (зная какие версии ставить, а не строя дерево из всех возможных), да и тот же dependabot можно натравить на репу — пускай он обновляет. Очень удобно.


                      Кстати, про junit не просто так говорю — тот же GitLab его очень хорошо умеет, отображаю плашку с замечаниями линтера прямо в MR.


                      Кастомизация — нашел в доке (читая в первый раз пропустил) каюсь

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое