IJPL-149476 convert InspectopediaExtractor to language with modern API (part 2)

GitOrigin-RevId: 213d0689a764c7095395868a165a0a9fee2fdbde
This commit is contained in:
Vladimir Krivosheev
2024-05-13 07:22:07 +02:00
committed by intellij-monorepo-bot
parent 3eaa41d59a
commit b7a73dd811
6 changed files with 107 additions and 198 deletions

View File

@@ -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 {
/**

View File

@@ -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<Int> = 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<MetaInformation>
)
@@ -29,16 +29,14 @@ data class MetaInformation @JsonCreator constructor(
@JsonProperty("categories") val categories: List<String>?
)
class MetaInformationState(val inspections: Map<String, MetaInformation>)
class MetaInformationState(@JvmField val inspections: Map<String, MetaInformation>)
@Service(Service.Level.APP)
class InspectionMetaInformationService(val serviceScope: CoroutineScope) {
class InspectionMetaInformationService(serviceScope: CoroutineScope) {
private val loadJob = AtomicReference<Deferred<MetaInformationState>?>()
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<MetaInformationState> {
val deferred = CompletableDeferred<MetaInformationState>()
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

View File

@@ -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<String, ObjectMapper> = 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<String>) {
private class InspectopediaExtractor : ModernApplicationStarter() {
override suspend fun start(args: List<String>) {
val size = args.size
if (size != 2) {
LOG.error("Usage: inspectopedia-generator <output directory>")
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<ProjectManager>().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<InspectionProjectProfileManager>()
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<InspectionMetaInformationService>().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("<!-- tooltip end -->".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
?: arrayOf("")
val pluginId = extension?.pluginDescriptor?.pluginId?.idString ?: ideName
val description = wrapper.loadDescription()?.splitToSequence("<!-- tooltip end -->")?.map { it.trim() }?.filter { it.isEmpty() }?.toList()
?: emptyList()
var panelInfo: List<OptionsPanelInfo?>? = 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<InspectopediaExtractor>()
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<OptionsPanelInfo?> {
val children: MutableList<OptionsPanelInfo?> = 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<OptionsPanelInfo> = 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
}
}
@Suppress("unused")
data class Plugins(
@JvmField val plugins: List<Plugin>,
@JvmField val ideCode: String,
@JvmField val ideName: String,
@JvmField val ideVersion: String,
)

View File

@@ -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<Inspection> getInspections() {
public @NotNull List<Inspection> getInspections() {
return Optional.ofNullable(inspections)
.map((Function<List<Inspection>, List<Inspection>>)ArrayList::new)
.orElse(Collections.emptyList());

View File

@@ -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<Plugin> plugins;
public String ideCode;
public String ideName;
public String ideVersion;
public Plugins(List<Plugin> plugins, String ideCode, String ideName, String ideVersion) {
this.plugins = plugins;
this.ideCode = ideCode;
this.ideName = ideName;
this.ideVersion = ideVersion;
}
public Plugins() {
}
@NotNull
public List<Plugin> getPlugins() {
return Optional.ofNullable(plugins)
.map((Function<List<Plugin>, List<Plugin>>) ArrayList::new)
.orElse(Collections.emptyList());
}
}

View File

@@ -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));