Skip to content

Commit 494ff1d

Browse files
authored
fix(amazonq auto scan): spurious yellow lines in editor aws#5237
Problem: - If user triggers a scan and modifies the code in the editor, the security issue shows yellow squiggle lines at different place in the editor. Solution: - Compare the existing code diff from editor with the codeSnippet from response. If match, show the issue to the user, else drop the suggestion. - Implemented only for Auto-Scans.
1 parent b0e20d8 commit 494ff1d

File tree

8 files changed

+115
-18
lines changed

8 files changed

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

packages/amazonq/test/unit/codewhisperer/service/securityScanHandler.test.ts

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ import {
1010
CodeAnalysisScope,
1111
RawCodeScanIssue,
1212
listScanResults,
13+
mapToAggregatedList,
1314
DefaultCodeWhispererClient,
1415
ListCodeScanFindingsResponse,
16+
CodeWhispererConstants,
1517
} from 'aws-core-vscode/codewhisperer'
1618
import assert from 'assert'
1719
import sinon from 'sinon'
20+
import * as vscode from 'vscode'
1821
import fs from 'fs'
1922

2023
const mockCodeScanFindings = JSON.stringify([
@@ -39,6 +42,7 @@ const mockCodeScanFindings = JSON.stringify([
3942
},
4043
suggestedFixes: [],
4144
},
45+
codeSnippet: [],
4246
} satisfies RawCodeScanIssue,
4347
])
4448

@@ -85,7 +89,8 @@ describe('securityScanHandler', function () {
8589
'jobId',
8690
'codeScanFindingsSchema',
8791
['projectPath'],
88-
CodeAnalysisScope.PROJECT
92+
CodeAnalysisScope.PROJECT,
93+
undefined
8994
)
9095

9196
assert.equal(aggregatedCodeScanIssueList.length, 2)
@@ -107,11 +112,74 @@ describe('securityScanHandler', function () {
107112
'jobId',
108113
'codeScanFindingsSchema',
109114
['projectPath'],
110-
CodeAnalysisScope.PROJECT
115+
CodeAnalysisScope.PROJECT,
116+
undefined
111117
)
112118

113119
assert.equal(aggregatedCodeScanIssueList.length, 2)
114120
assert.equal(aggregatedCodeScanIssueList[0].issues.length, 3)
115121
})
116122
})
123+
124+
describe('mapToAggregatedList', () => {
125+
let codeScanIssueMap: Map<string, RawCodeScanIssue[]>
126+
let editor: vscode.TextEditor | undefined
127+
128+
beforeEach(() => {
129+
codeScanIssueMap = new Map()
130+
editor = {
131+
document: {
132+
lineAt: (lineNumber: number): vscode.TextLine => ({
133+
lineNumber: lineNumber + 1,
134+
range: new vscode.Range(0, 0, 0, 0),
135+
rangeIncludingLineBreak: new vscode.Range(0, 0, 0, 0),
136+
firstNonWhitespaceCharacterIndex: 0,
137+
isEmptyOrWhitespace: false,
138+
text: `line ${lineNumber + 1}`,
139+
}),
140+
},
141+
} as vscode.TextEditor
142+
})
143+
144+
it('should aggregate issues by file path', () => {
145+
const json = JSON.stringify([
146+
{
147+
filePath: 'file1.ts',
148+
startLine: 1,
149+
endLine: 2,
150+
codeSnippet: [
151+
{ number: 1, content: 'line 1' },
152+
{ number: 2, content: 'line 2' },
153+
],
154+
},
155+
{ filePath: 'file2.ts', startLine: 1, endLine: 1, codeSnippet: [{ number: 1, content: 'line 1' }] },
156+
])
157+
158+
mapToAggregatedList(codeScanIssueMap, json, editor, CodeWhispererConstants.CodeAnalysisScope.FILE)
159+
160+
assert.equal(codeScanIssueMap.size, 2)
161+
assert.equal(codeScanIssueMap.get('file1.ts')?.length, 1)
162+
assert.equal(codeScanIssueMap.get('file2.ts')?.length, 1)
163+
})
164+
165+
it('should filter issues based on the scope', () => {
166+
const json = JSON.stringify([
167+
{
168+
filePath: 'file1.ts',
169+
startLine: 1,
170+
endLine: 2,
171+
codeSnippet: [
172+
{ number: 1, content: 'line 1' },
173+
{ number: 2, content: 'line 2' },
174+
],
175+
},
176+
{ filePath: 'file1.ts', startLine: 3, endLine: 3, codeSnippet: [{ number: 3, content: 'line 3' }] },
177+
])
178+
179+
mapToAggregatedList(codeScanIssueMap, json, editor, CodeWhispererConstants.CodeAnalysisScope.FILE)
180+
181+
assert.equal(codeScanIssueMap.size, 1)
182+
assert.equal(codeScanIssueMap.get('file1.ts')?.length, 2)
183+
})
184+
})
117185
})

packages/core/src/codewhisperer/commands/startSecurityScan.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,8 @@ export async function startSecurityScan(
199199
scanJob.jobId,
200200
CodeWhispererConstants.codeScanFindingsSchema,
201201
projectPaths,
202-
scope
202+
scope,
203+
editor
203204
)
204205
const { total, withFixes } = securityRecommendationCollection.reduce(
205206
(accumulator, current) => ({

packages/core/src/codewhisperer/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export { DocumentChangedSource, KeyStrokeHandler, DefaultDocumentChangedType } f
7474
export { ReferenceLogViewProvider } from './service/referenceLogViewProvider'
7575
export { LicenseUtil } from './util/licenseUtil'
7676
export { SecurityIssueProvider } from './service/securityIssueProvider'
77-
export { listScanResults } from './service/securityScanHandler'
77+
export { listScanResults, mapToAggregatedList } from './service/securityScanHandler'
7878
export { CodeWhispererCodeCoverageTracker } from './tracker/codewhispererCodeCoverageTracker'
7979
export { TelemetryHelper } from './util/telemetryHelper'
8080
export { LineSelection, LineTracker } from './tracker/lineTracker'

packages/core/src/codewhisperer/models/model.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,11 @@ export interface Remediation {
715715
suggestedFixes: SuggestedFix[]
716716
}
717717

718+
export interface CodeLine {
719+
content: string
720+
number: number
721+
}
722+
718723
export interface RawCodeScanIssue {
719724
filePath: string
720725
startLine: number
@@ -728,6 +733,7 @@ export interface RawCodeScanIssue {
728733
relatedVulnerabilities: string[]
729734
severity: string
730735
remediation: Remediation
736+
codeSnippet: CodeLine[]
731737
}
732738

733739
export interface CodeScanIssue {

packages/core/src/codewhisperer/service/securityScanHandler.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { DefaultCodeWhispererClient } from '../client/codewhisperer'
77
import { getLogger } from '../../shared/logger'
8+
import * as vscode from 'vscode'
89
import {
910
AggregatedCodeScanIssue,
1011
CodeScanIssue,
@@ -44,7 +45,8 @@ export async function listScanResults(
4445
jobId: string,
4546
codeScanFindingsSchema: string,
4647
projectPaths: string[],
47-
scope: CodeWhispererConstants.CodeAnalysisScope
48+
scope: CodeWhispererConstants.CodeAnalysisScope,
49+
editor: vscode.TextEditor | undefined
4850
) {
4951
const logger = getLoggerForScope(scope)
5052
const codeScanIssueMap: Map<string, RawCodeScanIssue[]> = new Map()
@@ -62,7 +64,7 @@ export async function listScanResults(
6264
})
6365
.promise()
6466
issues.forEach(issue => {
65-
mapToAggregatedList(codeScanIssueMap, issue)
67+
mapToAggregatedList(codeScanIssueMap, issue, editor, scope)
6668
})
6769
codeScanIssueMap.forEach((issues, key) => {
6870
// Project path example: /Users/username/project
@@ -109,19 +111,32 @@ function mapRawToCodeScanIssue(issue: RawCodeScanIssue): CodeScanIssue {
109111
}
110112
}
111113

112-
function mapToAggregatedList(codeScanIssueMap: Map<string, RawCodeScanIssue[]>, json: string) {
114+
export function mapToAggregatedList(
115+
codeScanIssueMap: Map<string, RawCodeScanIssue[]>,
116+
json: string,
117+
editor: vscode.TextEditor | undefined,
118+
scope: CodeWhispererConstants.CodeAnalysisScope
119+
) {
113120
const codeScanIssues: RawCodeScanIssue[] = JSON.parse(json)
114-
codeScanIssues.forEach(issue => {
115-
if (codeScanIssueMap.has(issue.filePath)) {
116-
const list = codeScanIssueMap.get(issue.filePath)
117-
if (list === undefined) {
118-
codeScanIssueMap.set(issue.filePath, [issue])
119-
} else {
120-
list.push(issue)
121-
codeScanIssueMap.set(issue.filePath, list)
121+
const filteredIssues = codeScanIssues.filter(issue => {
122+
if (scope === CodeWhispererConstants.CodeAnalysisScope.FILE && editor) {
123+
for (let lineNumber = issue.startLine; lineNumber <= issue.endLine; lineNumber++) {
124+
const line = editor.document.lineAt(lineNumber - 1)?.text
125+
const codeContent = issue.codeSnippet.find(codeIssue => codeIssue.number === lineNumber)?.content
126+
if (line !== codeContent) {
127+
return false
128+
}
122129
}
130+
}
131+
return true
132+
})
133+
134+
filteredIssues.forEach(issue => {
135+
const filePath = issue.filePath
136+
if (codeScanIssueMap.has(filePath)) {
137+
codeScanIssueMap.get(filePath)?.push(issue)
123138
} else {
124-
codeScanIssueMap.set(issue.filePath, [issue])
139+
codeScanIssueMap.set(filePath, [issue])
125140
}
126141
})
127142
}

packages/core/src/test/codewhisperer/commands/startSecurityScan.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ const mockCodeScanFindings = JSON.stringify([
106106
},
107107
suggestedFixes: [],
108108
},
109+
codeSnippet: [],
109110
} satisfies model.RawCodeScanIssue,
110111
])
111112

packages/core/src/testE2E/codewhisperer/securityScan.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ describe('CodeWhisperer security scan', async function () {
143143
scanJob.jobId,
144144
CodeWhispererConstants.codeScanFindingsSchema,
145145
projectPaths,
146-
scope
146+
scope,
147+
editor
147148
)
148149

149150
assert.deepStrictEqual(jobStatus, 'Completed')
@@ -183,7 +184,8 @@ describe('CodeWhisperer security scan', async function () {
183184
scanJob.jobId,
184185
CodeWhispererConstants.codeScanFindingsSchema,
185186
projectPaths,
186-
scope
187+
scope,
188+
editor
187189
)
188190

189191
assert.deepStrictEqual(jobStatus, 'Completed')

0 commit comments

Comments
 (0)