[Java, Kotlin] Создаем свою инспекцию для IDEA
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Disclaimer: я не являюсь сотрудником JetBrains (а жаль), поэтому код может не являться оптимальным и служит только для примера.ВведениеКому не интересна вводная часть - можно сразу перейти к настройкепроекта.У каждой крупной организации со временем формируется набор правил по оформлению кода, который, в лучшем случае, фиксируется в Code Style (зачем он нужен можно прочитать здесь). Но самое сложное не написать свод правил, а заставить всех им следовать.
Поэтому в иделе все проверки должны быть автоматизированы. Для этого уже много что придумано, но самым удобным, как мне кажется, остаются средства проверки IDE. По результатам опроса наиболее популярной IDE для Java-разработчиков является IDEA от JetBrains.Практически любая редакция IDEA - это платформенная часть и набор плагинов к ней, т.е. поддержка практически любого языка добавляется отдельным плагином и, конечно, IDEA очень хорошо расширяема. Поэтому добавить новую инспекцию в нее - не проблема.Краткое ТЗСамая популярная коллекция (или точнее - почти коллекция) на собеседованиях - Map, самая популярная реализация - HashMap. И коронным вопросом является вопрос про equals и hashCode (почитать можно тут, тут, а лучше конечно в самой документации 1, 2).
Поэтому у нас в организации есть правило, что у класса, который используется как ключ для HashMap, эти методы должны быть переопределены (явно или неявно - например, с помощью Lombok). К сожалению, в IDEA это не добавили - вот issue. Но для простых случаев это не сложно сделать самим.Будем поддерживать только следующие выражения:
- new HashMap<>();
- new HashSet<>();
- .collect(Collectors.toSet());
- .collect(Collectors.toMap());
С чего начать?Главные отправные точки:
Так же можно посмотреть различные видео, например: habr, youtube канал JetBrainsНа данный момент самым удобным способом создания каркаса плагина является - шаблон. После клонирования выполняется несколько предустановленных pipeline и на выходе мы получаем хорошую заготовку с шагами, которые необходимо выполнить.
## Template ToDo list
- [x] Create a new [IntelliJ Platform Plugin Template][template] project.
- [ ] Verify the [pluginGroup](/gradle.properties), [plugin ID](/src/main/resources/META-INF/plugin.xml) and [sources package](/src/main/kotlin).
- [ ] Review the [Legal Agreements](https://plugins.jetbrains.com/docs/marketplace/legal-agreements.html).
- [ ] [Publish a plugin manually](https://plugins.jetbrains.com/docs/intellij/publishing-plugin.html?from=IJPluginTemplate) for the first time.
- [ ] Set the Plugin ID in the above README badges.
- [ ] Set the [Deployment Token](https://plugins.jetbrains.com/docs/marketplace/plugin-upload.html).
- [ ] Click the <kbd>Watch</kbd> button on the top of the [IntelliJ Platform Plugin Template][template] to be notified about releases containing new features and fixes.
Так же существует набор простых примеров, которые сильно помогают разобраться, как все должно работать.Писать код будем на Kotlin.РеализацияИсходный код можно посмотреть здесь. Плагин для IDEA должен содержать файл resources/META-INF/plugin.xml, в котором описывается какие действия и расширения он предоставляет. Допустимых точек расширения очень много - около 1000. Если посмотреть в примерах и в коде IDEA, то для реализации инспекции нам нужно реализовать localInspection (имплементацию класса LocalInspectionTool, для языка Java есть абстрактный класс AbstractBaseJavaLocalInspectionTool).
<extensions defaultExtensionNs="com.intellij">
<localInspection language="JAVA"
displayName="Sniffer: Using HashMap with default hashcode"
groupPath="Java"
groupBundle="messages.SnifferInspectionsBundle"
groupKey="group.names.sniffer.probable.bugs"
enabledByDefault="true"
level="WEAK WARNING"
implementationClass="com.github.pyltsin.sniffer.EqualsHashCodeOverrideInspection"/>
</extensions>
Для реализации проверки я воспользуюсь методом
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder,
final boolean isOnTheFly,
@NotNull LocalInspectionToolSession session)
который реализует паттер Visitor.IDEA для кода строит дерево (PSI Tree, PSI - Program Structure Interface). Для того чтобы его увидеть можно воспользоваться PSI Viewer(Tools->View PSI Structure)
Наш Visitor будет посещать все узлы этого дерева и, если определит, что что-то не так, зарегистрирует проблему. В классе JavaElementVisitor уже есть весь список узлов, которые можно анализировать. Вот схема как парсится и строится это дерево из документации:
Мы работаем с PSI Tree, который является результатом этого процесса.Сначала рассмотрим простой случай:
Узел дерева new HashMap<>() соответствует PsiNewExpression и поэтому в нашем Visitor нужно переопределить метод visitNewExpression:
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean,
session: LocalInspectionToolSession
): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitNewExpression(expression: PsiNewExpression?) {
super.visitNewExpression(expression)
}
}
А дальше нужно смотреть, какие методы есть у класса PsiNewExpression. Почти наверняка PSI-классы содержат все, что вам нужно. А если чего-то и нет, то стоит обратить внимание на утильные классы (например, PsiReferenceUtil, PsiTypesUtil). Сама IDEA содержит огромное число примеров кода.Первое, что нужно определить, что этот узел ссылается на классы HashMap или HashSet:
expression.classOrAnonymousClassReference?.qualifiedName
in (JAVA_UTIL_HASH_MAP, JAVA_UTIL_HASH_SET)
JAVA_UTIL_HASH_MAP, JAVA_UTIL_HASH_SET - String-константы из com.intellij.psi.CommonClassNames. Он содержит список наиболее используемых Java-классов.Дальше нам нужно получить класс ключа. Спасибо IDEA, она для нас уже нашла это значение (даже для diamond-оператора - <>)
val keyType: PsiType =
expression.classOrAnonymousClassReference?.parameterList?.typeArguments[0]
Осталось проверить, что класс, на который ссылается PsiType, содержит переопределенный hashCode метод. Я нашел следующий путь (возможно есть более оптимальный):
private fun hasOverrideHashCode(psiType: PsiType): Boolean {
// получаем PsiClass (представление класса)
val psiClass = PsiTypesUtil.getPsiClass(psiType)
// получаем список похожих методов
val methods: Array<PsiMethod> =
psiClass?.findMethodsByName(HardcodedMethodConstants.HASH_CODE, false) ?: arrayOf()
// проверяем, если есть hashCode
return methods.any { MethodUtils.isHashCode(it) }
}
C equals поступаем аналогично.И если все плохо - осталось только зарегистрировать проблему:
holder.registerProblem(
expression,
"Все плохо, гипс сняли, hashCode не переопределили"
)
Теперь разберемся со Stream. Например, есть вот такой код:
Map<Clazz2, Clazz2> collect1 = Stream.of(new Clazz2(), new Clazz2())
.collect(Collectors.toMap(t -> t, t -> t));
В этом случае мы работаем с PsiMethodCallExpression.
override fun visitMethodCallExpression(expression: PsiMethodCallExpression?)
Для первичной проверки можно воспользоваться CallMatcher (удобное средство для проверки совпадения методов):
val matcher = CallMatcher.instanceCall(JAVA_UTIL_STREAM_STREAM, "collect")
val isCollect = matcher.matches(expression)
После этого можно проверить Collectors.toMap():
val collectorExpression = expression.argumentList.expressions[0]
val isToMap = CallMatcher.staticCall(JAVA_UTIL_STREAM_COLLECTORS, "toMap")
.matches(collectorExpression)
Получаем первый generic для этого выражения:
val psiType = expression.methodExpression.type.parameters[0]
А дальше пользуемся уже реализованными функциями проверки переопределения equals и hashCode.Для тестирования можно воспользоваться классом LightJavaInspectionTestCase. Примеры можно посмотреть в исходном коде данного плагина или в исходном коде IDEA. Стоит обратить внимание, что для функционирования тестов, нужен приложенный набор стандартных библиотек - папки mockJDK в IDEA.И, наконец, сам результат
Этот пример покрывает не все места создания HashMap (HashSet) в стандартной библиотеке, но его можно быстро расширить на ваши случаи.Краткий выводIDEA очень гибкий и удобный инструмент для разработки, который можно быстро расширить требуемыми функциямиСсылки
- Документация по разработке плагинов
- Исходный код IDEA
- Шаблон для плагинов
- Примеры простых плагинов
- Использованный в статье исходный код
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, *nix, Локализация продуктов, Kotlin, Изучение языков] Интернационализация и локализация приложения на Kotlin/Native
- [Программирование, Разработка под Android, Kotlin] Как можно использовать шейдеры в Android View и как Android View может использовать шейдеры (перевод)
- [JavaScript, Node.JS, MongoDB] Passport.js + mongoose объединяем две коллекции
- [Разработка веб-сайтов, JavaScript, Программирование, HTML] Красивое радио для браузера
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS] React Social App
- [Kotlin] Погружение в JetBrains Space Applicaitons
- [Open source, Программирование, Геоинформационные сервисы, Визуализация данных, Научно-популярное] Google Earth Engine (GEE) как общедоступный суперкомпьютер
- [Open source, Разработка мобильных приложений, Разработка под Android, GitHub, Kotlin] Легкий DataBinding для Android
- [JavaScript, Работа с 3D-графикой, WebGL] Рендеринг шрифтов для WebGL при помощи инстумента msdf-bmfont-xml и технологии MSDF
- [Разработка веб-сайтов, JavaScript, Программирование, Node.JS] Дорожная карта для разработчиков Node.js на 2021 год (перевод)
Теги для поиска: #_java, #_kotlin, #_idea, #_inspection, #_hashmap, #_kotlin, #_intellij_idea, #_plugins, #_intellij_idea_plugin, #_java, #_kotlin
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 09:38
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Disclaimer: я не являюсь сотрудником JetBrains (а жаль), поэтому код может не являться оптимальным и служит только для примера.ВведениеКому не интересна вводная часть - можно сразу перейти к настройкепроекта.У каждой крупной организации со временем формируется набор правил по оформлению кода, который, в лучшем случае, фиксируется в Code Style (зачем он нужен можно прочитать здесь). Но самое сложное не написать свод правил, а заставить всех им следовать. Поэтому в иделе все проверки должны быть автоматизированы. Для этого уже много что придумано, но самым удобным, как мне кажется, остаются средства проверки IDE. По результатам опроса наиболее популярной IDE для Java-разработчиков является IDEA от JetBrains.Практически любая редакция IDEA - это платформенная часть и набор плагинов к ней, т.е. поддержка практически любого языка добавляется отдельным плагином и, конечно, IDEA очень хорошо расширяема. Поэтому добавить новую инспекцию в нее - не проблема.Краткое ТЗСамая популярная коллекция (или точнее - почти коллекция) на собеседованиях - Map, самая популярная реализация - HashMap. И коронным вопросом является вопрос про equals и hashCode (почитать можно тут, тут, а лучше конечно в самой документации 1, 2). Поэтому у нас в организации есть правило, что у класса, который используется как ключ для HashMap, эти методы должны быть переопределены (явно или неявно - например, с помощью Lombok). К сожалению, в IDEA это не добавили - вот issue. Но для простых случаев это не сложно сделать самим.Будем поддерживать только следующие выражения:
## Template ToDo list
- [x] Create a new [IntelliJ Platform Plugin Template][template] project. - [ ] Verify the [pluginGroup](/gradle.properties), [plugin ID](/src/main/resources/META-INF/plugin.xml) and [sources package](/src/main/kotlin). - [ ] Review the [Legal Agreements](https://plugins.jetbrains.com/docs/marketplace/legal-agreements.html). - [ ] [Publish a plugin manually](https://plugins.jetbrains.com/docs/intellij/publishing-plugin.html?from=IJPluginTemplate) for the first time. - [ ] Set the Plugin ID in the above README badges. - [ ] Set the [Deployment Token](https://plugins.jetbrains.com/docs/marketplace/plugin-upload.html). - [ ] Click the <kbd>Watch</kbd> button on the top of the [IntelliJ Platform Plugin Template][template] to be notified about releases containing new features and fixes. <extensions defaultExtensionNs="com.intellij">
<localInspection language="JAVA" displayName="Sniffer: Using HashMap with default hashcode" groupPath="Java" groupBundle="messages.SnifferInspectionsBundle" groupKey="group.names.sniffer.probable.bugs" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.github.pyltsin.sniffer.EqualsHashCodeOverrideInspection"/> </extensions> public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder,
final boolean isOnTheFly, @NotNull LocalInspectionToolSession session) Наш Visitor будет посещать все узлы этого дерева и, если определит, что что-то не так, зарегистрирует проблему. В классе JavaElementVisitor уже есть весь список узлов, которые можно анализировать. Вот схема как парсится и строится это дерево из документации: Мы работаем с PSI Tree, который является результатом этого процесса.Сначала рассмотрим простой случай: Узел дерева new HashMap<>() соответствует PsiNewExpression и поэтому в нашем Visitor нужно переопределить метод visitNewExpression: override fun buildVisitor(
holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession ): PsiElementVisitor { return object : JavaElementVisitor() { override fun visitNewExpression(expression: PsiNewExpression?) { super.visitNewExpression(expression) } } expression.classOrAnonymousClassReference?.qualifiedName
in (JAVA_UTIL_HASH_MAP, JAVA_UTIL_HASH_SET) val keyType: PsiType =
expression.classOrAnonymousClassReference?.parameterList?.typeArguments[0] private fun hasOverrideHashCode(psiType: PsiType): Boolean {
// получаем PsiClass (представление класса) val psiClass = PsiTypesUtil.getPsiClass(psiType) // получаем список похожих методов val methods: Array<PsiMethod> = psiClass?.findMethodsByName(HardcodedMethodConstants.HASH_CODE, false) ?: arrayOf() // проверяем, если есть hashCode return methods.any { MethodUtils.isHashCode(it) } } holder.registerProblem(
expression, "Все плохо, гипс сняли, hashCode не переопределили" ) Map<Clazz2, Clazz2> collect1 = Stream.of(new Clazz2(), new Clazz2())
.collect(Collectors.toMap(t -> t, t -> t)); В этом случае мы работаем с PsiMethodCallExpression. override fun visitMethodCallExpression(expression: PsiMethodCallExpression?)
val matcher = CallMatcher.instanceCall(JAVA_UTIL_STREAM_STREAM, "collect")
val isCollect = matcher.matches(expression) val collectorExpression = expression.argumentList.expressions[0]
val isToMap = CallMatcher.staticCall(JAVA_UTIL_STREAM_COLLECTORS, "toMap") .matches(collectorExpression) val psiType = expression.methodExpression.type.parameters[0]
Этот пример покрывает не все места создания HashMap (HashSet) в стандартной библиотеке, но его можно быстро расширить на ваши случаи.Краткий выводIDEA очень гибкий и удобный инструмент для разработки, который можно быстро расширить требуемыми функциямиСсылки
=========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 09:38
Часовой пояс: UTC + 5