Skip to content

Commit 10b3f1c

Browse files
authored
fix(amazonq): validationException if empty supplemental context chunk is sent to the service (#5920)
## Problem - LSP might return chunk with empty string, which will cause validation exception if such supplemental chunk is sent to the service - pretty logging ## Solution - filter empty chunks out from the result set returned from the lsp --- <!--- REMINDER: Ensure that your PR meets the guidelines in CONTRIBUTING.md --> License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent a86f34b commit 10b3f1c

File tree

6 files changed

+97
-15
lines changed

6 files changed

+97
-15
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": "Fix empty chunks being sent to service and get validationException"
4+
}

packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as vscode from 'vscode'
88
import * as sinon from 'sinon'
99
import * as crossFile from 'aws-core-vscode/codewhisperer'
1010
import { aStringWithLineCount, createMockTextEditor } from 'aws-core-vscode/test'
11-
import { crossFileContextConfig } from 'aws-core-vscode/codewhisperer'
11+
import { FeatureConfigProvider, crossFileContextConfig } from 'aws-core-vscode/codewhisperer'
1212
import {
1313
assertTabCount,
1414
closeAllEditors,
@@ -30,12 +30,17 @@ describe('crossFileContextUtil', function () {
3030

3131
let mockEditor: vscode.TextEditor
3232

33+
afterEach(function () {
34+
sinon.restore()
35+
})
36+
3337
describe('fetchSupplementalContextForSrc', function () {
3438
beforeEach(async function () {
3539
tempFolder = (await createTestWorkspaceFolder()).uri.fsPath
3640
})
3741

38-
it('should fetch 3 chunks and each chunk should contains 50 lines', async function () {
42+
it('opentabs context should fetch 3 chunks and each chunk should contains 50 lines', async function () {
43+
sinon.stub(FeatureConfigProvider.instance, 'isNewProjectContextGroup').alwaysReturned(false)
3944
await toTextEditor(aStringWithLineCount(200), 'CrossFile.java', tempFolder, { preview: false })
4045
const myCurrentEditor = await toTextEditor('', 'TargetFile.java', tempFolder, {
4146
preview: false,
@@ -207,6 +212,7 @@ describe('crossFileContextUtil', function () {
207212

208213
fileExtLists.forEach((fileExt) => {
209214
it('should be non empty', async function () {
215+
sinon.stub(FeatureConfigProvider.instance, 'isNewProjectContextGroup').alwaysReturned(false)
210216
const editor = await toTextEditor('content-1', `file-1.${fileExt}`, tempFolder)
211217
await toTextEditor('content-2', `file-2.${fileExt}`, tempFolder, { preview: false })
212218
await toTextEditor('content-3', `file-3.${fileExt}`, tempFolder, { preview: false })
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import assert from 'assert'
7+
import * as vscode from 'vscode'
8+
import * as sinon from 'sinon'
9+
import * as crossFile from 'aws-core-vscode/codewhisperer'
10+
import { TestFolder } from 'aws-core-vscode/test'
11+
import { FeatureConfigProvider } from 'aws-core-vscode/codewhisperer'
12+
import { toTextEditor } from 'aws-core-vscode/test'
13+
14+
describe('supplementalContextUtil', function () {
15+
let testFolder: TestFolder
16+
17+
const fakeCancellationToken: vscode.CancellationToken = {
18+
isCancellationRequested: false,
19+
onCancellationRequested: sinon.spy(),
20+
}
21+
22+
beforeEach(async function () {
23+
testFolder = await TestFolder.create()
24+
sinon.stub(FeatureConfigProvider.instance, 'isNewProjectContextGroup').alwaysReturned(false)
25+
})
26+
27+
afterEach(function () {
28+
sinon.restore()
29+
})
30+
31+
describe('fetchSupplementalContext', function () {
32+
describe('openTabsContext', function () {
33+
it('opentabContext should include chunks if non empty', async function () {
34+
await toTextEditor('class Foo', 'Foo.java', testFolder.path, { preview: false })
35+
await toTextEditor('class Bar', 'Bar.java', testFolder.path, { preview: false })
36+
await toTextEditor('class Baz', 'Baz.java', testFolder.path, { preview: false })
37+
38+
const editor = await toTextEditor('public class Foo {}', 'Query.java', testFolder.path, {
39+
preview: false,
40+
})
41+
42+
const actual = await crossFile.fetchSupplementalContext(editor, fakeCancellationToken)
43+
assert.ok(actual?.supplementalContextItems.length === 3)
44+
})
45+
46+
it('opentabsContext should filter out empty chunks', async function () {
47+
// open 3 files as supplemental context candidate files but none of them have contents
48+
await toTextEditor('', 'Foo.java', testFolder.path, { preview: false })
49+
await toTextEditor('', 'Bar.java', testFolder.path, { preview: false })
50+
await toTextEditor('', 'Baz.java', testFolder.path, { preview: false })
51+
52+
const editor = await toTextEditor('public class Foo {}', 'Query.java', testFolder.path, {
53+
preview: false,
54+
})
55+
56+
const actual = await crossFile.fetchSupplementalContext(editor, fakeCancellationToken)
57+
assert.ok(actual?.supplementalContextItems.length === 0)
58+
})
59+
})
60+
})
61+
})

packages/core/src/codewhisperer/util/editorContext.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { selectFrom } from '../../shared/utilities/tsUtils'
1717
import { checkLeftContextKeywordsForJson } from './commonUtil'
1818
import { CodeWhispererSupplementalContext } from '../models/model'
1919
import { getOptOutPreference } from '../../shared/telemetry/util'
20+
import { indent } from '../../shared'
2021

2122
let tabSize: number = getTabSizeSetting()
2223

@@ -202,18 +203,26 @@ function logSupplementalContext(supplementalContext: CodeWhispererSupplementalCo
202203
return
203204
}
204205

205-
let logString = `CodeWhispererSupplementalContext:
206-
isUtg: ${supplementalContext.isUtg},
207-
isProcessTimeout: ${supplementalContext.isProcessTimeout},
208-
contentsLength: ${supplementalContext.contentsLength},
209-
latency: ${supplementalContext.latency},
210-
`
206+
let logString = indent(
207+
`CodeWhispererSupplementalContext:
208+
isUtg: ${supplementalContext.isUtg},
209+
isProcessTimeout: ${supplementalContext.isProcessTimeout},
210+
contentsLength: ${supplementalContext.contentsLength},
211+
latency: ${supplementalContext.latency}
212+
strategy: ${supplementalContext.strategy}`,
213+
4,
214+
true
215+
).trimStart()
216+
211217
supplementalContext.supplementalContextItems.forEach((context, index) => {
212-
logString += `Chunk ${index}:
213-
Path: ${context.filePath}
214-
Content: ${index}:${context.content}
215-
Score: ${context.score}
216-
-----------------------------------------------`
218+
logString += indent(`\nChunk ${index}:\n`, 4, true)
219+
logString += indent(
220+
`Path: ${context.filePath}
221+
Length: ${context.content.length}
222+
Score: ${context.score}`,
223+
8,
224+
true
225+
)
217226
})
218227

219228
getLogger().debug(logString)

packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export async function fetchSupplementalContextForSrcV1(
152152
// DO NOT send code chunk with empty content
153153
getLogger().debug(`CodeWhisperer finished fetching crossfile context out of ${relevantCrossFilePaths.length} files`)
154154
return {
155-
supplementalContextItems: supplementalContexts.filter((item) => item.content.trim().length !== 0),
155+
supplementalContextItems: supplementalContexts,
156156
strategy: 'OpenTabs_BM25',
157157
}
158158
}

packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ export async function fetchSupplementalContext(
3939
return {
4040
isUtg: isUtg,
4141
isProcessTimeout: false,
42-
supplementalContextItems: value.supplementalContextItems,
42+
supplementalContextItems: value.supplementalContextItems.filter(
43+
(item) => item.content.trim().length !== 0
44+
),
4345
contentsLength: value.supplementalContextItems.reduce((acc, curr) => acc + curr.content.length, 0),
4446
latency: performance.now() - timesBeforeFetching,
4547
strategy: value.strategy,

0 commit comments

Comments
 (0)