Skip to content

Commit

Permalink
feat: code action for OSS via LS in IntelliJ [IDE-284] (#530)
Browse files Browse the repository at this point in the history
* feat: code action oss

* chore: add CHANGELOG
  • Loading branch information
teodora-sandu authored Jul 8, 2024
1 parent 2b964bd commit 68f330e
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Snyk Security Changelog

## [2.8.8]
### Added
- renders code actions and code lenses for OpenSource scans via the LS

### Fixes
- change some of the colours used in the HTML panel so it's consistent with designs
Expand Down
9 changes: 8 additions & 1 deletion src/main/kotlin/snyk/code/annotator/CodeActionIntention.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ class CodeActionIntention(

override fun getIcon(p0: Int): Icon {
return when (product) {
ProductType.OSS -> SnykIcons.OPEN_SOURCE_SECURITY
ProductType.OSS -> {
if (this.codeAction.title.startsWith("Upgrade to")) {
SnykIcons.CHECKMARK_GREEN
} else {
SnykIcons.OPEN_SOURCE_SECURITY
}
}
ProductType.IAC -> SnykIcons.IAC
ProductType.CONTAINER -> SnykIcons.CONTAINER
ProductType.CODE_SECURITY -> SnykIcons.SNYK_CODE
Expand All @@ -84,6 +90,7 @@ class CodeActionIntention(
override fun getPriority(): PriorityAction.Priority {
return when {
codeAction.title.contains("fix", ignoreCase = true) -> PriorityAction.Priority.TOP
codeAction.title.contains("Upgrade to", ignoreCase = true) -> PriorityAction.Priority.TOP
else -> issue.getSeverityAsEnum().getQuickFixPriority()
}
}
Expand Down
43 changes: 26 additions & 17 deletions src/main/kotlin/snyk/code/annotator/SnykAnnotator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import java.util.concurrent.TimeoutException

private const val CODEACTION_TIMEOUT = 2L

abstract class SnykAnnotator(private val product: ProductType): ExternalAnnotator<PsiFile, Unit>() {
abstract class SnykAnnotator(private val product: ProductType) : ExternalAnnotator<PsiFile, Unit>() {
val logger = logger<SnykAnnotator>()

// overrides needed for the Annotator to invoke apply(). We don't do anything here
Expand All @@ -32,7 +32,11 @@ abstract class SnykAnnotator(private val product: ProductType): ExternalAnnotato
AnnotatorCommon.prepareAnnotate(psiFile)
}

override fun apply(psiFile: PsiFile, annotationResult: Unit, holder: AnnotationHolder) {
override fun apply(
psiFile: PsiFile,
annotationResult: Unit,
holder: AnnotationHolder,
) {
if (!LanguageServerWrapper.getInstance().ensureLanguageServerInitialized()) return

getIssuesForFile(psiFile)
Expand All @@ -47,25 +51,27 @@ abstract class SnykAnnotator(private val product: ProductType): ExternalAnnotato
return@forEach
}
if (!textRange.isEmpty) {
val annotationMessage = "${issue.annotationMessage()} (Snyk)"
val annotationMessage = issue.annotationMessage()
holder.newAnnotation(highlightSeverity, annotationMessage)
.range(textRange)
.withFix(ShowDetailsIntentionAction(annotationMessage, issue))
.create()

val params = CodeActionParams(
TextDocumentIdentifier(psiFile.virtualFile.toLanguageServerURL()),
issue.range,
CodeActionContext(emptyList())
)
val params =
CodeActionParams(
TextDocumentIdentifier(psiFile.virtualFile.toLanguageServerURL()),
issue.range,
CodeActionContext(emptyList()),
)
val languageServer = LanguageServerWrapper.getInstance().languageServer
val codeActions = try {
languageServer.textDocumentService
.codeAction(params).get(CODEACTION_TIMEOUT, TimeUnit.SECONDS) ?: emptyList()
} catch (ignored: TimeoutException) {
logger.info("Timeout fetching code actions for issue: $issue")
emptyList()
}
val codeActions =
try {
languageServer.textDocumentService
.codeAction(params).get(CODEACTION_TIMEOUT, TimeUnit.SECONDS) ?: emptyList()
} catch (ignored: TimeoutException) {
logger.info("Timeout fetching code actions for issue: $issue")
emptyList()
}

codeActions
.filter { a ->
Expand All @@ -78,7 +84,7 @@ abstract class SnykAnnotator(private val product: ProductType): ExternalAnnotato
val title = codeAction.title
holder.newAnnotation(highlightSeverity, title)
.range(textRange)
.withFix(CodeActionIntention(issue, codeAction, ProductType.CODE_SECURITY))
.withFix(CodeActionIntention(issue, codeAction, product))
.create()
}
}
Expand All @@ -94,7 +100,10 @@ abstract class SnykAnnotator(private val product: ProductType): ExternalAnnotato
?: emptySet()

/** Public for Tests only */
fun textRange(psiFile: PsiFile, range: Range): TextRange? {
fun textRange(
psiFile: PsiFile,
range: Range,
): TextRange? {
try {
val document =
psiFile.viewProvider.document ?: throw IllegalArgumentException("No document found for $psiFile")
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/snyk/common/lsp/LanguageServerSettings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ data class LanguageServerSettings(
@SerializedName("authenticationMethod") val authenticationMethod: AuthenticationMethod? = null,
@SerializedName("snykCodeApi") val snykCodeApi: String? = null,
@SerializedName("enableSnykLearnCodeActions") val enableSnykLearnCodeActions: String? = null,
@SerializedName("enableSnykOSSQuickFixCodeActions") val enableSnykOSSQuickFixCodeActions: String? = null,
@SerializedName("requiredProtocolVersion") val requiredProtocolVersion: String =
pluginSettings().requiredLsProtocolVersion.toString(),
)
Expand All @@ -47,13 +48,13 @@ data class SeverityFilter(
@SerializedName("critical") val critical: Boolean?,
@SerializedName("high") val high: Boolean?,
@SerializedName("medium") val medium: Boolean?,
@SerializedName("low") val low: Boolean?
@SerializedName("low") val low: Boolean?,
)

enum class AuthenticationMethod {
@SerializedName("token")
TokenAuthentication,

@SerializedName("oauth")
OAuthAuthentication
OAuthAuthentication,
}
1 change: 1 addition & 0 deletions src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ class LanguageServerWrapper(
scanningMode = if (!ps.scanOnSave) "manual" else "auto",
integrationName = pluginInfo.integrationName,
integrationVersion = pluginInfo.integrationVersion,
enableSnykOSSQuickFixCodeActions = "true",
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/snyk/common/lsp/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,12 @@ data class ScanIssue(

fun annotationMessage(): String {
return when (this.additionalData.getProductType()) {
ProductType.OSS -> this.title
ProductType.OSS -> this.title + " in " + this.additionalData.packageName + " id: " + this.ruleId()
ProductType.CODE_SECURITY, ProductType.CODE_QUALITY ->
this.title.ifBlank {
this.additionalData.message.let {
if (it.length < 70) it else "${it.take(70)}..."
}
} + " (Snyk)"
}

else -> TODO()
Expand Down

0 comments on commit 68f330e

Please sign in to comment.