mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-14 18:05:27 +07:00
IJPL-149476 convert InspectopediaExtractor to language with modern API (part 2)
GitOrigin-RevId: 213d0689a764c7095395868a165a0a9fee2fdbde
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3eaa41d59a
commit
b7a73dd811
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user