Skip to content

Commit eccdf21

Browse files
authored
Amazon Q : Fixing the inappropriate yellow lines shown in the editor file using CodeSnippet. (#4618)
* Dropping issue by checking the codeSnippet from listCodeAnalysis API
1 parent 937e9b4 commit eccdf21

File tree

5 files changed

+138
-40
lines changed

5 files changed

+138
-40
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "Amazon Q Security Scans: Fixed unnecessary yellow lines appearing in both auto scans and project scans."
4+
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ data class CodeWhispererCodeScanIssue(
739739
val severity: String,
740740
val recommendation: Recommendation,
741741
var suggestedFixes: List<SuggestedFix>,
742+
val codeSnippet: List<CodeLine>,
742743
val issueSeverity: HighlightDisplayLevel = HighlightDisplayLevel.WARNING,
743744
val isInvalid: Boolean = false,
744745
var rangeHighlighter: RangeHighlighterEx? = null

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanSession.kt

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ import java.time.Instant
6969
import java.util.Base64
7070
import java.util.UUID
7171
import kotlin.coroutines.coroutineContext
72-
import kotlin.math.max
73-
import kotlin.math.min
7472

7573
class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
7674
private val clientToken: UUID = UUID.randomUUID()
@@ -375,50 +373,69 @@ class CodeWhispererCodeScanSession(val sessionContext: CodeScanSessionContext) {
375373
}
376374

377375
fun mapToCodeScanIssues(recommendations: List<String>): List<CodeWhispererCodeScanIssue> {
378-
val scanRecommendations: List<CodeScanRecommendation> = recommendations.map {
379-
val value: List<CodeScanRecommendation> = MAPPER.readValue(it)
380-
value
381-
}.flatten()
376+
val scanRecommendations = recommendations.flatMap { MAPPER.readValue<List<CodeScanRecommendation>>(it) }
382377
if (isProjectScope()) {
383378
LOG.debug { "Total code scan issues returned from service: ${scanRecommendations.size}" }
384379
}
385-
return scanRecommendations.mapNotNull {
380+
return scanRecommendations.mapNotNull { recommendation ->
386381
val file = try {
387382
LocalFileSystem.getInstance().findFileByIoFile(
388-
Path.of(sessionContext.sessionConfig.projectRoot.path, it.filePath).toFile()
383+
Path.of(sessionContext.sessionConfig.projectRoot.path, recommendation.filePath).toFile()
389384
)
390385
} catch (e: Exception) {
391-
LOG.debug { "Cannot find file at location ${it.filePath}" }
386+
LOG.debug { "Cannot find file at location ${recommendation.filePath}" }
392387
null
393388
}
394-
when (file?.isDirectory) {
395-
false -> {
396-
runReadAction {
397-
FileDocumentManager.getInstance().getDocument(file)
398-
}?.let { document ->
399-
val endLineInDocument = min(max(0, it.endLine - 1), document.lineCount - 1)
389+
390+
if (file?.isDirectory == false) {
391+
runReadAction {
392+
FileDocumentManager.getInstance().getDocument(file)
393+
}?.let { document ->
394+
395+
val documentLines = document.getText().split("\n")
396+
val (startLine, endLine) = recommendation.run { startLine to endLine }
397+
var shouldDisplayIssue = true
398+
399+
for (codeBlock in recommendation.codeSnippet) {
400+
val lineNumber = codeBlock.number - 1
401+
if (codeBlock.number in startLine..endLine) {
402+
val documentLine = documentLines.getOrNull(lineNumber)
403+
if (documentLine != codeBlock.content) {
404+
shouldDisplayIssue = false
405+
break
406+
}
407+
}
408+
}
409+
410+
if (shouldDisplayIssue) {
411+
val endLineInDocument = minOf(maxOf(0, recommendation.endLine - 1), document.lineCount - 1)
400412
val endCol = document.getLineEndOffset(endLineInDocument) - document.getLineStartOffset(endLineInDocument) + 1
413+
401414
CodeWhispererCodeScanIssue(
402-
startLine = it.startLine,
415+
startLine = recommendation.startLine,
403416
startCol = 1,
404-
endLine = it.endLine,
417+
endLine = recommendation.endLine,
405418
endCol = endCol,
406419
file = file,
407420
project = sessionContext.project,
408-
title = it.title,
409-
description = it.description,
410-
detectorId = it.detectorId,
411-
detectorName = it.detectorName,
412-
findingId = it.findingId,
413-
ruleId = it.ruleId,
414-
relatedVulnerabilities = it.relatedVulnerabilities,
415-
severity = it.severity,
416-
recommendation = it.remediation.recommendation,
417-
suggestedFixes = it.remediation.suggestedFixes
421+
title = recommendation.title,
422+
description = recommendation.description,
423+
detectorId = recommendation.detectorId,
424+
detectorName = recommendation.detectorName,
425+
findingId = recommendation.findingId,
426+
ruleId = recommendation.ruleId,
427+
relatedVulnerabilities = recommendation.relatedVulnerabilities,
428+
severity = recommendation.severity,
429+
recommendation = recommendation.remediation.recommendation,
430+
suggestedFixes = recommendation.remediation.suggestedFixes,
431+
codeSnippet = recommendation.codeSnippet
418432
)
433+
} else {
434+
null
419435
}
420436
}
421-
else -> null
437+
} else {
438+
null
422439
}
423440
}.onEach { issue ->
424441
// Add range highlighters for all the issues found.
@@ -487,7 +504,8 @@ internal data class CodeScanRecommendation(
487504
val ruleId: String?,
488505
val relatedVulnerabilities: List<String>,
489506
val severity: String,
490-
val remediation: Remediation
507+
val remediation: Remediation,
508+
val codeSnippet: List<CodeLine>
491509
)
492510

493511
data class Description(val text: String, val markdown: String)
@@ -498,6 +516,8 @@ data class Recommendation(val text: String, val url: String)
498516

499517
data class SuggestedFix(val description: String, val code: String)
500518

519+
data class CodeLine(val number: Int, val content: String)
520+
501521
data class CodeScanSessionContext(
502522
val project: Project,
503523
val sessionConfig: CodeScanSessionConfig,

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererCodeScanTestBase.kt

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ open class CodeWhispererCodeScanTestBase(projectRule: CodeInsightTestFixtureRule
7777
internal lateinit var fakeCreateCodeScanResponseFailed: CreateCodeScanResponse
7878
internal lateinit var fakeCreateCodeScanResponsePending: CreateCodeScanResponse
7979
internal lateinit var fakeListCodeScanFindingsResponse: ListCodeScanFindingsResponse
80+
internal lateinit var fakeListCodeScanFindingsResponseE2E: ListCodeScanFindingsResponse
8081
internal lateinit var fakeListCodeScanFindingsOutOfBoundsIndexResponse: ListCodeScanFindingsResponse
8182
internal lateinit var fakeGetCodeScanResponse: GetCodeScanResponse
8283
internal lateinit var fakeGetCodeScanResponsePending: GetCodeScanResponse
@@ -110,7 +111,22 @@ open class CodeWhispererCodeScanTestBase(projectRule: CodeInsightTestFixtureRule
110111
)
111112
}
112113

113-
private fun setupCodeScanFinding(filePath: Path, startLine: Int, endLine: Int) = """
114+
private fun setupCodeScanFinding(
115+
filePath: Path,
116+
startLine: Int,
117+
endLine: Int,
118+
codeSnippets: List<Pair<Int, String>>
119+
): String {
120+
val codeSnippetJson = codeSnippets.joinToString(",\n") { (number, content) ->
121+
"""
122+
{
123+
"number": $number,
124+
"content": "$content"
125+
}
126+
""".trimIndent()
127+
}
128+
129+
return """
114130
{
115131
"filePath": "${filePath.systemIndependentPath}",
116132
"startLine": $startLine,
@@ -124,6 +140,9 @@ open class CodeWhispererCodeScanTestBase(projectRule: CodeInsightTestFixtureRule
124140
"detectorName": "detectorName",
125141
"findingId": "findingId",
126142
"relatedVulnerabilities": [],
143+
"codeSnippet": [
144+
$codeSnippetJson
145+
],
127146
"severity": "severity",
128147
"remediation": {
129148
"recommendation": {
@@ -133,19 +152,58 @@ open class CodeWhispererCodeScanTestBase(projectRule: CodeInsightTestFixtureRule
133152
"suggestedFixes": []
134153
}
135154
}
136-
""".trimIndent()
155+
""".trimIndent()
156+
}
137157

138158
private fun setupCodeScanFindings(filePath: Path) = """
139-
[
140-
${setupCodeScanFinding(filePath, 1, 2)},
141-
${setupCodeScanFinding(filePath, 1, 2)}
142-
]
159+
[
160+
${setupCodeScanFinding(
161+
filePath,
162+
1,
163+
2,
164+
listOf(
165+
1 to "import numpy as np",
166+
2 to " import from module1 import helper"
167+
)
168+
)},
169+
${setupCodeScanFinding(
170+
filePath,
171+
1,
172+
2,
173+
listOf(
174+
1 to "import numpy as np",
175+
2 to " import from module1 import helper"
176+
)
177+
)}
178+
]
179+
"""
180+
181+
private fun setupCodeScanFindingsE2E(filePath: Path) = """
182+
[
183+
${setupCodeScanFinding(
184+
filePath,
185+
1,
186+
2,
187+
listOf(
188+
1 to "using Utils;",
189+
2 to "using Helpers.Helper;"
190+
)
191+
)}
192+
]
143193
"""
144194

145195
private fun setupCodeScanFindingsOutOfBounds(filePath: Path) = """
146-
[
147-
${setupCodeScanFinding(filePath, 99999, 99999)}
148-
]
196+
[
197+
${setupCodeScanFinding(
198+
filePath,
199+
99999,
200+
99999,
201+
kotlin.collections.listOf(
202+
1 to "import numpy as np",
203+
2 to " import from module1 import helper"
204+
)
205+
)}
206+
]
149207
"""
150208

151209
protected fun setupResponse(filePath: Path) {
@@ -178,6 +236,11 @@ open class CodeWhispererCodeScanTestBase(projectRule: CodeInsightTestFixtureRule
178236
.responseMetadata(metadata)
179237
.build() as ListCodeScanFindingsResponse
180238

239+
fakeListCodeScanFindingsResponseE2E = ListCodeScanFindingsResponse.builder()
240+
.codeScanFindings(setupCodeScanFindingsE2E(filePath))
241+
.responseMetadata(metadata)
242+
.build() as ListCodeScanFindingsResponse
243+
181244
fakeListCodeScanFindingsOutOfBoundsIndexResponse = ListCodeScanFindingsResponse.builder()
182245
.codeScanFindings(setupCodeScanFindingsOutOfBounds(filePath))
183246
.responseMetadata(metadata)
@@ -214,6 +277,16 @@ open class CodeWhispererCodeScanTestBase(projectRule: CodeInsightTestFixtureRule
214277
"detectorName": "detectorName",
215278
"findingId": "findingId",
216279
"relatedVulnerabilities": [],
280+
"codeSnippet": [
281+
{
282+
"number": 1,
283+
"content": "codeBlock1"
284+
},
285+
{
286+
"number": 2,
287+
"content": "codeBlock2"
288+
}
289+
],
217290
"severity": "severity",
218291
"remediation": {
219292
"recommendation": {

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class CodeWhispererProjectCodeScanTest : CodeWhispererCodeScanTestBase(PythonCod
5454
onGeneric { createUploadUrl(any()) }.thenReturn(fakeCreateUploadUrlResponse)
5555
onGeneric { createCodeScan(any(), any()) }.thenReturn(fakeCreateCodeScanResponse)
5656
onGeneric { getCodeScan(any(), any()) }.thenReturn(fakeGetCodeScanResponse)
57-
onGeneric { listCodeScanFindings(any(), any()) }.thenReturn(fakeListCodeScanFindingsResponse)
57+
onGeneric { listCodeScanFindings(any(), any()) }.thenReturn(fakeListCodeScanFindingsResponseE2E)
5858
}
5959
}
6060

@@ -104,7 +104,7 @@ class CodeWhispererProjectCodeScanTest : CodeWhispererCodeScanTestBase(PythonCod
104104

105105
@Test
106106
fun `e2e happy path integration test`() {
107-
assertE2ERunsSuccessfully(sessionConfigSpy, project, totalLines, 10, totalSize, 2)
107+
assertE2ERunsSuccessfully(sessionConfigSpy, project, totalLines, 10, totalSize, 1)
108108
}
109109

110110
private fun setupCsharpProject() {

0 commit comments

Comments
 (0)