Ну, если под любимым языком подразумевается русский, английский и т. д., то это в другой хаб. А если язык программирования или разметки, то конечно писать анализатор самим! На первый взгляд, это очень сложно, но, к счастью, существуют готовые многоязыковые инструменты, в которые относительно легко добавить поддержку нового языка. Сегодня я покажу, как можно с достаточно незначительными затратами времени добавить поддержку языка Modelica в анализатор PMD. Кстати, знаете, что может ухудшить качество кодовой базы, полученной из последовательности идеальных pull request-ов? Тот факт, что сторонние программисты копировали в свои патчи куски существующего кода проекта вместо грамотного абстрагирования. Согласитесь, в какой-то мере такую банальность отловить ещё сложнее, чем некачественный код — он же качественный и даже уже тщательно отлаженный, поэтому тут недостаточно локальной проверки, нужно держать в голове всю кодовую базу, а человеку это непросто… Так вот: если на добавление полной поддержки Modelica (без создания конкретных правил) до состояния «может запускать примитивные проверки» у меня ушло около недели, то поддержку только copy-paste detector часто можно вообще добавить за день! Modelica — это, как можно догадаться из названия, язык для написания моделей физических систем. На самом деле, не только физических: можно описывать химические процессы, количественное поведение популяций животных и т. д. — то, что описывается системами дифференциальных уравнений вида der(X) = f(X), где X — вектор неизвестных. Императивные куски кода тоже поддерживаются. Уравнения в частных производных в явном виде не поддерживаются, но можно разбить исследуемую область на кусочки (как мы бы, наверное, и делали на каком-нибудь языке общего назначения), а потом записать уравнения для каждого элемента, сведя задачу к предыдущей. Прикол же Моделики в том, что решение этого самого der(X) = f(X) ложится на компилятор: вы можете просто поменять солвер в настройках, уравнение не обязано быть линейным и т. д. Короче говоря, есть как свои плюсы (выписал формулу из учебника — и оно заработало), так и минусы (с большей абстракцией мы получаем меньший контроль). Введение в Моделику — тема отдельной статьи (которые уже на Хабре несколько раз появлялись), а то и целого цикла, сегодня же она меня интересует как открытый и имеющий несколько реализаций, но, увы, всё ещё сыроватый стандарт. К тому же, Моделика, с одной стороны, имеет статическую типизацию (что нам поможет быстрее написать какой-нибудь осмысленный анализ), с другой, инстанциируя модель, компилятор не обязан полностью проверять всю библиотеку (поэтому статический анализатор весьма полезен для отлова «спящих» багов). Наконец, в отличие от какого-нибудь C++, для которого существует туча статических анализаторов и компиляторов с прекрасной, а главное развёрнутой, см. C++ templates диагностикой ошибок, компиляторы Моделики периодически всё же выдают Internal compiler error, а значит, есть где помочь пользователю даже с достаточно простым анализатором. Я отвечу песней байкой. Когда-то я хотел сделать какой-то небольшой pull request в среду разработки для OpenModelica. Увидев, как в другой части кода обрабатывается сохранение модели, я заметил маленький и не очень понятный внутри кусок кода строчек из четырёх, который поддерживал какой-то инвариант. Не поняв, с какими именно внутренностями редактора он взаимодействует, но осознав, что с точки зрения этого кусочка кода моя задача полностью идентична, я просто вынес его в функцию, чтобы переиспользовать у себя и не сломать. Ментейнер сказал, мол, замечательно, только тогда уж замени этот код на вызов функции и в остальных двадцати местах… Я решил пока не связываться, и просто сделал ещё одну копию, отметив, что потом как-нибудь нужно будет сразу всё причесать, не смешивая с текущим патчем. Погуглив, я нашёл Copy-paste Detector (CPD) — часть статического анализатора PMD — который поддерживает ещё больше языков, чем сам анализатор. Натравив его на кодовую базу OMEdit, я ожидал увидеть те два десятка кусочков из четырёх строк. Их-то я как раз не увидел (каждый из них просто по количеству токенов не превысил порог), зато увидел, например, повторение чуть ли не на полсотни строк C++ кода. Как я уже говорил, вряд ли ментейнер так запросто скопипастил гигантский кусок из другого файла. А вот пропустить такое в PR он мог запросто — ведь код по определению уже соответствовал всем стандартам проекта! Когда я поделился наблюдением с ментейнером, он согласился, что нужно будет прибраться в рамках отдельной задачи. Соответственно, Program Mistake Detector (PMD) — это легко расширяемый статический анализатор. Может, множества значений, которые может принимать переменная, он и не вычисляет (хотя кто его знает...), но для добавления в него правил даже не нужно знать Java и вообще как-то менять его код! Дело в том, что первым делом он, что и неудивительно, строит AST файлов с исходниками. А на что похоже дерево разбора исходника? На дерево разбора XML! Значит, можно описывать правила просто как XPath запросы — на что совпало, на то и выдаём предупреждение. У них даже графический отладчик для правил есть! Более сложные правила, конечно, можно писать прямо на Java в виде visitor-ов для AST. Следствие: PMD можно использовать не только для суровых и универсальных правил, которые суровые Java-программисты закоммитили в код анализатора, но и для местных coding style — хоть в каждый репозиторий свой собственный местный ruleset.xml загоняй! В принципе, добавить поддержку нового языка в CPD часто очень просто. Дословно пересказывать документацию «как делать» смысла не вижу — она очень понятная, структурированная и пошаговая. Такую пересказывать — только в испорченный телефон играть. Лучше опишу, что вас при этом ждёт (TLDR: ничего страшного): Сразу предупрежу, что разработку я веду на Ubuntu. На Windows оно тоже должно работать отлично — и в смысле качества, и в смысле незначительно отличающегося способа запуска тулов. Итак, чтобы добавить новый язык в CPD, нужно всего лишь... Теперь, находясь в корне репозитория pmd, вы можете набрать ./mvnw clean verify, при этом в pmd-dist/target вы получите среди прочего binary distribution в виде zip-архива, который нужно распаковать и запустить с помощью ./bin/run.sh cpd --minimum-tokens 100 --files /path/to/source/dir --language <your language name> из распакованного каталога. В принципе, можно выполнить ../mvnw clean verify изнутри вашего нового модуля, что радикально ускорит сборку, но потом придётся правильно подложить собранный jar-ник в распакованный binary distribution (например, собранный единожды после регистрации нового модуля). Как я уже говорил, полную поддержку Antlr обещают в PMD 7. Если же вам, как и мне, не хочется ждать у моря релиза, то придётся откуда-то получить описание грамматики языка в формате JJTree. Может, можно и самим накостылить поддержку произвольного парсера — в документации говорят, что это возможно, но не рассказывают, как именно… Я же просто взял за основу modelica.g4 из всё того же репозитория с грамматиками для Anltr, и вручную переделал в JJTree. Естественно, если грамматика получилась переработкой существующей, опять же, укажите источник, проверьте соблюдение лицензий и. т. д. Кстати, для человека, хорошо разбирающегося во всевозможных генераторах парсеров, это вряд ли станет сюрпризом. Я же до этого «всерьёз» пользовался разве что собственноручно написанными регулярками и parser combinator-ами на Scala. Поэтому очевидная, в сущности, вещь поначалу меня опечалила: AST я, конечно, по modelica.g4 получу, но выглядит оно не очень понятно и «юзабельно»: в нём будут тучи лишних узлов, а если не смотреть на токены, а только на узлы, то не всегда понятно, где, например, кончается ветка then, и начинается else. Опять же, не буду пересказывать документацию на JJTree и неплохой tutorial — в этот раз, правда, не потому, что оригинал блещет подробностью и понятностью, а потому, что я и сам до конца не разобрался, а неправильно, но с уверенностью пересказанная документация, очевидно, хуже отсутствия пересказа. Лучше оставлю небольшие подсказки, выясненные по ходу дела: Теперь, когда у вас есть описание грамматики языка в формате JJTree, эти простые 14 шагов помогут вам добавить поддержку языка. Большинство из них имеют вид «создайте класс похожий на реализацию для java или vm, но адаптированный». Отмечу лишь типичные особенности, часть из них появятся в основной документации, если примут мой pull request для документации: Возможно, у вас получится всё отладить без визуализатора. Но зачем? Во-первых, допилить его очень просто. Во-вторых, это очень сильно поможет вашим пользователям, не знакомым с Java: они легко и просто (если это вообще применимо к XPath), ну или хотя бы без перекомпиляции PMD смогут описывать простые паттерны того, что им не нравится (в простейшем случае — style guide вроде «имя моделиковского пакета всегда начинается со строчной p»). В отличие от остальных ошибок, которые сразу видны, проблемы с PMD Designer ведут себя достаточно коварно: казалось бы, вы уже поняли, что та надпись Java в правой части меню — это не кнопка, а выпадающий список выбора языка O_o, в котором уже появилась Modelica, потому что в classpath появился новый модуль с регистрациями точек входа. Но вот вы выбираете свой язык, загружаете тестовый файл, и видите AST. И вроде бы, победа, но какое-то оно чёрно-белое, да и выделенное поддерево можно было бы в тексте подсветить — хотя нет, подсветка есть, но обновляется криво — а ещё, как же они не догадались подсвечивать найденные совпадения с XPath… Уже прикидывая объём работ, вы задумываетесь об очередном pull request-е, но тут случайно решаете переключить язык на Java и загрузить какой-нибудь исходник самого PMD… Ой! Оно цветное!.. И подсветка поддерева работает! Эээ… а оно, оказывается, нормально подсвечивает найденные совпадения и выписывает куски текста в окошко справа от запроса… Такое ощущение, что когда в JavaFX-коде во время отрисовки интерфейса происходит exception, он прерывает отрисовку, но не печатается в консоль... В общем, нужно всего лишь добавить ма-а-аленький класс для подсветки синтаксиса на основе регулярных выражений. В моём случае это был net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.ModelicaSyntaxHighlighter, который нужно зарегистрировать в классе AvailableSyntaxHighlighters. Обратите внимание, что оба этих изменения происходят в репозитории pmd-designer, артефакт от сборки которого нужно подложить в свой binary distribution. В итоге это выглядит примерно так (гифка взята из README в репозитории PMD Designer):  Если вы прошли все указанные уровни, то у вас теперь есть: Надеюсь, у вас также есть понимание того факта, что грамматика теперь является стабильным API вашей реализации поддержки языка — не меняйте её (а точнее описываемую ею функцию преобразования исходника в AST) без крайней необходимости, а уж если поменяли, оповещайте как о breaking change, а то пользователи расстроятся: скорее всего, не все напишут тесты на свои правила, а это очень грустно, когда правила проверяли код, а потом без предупреждения перестали — почти как бекап, совершенно внезапно сломавшийся, причём год назад... На этом история не заканчивается: предстоит как минимум написать какие-нибудь полезные правила. Но и это ещё не всё: PMD штатно поддерживает scopes и declarations. С каждым узлом AST ассоциирован scope: тело класса, функции, цикла… Весь файл, на худой конец! А в каждом скоупе есть список определений (declarations), которые он непосредственно содержит. Как и в других случаях, предлагается реализовывать по аналогии с другими языками, например, Моделикой (но на момент написания, логика в моём pull request-е, прямо скажем, сыровата). Код вычисления scopes и declarations представляет из себя visitor, называемый как-нибудь вроде ScopeAndDeclarationFinder, который бегает по дереву и развешивает по нему требуемые ярлычки — в целом, как и обычное правило, только правила, я всё-таки думаю, обычно read-only по отношению к AST. Запускается всё это через ещё один метод класса, описывающего нашу поддержку языка. Статический анализатор PMD является универсальным и легко расширяемым. Скорее всего, он будет проигрывать специализированным «монстрам» вроде Clang Static Analyzer для конкретных языков, но он очень полезен для реализации статического анализа для нового языка. Но даже если анализаторы уже имеются, поддержку языка можно добавить в CPD (а это ещё проще), и получить поиск скопированных кусков кода в пределах проекта. Программист