From b7a73dd811fa818e767ce21ddfc04b1b6d6edf64 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Mon, 13 May 2024 07:22:07 +0200 Subject: [PATCH] IJPL-149476 convert InspectopediaExtractor to language with modern API (part 2) GitOrigin-RevId: 213d0689a764c7095395868a165a0a9fee2fdbde --- .../codeInspection/options/OptComponent.java | 4 +- .../ex/InspectionMetainformationService.kt | 24 +- .../extractor/InspectopediaExtractor.kt | 229 +++++++----------- .../inspectopedia/extractor/data/Plugin.java | 7 +- .../inspectopedia/extractor/data/Plugins.java | 38 --- .../extractor/utils/HtmlUtils.java | 3 +- 6 files changed, 107 insertions(+), 198 deletions(-) delete mode 100644 platform/inspect/src/com/intellij/inspectopedia/extractor/data/Plugins.java diff --git a/platform/analysis-api/src/com/intellij/codeInspection/options/OptComponent.java b/platform/analysis-api/src/com/intellij/codeInspection/options/OptComponent.java index 5bc997606933..0c760176d798 100644 --- a/platform/analysis-api/src/com/intellij/codeInspection/options/OptComponent.java +++ b/platform/analysis-api/src/com/intellij/codeInspection/options/OptComponent.java @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.codeInspection.options; import org.jetbrains.annotations.NotNull; @@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull; import java.util.List; /** - * Basic interface for all the components that could be displayed in options panel + * Basic interface for all the components that could be displayed in an options panel */ public sealed interface OptComponent permits OptControl, OptRegularComponent, OptTab { /** diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/ex/InspectionMetainformationService.kt b/platform/analysis-impl/src/com/intellij/codeInspection/ex/InspectionMetainformationService.kt index 27d78ca7f00c..4b0705b7236e 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/ex/InspectionMetainformationService.kt +++ b/platform/analysis-impl/src/com/intellij/codeInspection/ex/InspectionMetainformationService.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.codeInspection.ex import com.fasterxml.jackson.annotation.JsonCreator @@ -16,10 +16,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import java.util.concurrent.atomic.AtomicReference -val CWE_TOP25_2023 = setOf(20, 22, 77, 78, 79, 89, 94, 119, 125, 190, 269, 276, 287, 306, 352, 362, 416, 434, 476, 502, - 787, 798, 862, 863, 918) +val CWE_TOP25_2023: Set = setOf(20, 22, 77, 78, 79, 89, 94, 119, 125, 190, 269, 276, 287, 306, 352, 362, 416, 434, 476, 502, + 787, 798, 862, 863, 918) -data class InspectionsMetaInformation @JsonCreator constructor( +private data class InspectionsMetaInformation @JsonCreator constructor( @JsonProperty("inspections") val inspections: List ) @@ -29,16 +29,14 @@ data class MetaInformation @JsonCreator constructor( @JsonProperty("categories") val categories: List? ) -class MetaInformationState(val inspections: Map) - +class MetaInformationState(@JvmField val inspections: Map) @Service(Service.Level.APP) -class InspectionMetaInformationService(val serviceScope: CoroutineScope) { +class InspectionMetaInformationService(serviceScope: CoroutineScope) { private val loadJob = AtomicReference?>() init { val app = ApplicationManager.getApplication() - app.getMessageBus().connect(serviceScope).subscribe(DynamicPluginListener.TOPIC, object : DynamicPluginListener { override fun pluginLoaded(pluginDescriptor: IdeaPluginDescriptor) { invalidateState() @@ -62,9 +60,13 @@ class InspectionMetaInformationService(val serviceScope: CoroutineScope) { private fun loadState(): Deferred { val deferred = CompletableDeferred() while (true) { - if (loadJob.compareAndSet(null, deferred)) break - val job = loadJob.get() - if (job != null) return job + if (loadJob.compareAndSet(null, deferred)) { + break + } + + loadJob.get()?.let { + return it + } } var error: Throwable? = null diff --git a/platform/inspect/src/com/intellij/inspectopedia/extractor/InspectopediaExtractor.kt b/platform/inspect/src/com/intellij/inspectopedia/extractor/InspectopediaExtractor.kt index 4d99a728ca22..51bc939ac179 100644 --- a/platform/inspect/src/com/intellij/inspectopedia/extractor/InspectopediaExtractor.kt +++ b/platform/inspect/src/com/intellij/inspectopedia/extractor/InspectopediaExtractor.kt @@ -1,222 +1,163 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@file:Suppress("ReplacePutWithAssignment") +@file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet") package com.intellij.inspectopedia.extractor import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.MapperFeature -import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.json.JsonMapper import com.intellij.codeInspection.ex.InspectionMetaInformationService import com.intellij.codeInspection.options.* -import com.intellij.ide.plugins.IdeaPluginDescriptor -import com.intellij.ide.plugins.PluginManager -import com.intellij.inspectopedia.extractor.InspectopediaExtractor.Companion.getMyText +import com.intellij.ide.plugins.PluginManagerCore.getPluginSet import com.intellij.inspectopedia.extractor.data.Inspection import com.intellij.inspectopedia.extractor.data.OptionsPanelInfo import com.intellij.inspectopedia.extractor.data.Plugin -import com.intellij.inspectopedia.extractor.data.Plugins import com.intellij.inspectopedia.extractor.utils.HtmlUtils import com.intellij.openapi.application.ApplicationInfo -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.ApplicationStarter -import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.application.ModernApplicationStarter +import com.intellij.openapi.components.serviceAsync +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.ProjectManager import com.intellij.profile.codeInspection.InspectionProjectProfileManager -import com.intellij.util.containers.ContainerUtil import java.io.IOException import java.nio.file.Files import java.nio.file.Path import java.util.* -import java.util.function.Function -import java.util.stream.Collectors +import kotlin.system.exitProcess -private class InspectopediaExtractor : ApplicationStarter { - private val assets: MutableMap = HashMap() - - init { - val jsonMapper = JsonMapper.builder() - .enable(SerializationFeature.INDENT_OUTPUT) - .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) - .build() - assets.put("json", jsonMapper) - } - - override fun main(args: List) { +private class InspectopediaExtractor : ModernApplicationStarter() { + override suspend fun start(args: List) { val size = args.size if (size != 2) { LOG.error("Usage: inspectopedia-generator ") - System.exit(-1) + exitProcess(-1) } val appInfo = ApplicationInfo.getInstance() - val IDE_CODE = appInfo.build.productCode.lowercase(Locale.getDefault()) - val IDE_NAME = appInfo.versionName - val IDE_VERSION = appInfo.shortVersion - val ASSET_FILENAME = "$IDE_CODE-inspections." + val ideCode = appInfo.build.productCode.lowercase(Locale.getDefault()) + val ideName = appInfo.versionName + val ideVersion = appInfo.shortVersion val outputDirectory = args[1] val rootOutputPath = Path.of(outputDirectory).toAbsolutePath() - val outputPath = rootOutputPath.resolve(IDE_CODE) + val outputPath = rootOutputPath.resolve(ideCode) try { Files.createDirectories(outputPath) } catch (e: IOException) { LOG.error("Output directory does not exist and could not be created") - System.exit(-1) + exitProcess(-1) } if (!Files.exists(outputPath) || !Files.isDirectory(outputPath) || !Files.isWritable(outputPath)) { LOG.error("Output path is invalid") - System.exit(-1) + exitProcess(-1) } try { - val project = ProjectManager.getInstance().defaultProject + val project = serviceAsync().defaultProject - LOG.info("Using project " + project.name + ", default: " + project.isDefault) - val inspectionManager = InspectionProjectProfileManager.getInstance(project) + LOG.info("Using project ${project.name}, default: ${project.isDefault}") + val inspectionManager = project.serviceAsync() val scopeToolStates = inspectionManager.currentProfile.allTools - val availablePlugins = Arrays.stream( - PluginManager.getPlugins()).map { pluginDescriptor: IdeaPluginDescriptor -> - Plugin(pluginDescriptor.pluginId.idString, pluginDescriptor.name, - pluginDescriptor.version) - }.distinct() - .collect(Collectors.toMap( - Function { obj: Plugin -> obj.getId() }, Function { plugin: Plugin -> plugin })) + val availablePlugins = getPluginSet().allPlugins.asSequence() + .map { Plugin(it.pluginId.idString, it.name, it.version) } + .distinct() + .associateByTo(HashMap()) { it.id } - availablePlugins[IDE_NAME] = Plugin(IDE_NAME, IDE_NAME, IDE_VERSION) + availablePlugins.put(ideName, Plugin(ideName, ideName, ideVersion)) - val service = ApplicationManager.getApplication().getService( - InspectionMetaInformationService::class.java) - - val inspectionsExtraState = if (service == null) null else service.getState(null) + val inspectionsExtraState = serviceAsync().getState() for (scopeToolState in scopeToolStates) { val wrapper = scopeToolState.tool val extension = wrapper.extension - val pluginId = extension?.pluginDescriptor?.pluginId?.idString ?: IDE_NAME - val originalDescription = wrapper.loadDescription() - val description = originalDescription?.split("".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray() - ?: arrayOf("") + val pluginId = extension?.pluginDescriptor?.pluginId?.idString ?: ideName + val description = wrapper.loadDescription()?.splitToSequence("")?.map { it.trim() }?.filter { it.isEmpty() }?.toList() + ?: emptyList() var panelInfo: List? = null try { val tool = wrapper.tool val panel = tool.optionsPane - if (panel != OptPane.EMPTY) { - LOG.info("Saving options panel for " + wrapper.shortName) - panelInfo = retrievePanelStructure(panel, tool.optionController) + LOG.info("Saving options panel for ${wrapper.shortName}") + panelInfo = panel.components.map { retrievePanelStructure(it, tool.optionController) } } } - catch (t: Throwable) { - LOG.info("Cannot create options panel " + wrapper.shortName, t) + catch (e: Throwable) { + LOG.info("Cannot create options panel ${wrapper.shortName}", e) } - val metaInformation = inspectionsExtraState?.inspections?.get(wrapper.id) + val metaInformation = inspectionsExtraState.inspections.get(wrapper.id) val cweIds = metaInformation?.cweIds val language = wrapper.language - val briefDescription = HtmlUtils.cleanupHtml(description[0], language) - val extendedDescription = if (description.size > 1) HtmlUtils.cleanupHtml( - description[1], language) - else null + val briefDescription = description.firstOrNull()?.let { HtmlUtils.cleanupHtml(it, language) } ?: "" + val extendedDescription = if (description.size > 1) HtmlUtils.cleanupHtml(description[1], language) else null val inspection = Inspection(wrapper.shortName, wrapper.displayName, wrapper.defaultLevel.name, language, briefDescription, - extendedDescription, Arrays.asList(*wrapper.groupPath), wrapper.applyToDialects(), + extendedDescription, wrapper.groupPath.asList(), wrapper.applyToDialects(), wrapper.isCleanupTool, wrapper.isEnabledByDefault, panelInfo, cweIds) - availablePlugins[pluginId]!!.addInspection(inspection) + availablePlugins.get(pluginId)!!.addInspection(inspection) } - val sortedPlugins = availablePlugins.values.stream() - .sorted(Comparator.comparing { obj: Plugin -> obj.getId() }) - .peek { plugin: Plugin -> - plugin.inspections.sort(null) - }.toList() - val pluginsData = Plugins(sortedPlugins, IDE_CODE, IDE_NAME, IDE_VERSION) + val sortedPlugins = availablePlugins.values + .sortedBy { it.getId() } + .onEach { it.inspections.sort() } + val pluginData = Plugins(plugins = sortedPlugins, ideCode = ideCode, ideName = ideName, ideVersion = ideVersion) - for (ext in assets.keys) { - var data: String? = "" - try { - data = assets[ext]!!.writeValueAsString(pluginsData) - } - catch (e: JsonProcessingException) { - LOG.error("Cannot serialize " + ext.uppercase(Locale.getDefault()), e) - System.exit(-1) - } + val jsonMapper = JsonMapper.builder() + .enable(SerializationFeature.INDENT_OUTPUT) + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .build() - val outPath = outputPath.resolve(ASSET_FILENAME + ext) - - try { - Files.writeString(outPath, data) - } - catch (e: IOException) { - LOG.error("Cannot write $outPath", e) - System.exit(-1) - } - LOG.info("Inspections info saved in $outPath") + val data = try { + jsonMapper.writeValueAsString(pluginData) } + catch (e: JsonProcessingException) { + LOG.error("Cannot serialize", e) + exitProcess(-1) + } + + val outPath = outputPath.resolve("$ideCode-inspections.json") + try { + Files.writeString(outPath, data) + } + catch (e: IOException) { + LOG.error("Cannot write $outPath", e) + exitProcess(-1) + } + LOG.info("Inspections info saved in $outPath") } catch (e: Exception) { - LOG.error(e.message, e) - System.exit(-1) + e.printStackTrace() + exitProcess(-1) } - System.exit(0) + exitProcess(0) } } private val LOG = logger() private fun getMyText(cmp: OptComponent): LocMessage? { - return if (cmp is OptCheckbox) { - cmp.label + return when (cmp) { + is OptCheckbox -> cmp.label + is OptString -> cmp.splitLabel + is OptNumber -> cmp.splitLabel + is OptExpandableString -> cmp.label + is OptStringList -> cmp.label + is OptTable -> cmp.label + is OptTableColumn -> cmp.name + is OptTab -> cmp.label + is OptDropdown -> cmp.splitLabel + is OptGroup -> cmp.label + is OptSettingLink -> cmp.displayName + else -> null } - else if (cmp is OptString) { - cmp.splitLabel - } - else if (cmp is OptNumber) { - cmp.splitLabel - } - else if (cmp is OptExpandableString) { - cmp.label - } - else if (cmp is OptStringList) { - cmp.label - } - else if (cmp is OptTable) { - cmp.label - } - else if (cmp is OptTableColumn) { - cmp.name - } - else if (cmp is OptTab) { - cmp.label - } - else if (cmp is OptDropdown) { - cmp.splitLabel - } - else if (cmp is OptGroup) { - cmp.label - } - else if (cmp is OptSettingLink) { - cmp.displayName - } - else { - null - } -} - -private fun retrievePanelStructure(pane: OptPane, - controller: OptionController): List { - val children: MutableList = ArrayList() - for (component in pane.components) { - children.add(retrievePanelStructure(component, controller)) - } - return children } private fun retrievePanelStructure(component: OptComponent, controller: OptionController): OptionsPanelInfo { @@ -228,7 +169,7 @@ private fun retrievePanelStructure(component: OptComponent, controller: OptionCo val option = component.findOption(result.value) result.value = option?.label?.label() } - result.content = ContainerUtil.map(component.options) { opt: OptDropdown.Option -> opt.label.label() } + result.content = component.options.map { it.label.label() } } val text = getMyText(component) result.text = text?.label() @@ -236,12 +177,18 @@ private fun retrievePanelStructure(component: OptComponent, controller: OptionCo val description = component.description() result.description = description?.toString() } - val children: MutableList = ArrayList() - for (child in component.children()) { - children.add(retrievePanelStructure(child, controller)) - } + + val children = component.children().map { retrievePanelStructure(it, controller) } if (!children.isEmpty()) { result.children = children } return result -} \ No newline at end of file +} + +@Suppress("unused") +data class Plugins( + @JvmField val plugins: List, + @JvmField val ideCode: String, + @JvmField val ideName: String, + @JvmField val ideVersion: String, +) diff --git a/platform/inspect/src/com/intellij/inspectopedia/extractor/data/Plugin.java b/platform/inspect/src/com/intellij/inspectopedia/extractor/data/Plugin.java index d84fde4cf2c4..55a1cbe581eb 100644 --- a/platform/inspect/src/com/intellij/inspectopedia/extractor/data/Plugin.java +++ b/platform/inspect/src/com/intellij/inspectopedia/extractor/data/Plugin.java @@ -1,4 +1,4 @@ -// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.inspectopedia.extractor.data; import org.jetbrains.annotations.NotNull; @@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.function.Function; -public class Plugin { +public final class Plugin { public String id; public String name; @@ -37,8 +37,7 @@ public class Plugin { return version; } - @NotNull - public List getInspections() { + public @NotNull List getInspections() { return Optional.ofNullable(inspections) .map((Function, List>)ArrayList::new) .orElse(Collections.emptyList()); diff --git a/platform/inspect/src/com/intellij/inspectopedia/extractor/data/Plugins.java b/platform/inspect/src/com/intellij/inspectopedia/extractor/data/Plugins.java deleted file mode 100644 index c684adbdb698..000000000000 --- a/platform/inspect/src/com/intellij/inspectopedia/extractor/data/Plugins.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.inspectopedia.extractor.data; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -public class Plugins { - - public List plugins; - - public String ideCode; - - public String ideName; - - public String ideVersion; - - public Plugins(List plugins, String ideCode, String ideName, String ideVersion) { - this.plugins = plugins; - this.ideCode = ideCode; - this.ideName = ideName; - this.ideVersion = ideVersion; - } - - public Plugins() { - } - - @NotNull - public List getPlugins() { - return Optional.ofNullable(plugins) - .map((Function, List>) ArrayList::new) - .orElse(Collections.emptyList()); - } -} diff --git a/platform/inspect/src/com/intellij/inspectopedia/extractor/utils/HtmlUtils.java b/platform/inspect/src/com/intellij/inspectopedia/extractor/utils/HtmlUtils.java index 83b37e11a32b..0638f2f9fa6e 100644 --- a/platform/inspect/src/com/intellij/inspectopedia/extractor/utils/HtmlUtils.java +++ b/platform/inspect/src/com/intellij/inspectopedia/extractor/utils/HtmlUtils.java @@ -46,8 +46,7 @@ public final class HtmlUtils { "code[style=block] > *" ); - @NotNull - public static String cleanupHtml(@NotNull String source, @Nullable String languageForCodeBlocks) { + public static @NotNull String cleanupHtml(@NotNull String source, @Nullable String languageForCodeBlocks) { final Document document = Jsoup.parse(source); RENAME_MAP.forEach(map -> document.select(map.first).tagName(map.second));