diff --git a/CHANGELOG.md b/CHANGELOG.md index dc93eb6..45405a2 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ## [Unreleased] +## [1.1.5] - 2024-02-01 + +- Fix external annotator for registered languages +- Fix performing of intention action on hover + ## [1.1.4] - 2024-01-31 - Fix scan results sharing across projects @@ -34,6 +39,8 @@ The first public release of the plugin. +[1.1.5]: https://github.com/cycodehq/intellij-platform-plugin/releases/tag/v1.1.5 + [1.1.4]: https://github.com/cycodehq/intellij-platform-plugin/releases/tag/v1.1.4 [1.1.3]: https://github.com/cycodehq/intellij-platform-plugin/releases/tag/v1.1.3 @@ -48,4 +55,4 @@ The first public release of the plugin. [1.0.0]: https://github.com/cycodehq/intellij-platform-plugin/releases/tag/v1.0.0 -[Unreleased]: https://github.com/cycodehq/intellij-platform-plugin/compare/v1.1.4...HEAD +[Unreleased]: https://github.com/cycodehq/intellij-platform-plugin/compare/v1.1.5...HEAD diff --git a/build.gradle.kts b/build.gradle.kts index 4817cd6..efe39ac 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -110,6 +110,10 @@ tasks { systemProperty("jb.consents.confirmation.enabled", "false") } + runIde { + systemProperty("idea.log.debug.categories", "com.cycode.plugin") + } + signPlugin { certificateChain = environment("CERTIFICATE_CHAIN") privateKey = environment("PRIVATE_KEY") @@ -124,4 +128,9 @@ tasks { // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel channels = properties("pluginVersion").map { listOf(it.split('-').getOrElse(1) { "default" }.split('.').first()) } } + + buildSearchableOptions { + // required for Auto-Reload development mode + enabled = false + } } diff --git a/gradle.properties b/gradle.properties index dc6dbf2..58ef249 100755 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = com.cycode.plugin pluginName = Cycode pluginRepositoryUrl = https://github.com/cycodehq/intellij-platform-plugin # SemVer format -> https://semver.org -pluginVersion = 1.1.4 +pluginVersion = 1.1.5 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 211.1 diff --git a/src/main/kotlin/com/cycode/plugin/activities/PostStartupActivity.kt b/src/main/kotlin/com/cycode/plugin/activities/PostStartupActivity.kt index 7823338..4e5a174 100644 --- a/src/main/kotlin/com/cycode/plugin/activities/PostStartupActivity.kt +++ b/src/main/kotlin/com/cycode/plugin/activities/PostStartupActivity.kt @@ -1,5 +1,6 @@ package com.cycode.plugin.activities +import com.cycode.plugin.annotators.CycodeAnnotator import com.cycode.plugin.services.cycode import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.project.Project @@ -7,6 +8,10 @@ import com.intellij.openapi.startup.StartupActivity class PostStartupActivity : StartupActivity.DumbAware { override fun runActivity(project: Project) { + // we are using singleton here because runActivity is called for each project + CycodeAnnotator.INSTANCE.registerForAllLangs() + + // synchronized method inside cycode(project).installCliIfNeededAndCheckAuthentication() thisLogger().info("PostStartupActivity finished.") diff --git a/src/main/kotlin/com/cycode/plugin/annotators/CycodeAnnotator.kt b/src/main/kotlin/com/cycode/plugin/annotators/CycodeAnnotator.kt index bf7f6a1..48a4a2e 100644 --- a/src/main/kotlin/com/cycode/plugin/annotators/CycodeAnnotator.kt +++ b/src/main/kotlin/com/cycode/plugin/annotators/CycodeAnnotator.kt @@ -9,23 +9,118 @@ import com.cycode.plugin.intentions.CycodeIgnoreIntentionQuickFix import com.cycode.plugin.intentions.CycodeIgnoreType import com.cycode.plugin.services.ScanResultsService import com.cycode.plugin.services.scanResults +import com.intellij.lang.ExternalLanguageAnnotators +import com.intellij.lang.Language import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.ExternalAnnotator import com.intellij.lang.annotation.HighlightSeverity -import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.DumbAware import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiFile +private val LOG = logger() + class CycodeAnnotator : DumbAware, ExternalAnnotator() { + private val registeredForLanguages = mutableSetOf() + private val psiFileCallsCounter = mutableMapOf() + private fun getScanResults(psiFile: PsiFile): ScanResultsService { return scanResults(psiFile.project) } + companion object { + val INSTANCE: CycodeAnnotator by lazy { + CycodeAnnotator() + } + } + override fun collectInformation(file: PsiFile): PsiFile = file override fun doAnnotate(psiFile: PsiFile?) {} + fun registerForAllLangs() { + /** + * we want to annotate all languages because secrets can be detected in any file + * and SCA can be detected in any supported package and lock file. + * maintaining a list of supported languages is not flexible enough + * because of non JetBrains plugins + */ + Language.getRegisteredLanguages().forEach { + if (registeredForLanguages.contains(it)) { + return@forEach + } + + ExternalLanguageAnnotators.INSTANCE.addExplicitExtension(it, this) + registeredForLanguages.add(it) + } + } + + private fun countExpectedApplyCalls(psiFile: PsiFile): Int { + var expectedCallsCount = 0 + + val viewProvider = psiFile.viewProvider + for (language in viewProvider.languages) { + val psiRoot = viewProvider.getPsi(language) + val annotators = ExternalLanguageAnnotators.allForFile(language, psiRoot) + expectedCallsCount += annotators.filterIsInstance().count() + } + + return expectedCallsCount + } + + private fun getCallCount(psiFile: PsiFile): Int { + synchronized(psiFileCallsCounter) { + if (psiFileCallsCounter.containsKey(psiFile)) { + return psiFileCallsCounter[psiFile]!! + } + } + + return 0 + } + + private fun incrementCallCount(psiFile: PsiFile) { + synchronized(psiFileCallsCounter) { + val callCount = getCallCount(psiFile) + psiFileCallsCounter[psiFile] = callCount + 1 + } + } + + private fun resetCallCount(psiFile: PsiFile) { + synchronized(psiFileCallsCounter) { + // we are deleting instead of setting to 0 to save memory + psiFileCallsCounter.remove(psiFile) + } + } + + private fun shouldIgnoreApplyCall(psiFile: PsiFile): Boolean { + /** + * Our annotator is registered for all available languages. + * One file could contain multiple languages. + * Also, it's possible that multiple languages are registered for the same file extension or mime type. + * As a result, our annotator will be called multiple times for the same file, but for different languages. + * Our goal is to ignore all calls except one (any of them) to prevent duplications. + * In the current implementation, we are applying annotations only on the last call. + * This method counts calls and returns true if we should ignore the current one. + */ + + incrementCallCount(psiFile) + + if (getCallCount(psiFile) < countExpectedApplyCalls(psiFile)) { + // we are waiting for all annotators to trigger + LOG.debug("Ignore this apply. Calls ${getCallCount(psiFile)}/${countExpectedApplyCalls(psiFile)}") + return true + } + + LOG.debug("Apply called. This is the last call.") + resetCallCount(psiFile) + return false + } + override fun apply(psiFile: PsiFile, annotationResult: Unit, holder: AnnotationHolder) { + if (shouldIgnoreApplyCall(psiFile)) { + return + } + applyAnnotationsForSecrets(psiFile, holder) applyAnnotationsForSca(psiFile, holder) } @@ -48,7 +143,7 @@ class CycodeAnnotator : DumbAware, ExternalAnnotator() { scanResults.saveDetectedSegment(CliScanType.Secret, textRange, detectedSubstr) } else if (detectedSegment != detectedSubstr) { // case: the code has been added or deleted before the detection - thisLogger().warn( + LOG.debug( "[Secret] Text range of detection has been shifted. " + "Annotation is not relevant to this state of the file content anymore" ) @@ -64,7 +159,7 @@ class CycodeAnnotator : DumbAware, ExternalAnnotator() { // instead, we check if the package name is still in the text range val detectedSubstr = psiFile.text.substring(textRange.startOffset, textRange.endOffset) if (!detectedSubstr.contains(expectedPackageName)) { - thisLogger().warn( + LOG.debug( "[SCA] Text range of detection has been shifted. " + "Annotation is not relevant to this state of the file content anymore" ) @@ -79,7 +174,7 @@ class CycodeAnnotator : DumbAware, ExternalAnnotator() { // check if text range fits in file // case: row with detections has been deleted, but detection is still in the local results DB - thisLogger().warn("Text range of detection is out of file bounds") + LOG.debug("Text range of detection is out of file bounds") return false } diff --git a/src/main/resources/META-INF/only-csharp.xml b/src/main/resources/META-INF/only-csharp.xml deleted file mode 100644 index f4c7c6f..0000000 --- a/src/main/resources/META-INF/only-csharp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/META-INF/only-go.xml b/src/main/resources/META-INF/only-go.xml deleted file mode 100644 index 27d863f..0000000 --- a/src/main/resources/META-INF/only-go.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/main/resources/META-INF/only-java.xml b/src/main/resources/META-INF/only-java.xml deleted file mode 100644 index 30eb87e..0000000 --- a/src/main/resources/META-INF/only-java.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/main/resources/META-INF/only-javascript.xml b/src/main/resources/META-INF/only-javascript.xml deleted file mode 100644 index 7bfd7f8..0000000 --- a/src/main/resources/META-INF/only-javascript.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/META-INF/only-json.xml b/src/main/resources/META-INF/only-json.xml deleted file mode 100644 index 7e31377..0000000 --- a/src/main/resources/META-INF/only-json.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/META-INF/only-kotlin.xml b/src/main/resources/META-INF/only-kotlin.xml deleted file mode 100644 index b18bb7b..0000000 --- a/src/main/resources/META-INF/only-kotlin.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/META-INF/only-php.xml b/src/main/resources/META-INF/only-php.xml deleted file mode 100644 index fc72916..0000000 --- a/src/main/resources/META-INF/only-php.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/META-INF/only-python.xml b/src/main/resources/META-INF/only-python.xml deleted file mode 100644 index b9b54ac..0000000 --- a/src/main/resources/META-INF/only-python.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/META-INF/only-xml.xml b/src/main/resources/META-INF/only-xml.xml deleted file mode 100644 index 177f582..0000000 --- a/src/main/resources/META-INF/only-xml.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/main/resources/META-INF/only-yaml.xml b/src/main/resources/META-INF/only-yaml.xml deleted file mode 100644 index 9ad85e1..0000000 --- a/src/main/resources/META-INF/only-yaml.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 933d977..e213d29 100755 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -7,17 +7,6 @@ com.intellij.modules.platform com.intellij.modules.lang - com.intellij.java - org.jetbrains.plugins.yaml - com.intellij.modules.json - com.intellij.modules.xml - org.jetbrains.kotlin - com.intellij.modules.python - com.jetbrains.php - org.jetbrains.plugins.go - com.intellij.modules.rider - JavaScript - messages.CycodeBundle