Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
9 changes: 9 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
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
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.")
Expand Down
103 changes: 99 additions & 4 deletions src/main/kotlin/com/cycode/plugin/annotators/CycodeAnnotator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<CycodeAnnotator>()

class CycodeAnnotator : DumbAware, ExternalAnnotator<PsiFile, Unit>() {
private val registeredForLanguages = mutableSetOf<Language>()
private val psiFileCallsCounter = mutableMapOf<PsiFile, Int>()

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<CycodeAnnotator>().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)
}
Expand All @@ -48,7 +143,7 @@ class CycodeAnnotator : DumbAware, ExternalAnnotator<PsiFile, Unit>() {
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"
)
Expand All @@ -64,7 +159,7 @@ class CycodeAnnotator : DumbAware, ExternalAnnotator<PsiFile, Unit>() {
// 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"
)
Expand All @@ -79,7 +174,7 @@ class CycodeAnnotator : DumbAware, ExternalAnnotator<PsiFile, Unit>() {
// 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
}

Expand Down
5 changes: 0 additions & 5 deletions src/main/resources/META-INF/only-csharp.xml

This file was deleted.

6 changes: 0 additions & 6 deletions src/main/resources/META-INF/only-go.xml

This file was deleted.

6 changes: 0 additions & 6 deletions src/main/resources/META-INF/only-java.xml

This file was deleted.

5 changes: 0 additions & 5 deletions src/main/resources/META-INF/only-javascript.xml

This file was deleted.

5 changes: 0 additions & 5 deletions src/main/resources/META-INF/only-json.xml

This file was deleted.

5 changes: 0 additions & 5 deletions src/main/resources/META-INF/only-kotlin.xml

This file was deleted.

5 changes: 0 additions & 5 deletions src/main/resources/META-INF/only-php.xml

This file was deleted.

5 changes: 0 additions & 5 deletions src/main/resources/META-INF/only-python.xml

This file was deleted.

6 changes: 0 additions & 6 deletions src/main/resources/META-INF/only-xml.xml

This file was deleted.

5 changes: 0 additions & 5 deletions src/main/resources/META-INF/only-yaml.xml

This file was deleted.

11 changes: 0 additions & 11 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,6 @@
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.modules.lang</depends>

<depends optional="true" config-file="only-java.xml">com.intellij.java</depends>
<depends optional="true" config-file="only-yaml.xml">org.jetbrains.plugins.yaml</depends>
<depends optional="true" config-file="only-json.xml">com.intellij.modules.json</depends>
<depends optional="true" config-file="only-xml.xml">com.intellij.modules.xml</depends>
<depends optional="true" config-file="only-kotlin.xml">org.jetbrains.kotlin</depends>
<depends optional="true" config-file="only-python.xml">com.intellij.modules.python</depends>
<depends optional="true" config-file="only-php.xml">com.jetbrains.php</depends>
<depends optional="true" config-file="only-go.xml">org.jetbrains.plugins.go</depends>
<depends optional="true" config-file="only-csharp.xml">com.intellij.modules.rider</depends>
<depends optional="true" config-file="only-javascript.xml">JavaScript</depends>

<resource-bundle>messages.CycodeBundle</resource-bundle>

<extensions defaultExtensionNs="com.intellij">
Expand Down