From 6b7ecb38dcb9cf62c3eb4e7311fcbb6867662fe8 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 11 Feb 2026 11:42:22 +0100 Subject: [PATCH] AP-8446 [devkit]: reference provider for FUS group and event IDs + missing description inspection GitOrigin-RevId: 4f07dc713edd53dc2bc72ff291227a4853580e72 --- .../statistic/eventLog/EventLogGroup.kt | 2 + .../EventLogDescription.html | 6 + .../resources/intellij.devkit.core.xml | 5 + .../messages/DevKitBundle.properties | 11 + .../devkit/devkit-core/src/DevKitBundle.java | 10 +- .../EventLogDescriptionInspection.kt | 105 +++++++++ ...EventLogDescriptionReferenceContributor.kt | 59 +++++ .../src/references/EventLogDescriptionUtil.kt | 65 ++++++ ...evkitInspectionsRegistrationCheckTest.java | 3 +- ...tLogDescriptionReferenceContributorTest.kt | 215 ++++++++++++++++++ 10 files changed, 474 insertions(+), 7 deletions(-) create mode 100644 plugins/devkit/devkit-core/resources/inspectionDescriptions/EventLogDescription.html create mode 100644 plugins/devkit/devkit-core/src/inspections/EventLogDescriptionInspection.kt create mode 100644 plugins/devkit/devkit-core/src/references/EventLogDescriptionReferenceContributor.kt create mode 100644 plugins/devkit/devkit-core/src/references/EventLogDescriptionUtil.kt create mode 100644 plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/references/EventLogDescriptionReferenceContributorTest.kt diff --git a/platform/statistics/src/com/intellij/internal/statistic/eventLog/EventLogGroup.kt b/platform/statistics/src/com/intellij/internal/statistic/eventLog/EventLogGroup.kt index 27c1a08c4973..dc388626c048 100644 --- a/platform/statistics/src/com/intellij/internal/statistic/eventLog/EventLogGroup.kt +++ b/platform/statistics/src/com/intellij/internal/statistic/eventLog/EventLogGroup.kt @@ -35,6 +35,7 @@ open class EventLogGroup { val groupData: List, FeatureUsageData.() -> Unit>> @Deprecated("Descriptions are moved to a separate file; use another constructor", ReplaceWith("EventLogGroup(id, version, recorder, groupData)")) + @Suppress("EventLogDescription") constructor( @EventIdName id: String, version: Int, @@ -68,6 +69,7 @@ open class EventLogGroup { } @Deprecated("Descriptions are moved to a separate file; use another constructor", ReplaceWith("EventLogGroup(id, version, recorder)")) + @Suppress("EventLogDescription") constructor( @EventIdName id: String, version: Int, diff --git a/plugins/devkit/devkit-core/resources/inspectionDescriptions/EventLogDescription.html b/plugins/devkit/devkit-core/resources/inspectionDescriptions/EventLogDescription.html new file mode 100644 index 000000000000..7866ebafd703 --- /dev/null +++ b/plugins/devkit/devkit-core/resources/inspectionDescriptions/EventLogDescription.html @@ -0,0 +1,6 @@ + + +Reports missing FUS group or event descriptions in the corresponding recorder properties files (build/event/*.properties), +and warns when a group or event ID cannot be evaluated. + + diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml index e5ad3e928b27..647bb4f5219f 100644 --- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml +++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml @@ -28,6 +28,7 @@ + @@ -573,6 +574,10 @@ groupKey="inspections.group.code" key="inspection.use.optimized.eel.functions.display.name"/>" + + diff --git a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties index 358f8fa76aad..e6f66764a1d9 100644 --- a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties +++ b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties @@ -382,6 +382,17 @@ inspections.use.gray.awt.color.used.name='java.awt.Color' used for gray inspections.use.gray.fix.convert.name=Convert to ''Gray._{0}'' inspections.use.gray.fix.convert.family.name=Convert to 'Gray' +inspections.event.log.description.display.name=Missing event log descriptions +inspections.event.log.recorder.not.evaluable=Cannot evaluate the recorder name; consider using a constant expression +inspections.event.log.recorder.unknown=Unknown recorder (''{0}'' is missing) +inspections.event.log.group.id.not.evaluable=Cannot evaluate the group ID; consider using a constant expression +inspections.event.log.group.description.missing=Group ''{0}'' description is missing from ''{1}'' +inspections.event.log.group.description.empty=Group ''{0}'' description is empty +inspections.event.log.event.id.not.evaluable=Cannot evaluate the event ID; consider using a constant expression +inspections.event.log.event.group.not.evaluable=Cannot find the event group; consider using a constant expression +inspections.event.log.event.description.missing=Event ''{0}'' description is missing from ''{1}'' +inspections.event.log.event.description.empty=Event ''{0}'' description is empty + line.marker.related.property.tooltip=Related property line.marker.related.property.popup.title=Related Properties line.marker.related.property.description=Related property diff --git a/plugins/devkit/devkit-core/src/DevKitBundle.java b/plugins/devkit/devkit-core/src/DevKitBundle.java index 4ea9ea16b575..9cb1a6c46dcd 100644 --- a/plugins/devkit/devkit-core/src/DevKitBundle.java +++ b/plugins/devkit/devkit-core/src/DevKitBundle.java @@ -3,15 +3,13 @@ package org.jetbrains.idea.devkit; import com.intellij.DynamicBundle; import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.PropertyKey; import java.util.function.Supplier; public final class DevKitBundle { - - public static final @NonNls String BUNDLE = "messages.DevKitBundle"; + public static final String BUNDLE = "messages.DevKitBundle"; private static final DynamicBundle ourInstance = new DynamicBundle(DevKitBundle.class, BUNDLE); @@ -19,8 +17,10 @@ public final class DevKitBundle { return ourInstance.getMessage(key, params); } - public static @NotNull Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, - Object @NotNull ... params) { + public static @NotNull Supplier<@Nls String> messagePointer( + @NotNull @PropertyKey(resourceBundle = BUNDLE) String key, + Object @NotNull ... params + ) { return ourInstance.getLazyMessage(key, params); } } diff --git a/plugins/devkit/devkit-core/src/inspections/EventLogDescriptionInspection.kt b/plugins/devkit/devkit-core/src/inspections/EventLogDescriptionInspection.kt new file mode 100644 index 000000000000..1621428106f9 --- /dev/null +++ b/plugins/devkit/devkit-core/src/inspections/EventLogDescriptionInspection.kt @@ -0,0 +1,105 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.inspections + +import com.intellij.codeInspection.AbstractBaseUastLocalInspectionTool +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.lang.properties.PropertiesFileType +import com.intellij.openapi.project.guessProjectDir +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiManager +import com.intellij.uast.UastHintedVisitorAdapter +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.PropertyKey +import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.idea.devkit.DevKitBundle +import org.jetbrains.idea.devkit.references.EVENT_LOG_GROUP_FQN +import org.jetbrains.idea.devkit.references.EVENT_LOG_PROPERTIES_DIR +import org.jetbrains.idea.devkit.references.eventLogGroupCall +import org.jetbrains.idea.devkit.references.findEventLogPropertiesFile +import org.jetbrains.idea.devkit.references.findGroupIdAndRecorderName +import org.jetbrains.idea.devkit.references.findRecorderName +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UastCallKind +import org.jetbrains.uast.evaluateString +import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor + +@VisibleForTesting +@ApiStatus.Internal +class EventLogDescriptionInspection : AbstractBaseUastLocalInspectionTool() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + val hasFUS = JavaPsiFacade.getInstance(holder.project).findClass(EVENT_LOG_GROUP_FQN, holder.file.resolveScope) != null + if (!hasFUS) + return PsiElementVisitor.EMPTY_VISITOR + + val hasDescriptions = holder.project.guessProjectDir() + ?.findFileByRelativePath(EVENT_LOG_PROPERTIES_DIR) + ?.let { PsiManager.getInstance(holder.project).findDirectory(it) } + ?.let { it.files.any { file -> file.name.endsWith(PropertiesFileType.DOT_DEFAULT_EXTENSION) } } == true + if (!hasDescriptions) + return PsiElementVisitor.EMPTY_VISITOR + + return UastHintedVisitorAdapter.create( + holder.file.language, + EventLogDescriptionInspectionVisitor(holder), + arrayOf(UCallExpression::class.java), + directOnly = true + ) + } +} + +private val EVENT_LOG_GROUP_CALL_PATTERN = eventLogGroupCall() + +private class EventLogDescriptionInspectionVisitor(private val holder: ProblemsHolder) : AbstractUastNonRecursiveVisitor() { + override fun visitCallExpression(node: UCallExpression): Boolean { + val argument = node.getArgumentForParameter(0) + if (argument != null && EVENT_LOG_GROUP_CALL_PATTERN.accepts(node)) { + if (node.kind == UastCallKind.CONSTRUCTOR_CALL) { + val recorderArg = node.getArgumentForParameter(2) + val recorder = findRecorderName(node) + ?: return warn(recorderArg ?: node, "inspections.event.log.recorder.not.evaluable") + val file = findEventLogPropertiesFile(holder.project, recorder) + ?: return error(recorderArg ?: node, "inspections.event.log.recorder.unknown", "${EVENT_LOG_PROPERTIES_DIR}/${recorder}.properties") + val groupId = argument.evaluateString()?.takeIf { it.isNotBlank() } + ?: return warn(argument, "inspections.event.log.group.id.not.evaluable") + val description = file.findPropertyByKey(groupId)?.value + ?: return error(argument, "inspections.event.log.group.description.missing", groupId, file.name) + if (description.isBlank()) + error(argument, "inspections.event.log.group.description.empty", groupId) + } + else { + val receiver = node.receiver + val (groupId, recorder) = receiver?.let { findGroupIdAndRecorderName(it) } + ?: return warn(receiver ?: node, "inspections.event.log.event.group.not.evaluable") + val eventId = argument.evaluateString()?.takeIf { it.isNotBlank() } + ?: return warn(argument, "inspections.event.log.event.id.not.evaluable") + findEventLogPropertiesFile(holder.project, recorder)?.let { file -> + val key = "${groupId}.${eventId}" + val description = file.findPropertyByKey(key)?.value + ?: return error(argument, "inspections.event.log.event.description.missing", key, file.name) + if (description.isBlank()) + error(argument, "inspections.event.log.event.description.empty", key) + } + } + } + + return true + } + + private fun warn(element: UElement, messageKey: @PropertyKey(resourceBundle = DevKitBundle.BUNDLE) String): Boolean { + element.sourcePsi?.let { + holder.registerProblem(it, DevKitBundle.message(messageKey), ProblemHighlightType.WARNING) + } + return true + } + + private fun error(element: UElement, messageKey: @PropertyKey(resourceBundle = DevKitBundle.BUNDLE) String, vararg params: Any): Boolean { + element.sourcePsi?.let { + holder.registerProblem(it, DevKitBundle.message(messageKey, *params), ProblemHighlightType.GENERIC_ERROR) + } + return true + } +} diff --git a/plugins/devkit/devkit-core/src/references/EventLogDescriptionReferenceContributor.kt b/plugins/devkit/devkit-core/src/references/EventLogDescriptionReferenceContributor.kt new file mode 100644 index 000000000000..a91382dd26f0 --- /dev/null +++ b/plugins/devkit/devkit-core/src/references/EventLogDescriptionReferenceContributor.kt @@ -0,0 +1,59 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.references + +import com.intellij.lang.properties.psi.PropertiesFile +import com.intellij.lang.properties.references.PropertyReferenceBase +import com.intellij.patterns.uast.injectionHostUExpression +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiLanguageInjectionHost +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceContributor +import com.intellij.psi.PsiReferenceRegistrar +import com.intellij.psi.UastInjectionHostReferenceProvider +import com.intellij.psi.registerUastReferenceProvider +import com.intellij.util.ProcessingContext +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UExpression +import org.jetbrains.uast.UastCallKind +import org.jetbrains.uast.evaluateString +import org.jetbrains.uast.getParentOfType + +internal class EventLogDescriptionReferenceContributor : PsiReferenceContributor() { + override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { + registrar.registerUastReferenceProvider( + injectionHostUExpression().callParameter(0, eventLogGroupCall()), + EventLogDescriptionReferenceProvider(), + PsiReferenceRegistrar.DEFAULT_PRIORITY, + ) + } +} + +private class EventLogDescriptionReferenceProvider : UastInjectionHostReferenceProvider() { + override fun getReferencesForInjectionHost( + uExpression: UExpression, + host: PsiLanguageInjectionHost, + context: ProcessingContext, + ): Array { + val call = uExpression.getParentOfType(UCallExpression::class.java, true) ?: return PsiReference.EMPTY_ARRAY + if (call.kind == UastCallKind.CONSTRUCTOR_CALL) { + val groupId = uExpression.evaluateString() ?: return PsiReference.EMPTY_ARRAY + val recorder = findRecorderName(call) ?: return PsiReference.EMPTY_ARRAY + return arrayOf(EventLogDescriptionReference(groupId, host, recorder)) + } + else { + val eventId = uExpression.evaluateString() ?: return PsiReference.EMPTY_ARRAY + val receiver = call.receiver ?: return PsiReference.EMPTY_ARRAY + val (groupId, recorder) = findGroupIdAndRecorderName(receiver) ?: return PsiReference.EMPTY_ARRAY + return arrayOf(EventLogDescriptionReference("${groupId}.${eventId}", host, recorder)) + } + } +} + +private class EventLogDescriptionReference(key: String, element: PsiElement, private val recorder: String) + : PropertyReferenceBase(key, /*soft =*/ true, element) +{ + override fun getPropertiesFiles(): List { + val file = findEventLogPropertiesFile(this.element.project, recorder) + return if (file != null) listOf(file) else emptyList() + } +} diff --git a/plugins/devkit/devkit-core/src/references/EventLogDescriptionUtil.kt b/plugins/devkit/devkit-core/src/references/EventLogDescriptionUtil.kt new file mode 100644 index 000000000000..50bcb3d5cd08 --- /dev/null +++ b/plugins/devkit/devkit-core/src/references/EventLogDescriptionUtil.kt @@ -0,0 +1,65 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.references + +import com.intellij.internal.statistic.eventLog.FUS_RECORDER +import com.intellij.lang.properties.psi.PropertiesFile +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessProjectDir +import com.intellij.patterns.ElementPattern +import com.intellij.patterns.PsiJavaPatterns.psiMethod +import com.intellij.patterns.uast.callExpression +import com.intellij.psi.PsiManager +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UExpression +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.UQualifiedReferenceExpression +import org.jetbrains.uast.UResolvable +import org.jetbrains.uast.UVariable +import org.jetbrains.uast.UastCallKind +import org.jetbrains.uast.evaluateString +import org.jetbrains.uast.resolveToUElement +import org.jetbrains.uast.toUElementOfType + +internal const val EVENT_LOG_GROUP_FQN = "com.intellij.internal.statistic.eventLog.EventLogGroup" +internal const val EVENT_LOG_PROPERTIES_DIR = "build/events" + +private const val REGISTER_EVENT_NAME = "registerEvent" +private const val REGISTER_VARARG_EVENT_NAME = "registerVarargEvent" + +internal fun eventLogGroupCall(): ElementPattern = callExpression().andOr( + callExpression().constructor(EVENT_LOG_GROUP_FQN), + callExpression().withAnyResolvedMethod( + psiMethod().withName(REGISTER_EVENT_NAME, REGISTER_VARARG_EVENT_NAME).definedInClass(EVENT_LOG_GROUP_FQN) + ) +) + +internal fun findRecorderName(call: UCallExpression): String? { + val recorderArg = call.getArgumentForParameter(2) ?: return FUS_RECORDER + return recorderArg.evaluateString()?.takeIf { it.isNotBlank() } +} + +internal fun findGroupIdAndRecorderName(expression: UExpression): Pair? = when (expression) { + is UCallExpression if (expression.kind == UastCallKind.CONSTRUCTOR_CALL) -> { + val groupId = expression.getArgumentForParameter(0)?.let(UExpression::evaluateString) ?: return null + val recorder = findRecorderName(expression) ?: return null + groupId to recorder + } + is UQualifiedReferenceExpression -> { + findGroupIdAndRecorderName(expression.selector) + } + else -> { + val variable = when (val resolved = (expression as? UResolvable)?.resolveToUElement()) { + is UVariable -> resolved + is UMethod -> resolved.sourcePsi.toUElementOfType() // Kotlin synthetic accessors + else -> null + } ?: return null + val initializer = variable.uastInitializer ?: return null + findGroupIdAndRecorderName(initializer) + } +} + +internal fun findEventLogPropertiesFile(project: Project, recorder: String): PropertiesFile? { + val projectDir = project.guessProjectDir() ?: return null + val file = projectDir.findFileByRelativePath("${EVENT_LOG_PROPERTIES_DIR}/${recorder}.properties") ?: return null + return PsiManager.getInstance(project).findFile(file) as? PropertiesFile +} diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java index 4634afa78173..bb0a110a583a 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java @@ -12,8 +12,7 @@ import java.util.List; import java.util.stream.Stream; public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase { - - private static final int EXPECTED_INSPECTIONS_NUMBER = 93; + private static final int EXPECTED_INSPECTIONS_NUMBER = 94; /** * Inspections that are finished and intentionally disabled. diff --git a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/references/EventLogDescriptionReferenceContributorTest.kt b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/references/EventLogDescriptionReferenceContributorTest.kt new file mode 100644 index 000000000000..f6366f7968ec --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/references/EventLogDescriptionReferenceContributorTest.kt @@ -0,0 +1,215 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.references + +import com.intellij.lang.properties.psi.Property +import com.intellij.openapi.application.runReadAction +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase5 +import org.jetbrains.idea.devkit.inspections.EventLogDescriptionInspection +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertInstanceOf +import org.junit.jupiter.api.assertNotNull + +class EventLogDescriptionReferenceContributorTest : LightJavaCodeInsightFixtureTestCase5() { + @BeforeEach + fun setUp() { + fixture.addClass(""" + package com.intellij.internal.statistic.eventLog; + public class EventLogGroup { + public EventLogGroup(String id, int version) {} + public EventLogGroup(String id, int version, String recorder) {} + public EventId registerEvent(String id) {} + public EventId registerVarargEvent(String id, Object... fields) {} + public static class EventId {} + }""".trimIndent() + ) + fixture.addFileToProject("build/events/FUS.properties", """ + my.group=FUS group + my.group.event=FUS event + my.group.var.event=FUS vararg event + empty.group= + my.group.empty.event= + """.trimIndent()) + fixture.addFileToProject("build/events/ML.properties", "ml.group=ML group") + } + + @Test + fun `group in default recorder - Java`() { + fixture.configureByText("Foo.java", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup; + class Foo { + private static final EventLogGroup GROUP = new EventLogGroup("my.group", 1); + } + """.trimIndent() + ) + testResolve("FUS.properties", "FUS group") + } + + @Test + fun `event in default recorder - Java`() { + fixture.configureByText("Foo.java", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup; + class Foo { + private static final EventLogGroup GROUP = new EventLogGroup("my.group", 1); + private static final EventLogGroup.EventId EVENT = GROUP.registerEvent("event"); + } + """.trimIndent() + ) + testResolve("FUS.properties", "FUS event") + } + + @Test + fun `vararg event in default recorder - Java`() { + fixture.configureByText("Foo.java", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup; + class Foo { + private static final EventLogGroup GROUP = new EventLogGroup("my.group", 1); + private static final EventLogGroup.EventId EVENT = GROUP.registerVarargEvent("var.event", "whatever"); + } + """.trimIndent() + ) + testResolve("FUS.properties", "FUS vararg event") + } + + @Test + fun `group in default recorder - Kotlin`() { + fixture.configureByText("Foo.kt", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup + object Foo { + private val GROUP = EventLogGroup("my.group", 1) + } + """.trimIndent() + ) + testResolve("FUS.properties", "FUS group") + } + + @Test + fun `event in default recorder - Kotlin`() { + fixture.configureByText("Foo.kt", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup + object Foo { + val GROUP = EventLogGroup("my.group", 1) // public, to verify the Kotlin property accessor way + val EVENT = GROUP.registerEvent("event") + } + """.trimIndent() + ) + testResolve("FUS.properties", "FUS event") + } + + @Test + fun `vararg event in default recorder - Kotlin`() { + fixture.configureByText("Foo.kt", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup + object Foo { + private val GROUP = EventLogGroup("my.group", 1) + private val EVENT = GROUP.registerVarargEvent("var.event", "whatever") + } + """.trimIndent() + ) + testResolve("FUS.properties", "FUS vararg event") + } + + @Test + fun `group in custom recorder`() { + fixture.configureByText("Foo.java", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup; + class Foo { + private static final String RECORDER = "ML"; + private static final EventLogGroup GROUP = new EventLogGroup("ml.group", 1, RECORDER); + }""".trimIndent() + ) + testResolve("ML.properties", "ML group") + } + + @Test + fun `skipping navigation when recorder is non-evaluatable`() { + fixture.configureByText("Foo.java", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup; + class Foo { + private static String recorder() { return "ML"; } + private static final EventLogGroup GROUP = new EventLogGroup("ml.group", 1, recorder()); + }""".trimIndent() + ) + + testNoResolve() + } + + @Test + fun `problem highlighting - Java`() { + fixture.enableInspections(EventLogDescriptionInspection::class.java) + fixture.configureByText("Foo.java", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup; + class Foo { + // the good + private static final EventLogGroup GROUP = new EventLogGroup("my.group", 1); + private static final EventLogGroup.EventId EVENT = GROUP.registerEvent("event"); + // the bad + private static final EventLogGroup G1 = + new EventLogGroup("-", 1, System.getProperty("recorder")); + private static final EventLogGroup G2 = + new EventLogGroup("-", 1, "WTF"); + private static final EventLogGroup G3 = + new EventLogGroup(System.getProperty("group"), 1, "FUS"); + private static final EventLogGroup G4 = + new EventLogGroup("missing.group", 1); + private static final EventLogGroup G5 = + new EventLogGroup("empty.group", 1); + // the ugly + private static EventLogGroup group() { return new EventLogGroup("my.group", 1); } + private static final EventLogGroup.EventId E1 = + group().registerEvent("-"); + private static final EventLogGroup.EventId E2 = + GROUP.registerEvent(System.getProperty("event")); + private static final EventLogGroup.EventId E3 = + GROUP.registerEvent("missing.event"); + private static final EventLogGroup.EventId E4 = + GROUP.registerEvent("empty.event"); + }""".trimIndent() + ) + fixture.checkHighlighting() + } + + @Test + fun `problem highlighting - Kotlin`() { + fixture.enableInspections(EventLogDescriptionInspection::class.java) + fixture.configureByText("Foo.kt", """ + import com.intellij.internal.statistic.eventLog.EventLogGroup + object Foo { + // the good + private val GROUP = EventLogGroup("my.group", 1) + private val EVENT = GROUP.registerEvent("event") + // the bad + private val G1 = EventLogGroup("-", 1, System.getProperty("recorder")) + private val G2 = EventLogGroup("-", 1, "WTF") + private val G3 = EventLogGroup(System.getProperty("group"), 1, "FUS") + private val G4 = EventLogGroup("missing.group", 1) + private val G5 = EventLogGroup("empty.group", 1) + // the ugly + private fun group() = EventLogGroup("my.group", 1) + private val E1 = group().registerEvent("-") + private val E2 = GROUP.registerEvent(System.getProperty("event")) + private val E3 = GROUP.registerEvent("missing.event") + private val E4 = GROUP.registerEvent("empty.event") + }""".trimIndent() + ) + fixture.checkHighlighting() + } + + private fun testResolve(expectedFile: String, expectedText: String) = runReadAction { + val reference = fixture.getReferenceAtCaretPosition() + assertNotNull(reference) + assertEquals("EventLogDescriptionReference", reference.javaClass.simpleName) + val property = assertInstanceOf(reference.resolve()) + assertEquals(expectedFile, property.containingFile.name) + assertEquals(expectedText, property.value) + } + + private fun testNoResolve() { + val reference = fixture.getReferenceAtCaretPosition() + if (reference != null) { + assertNotEquals("EventLogDescriptionReference", reference.javaClass.simpleName) + } + } +}