Skip to content

Commit 0aa56e8

Browse files
authored
fix(codewhisperer): closing paren #3900
1 parent 3ec57b6 commit 0aa56e8

File tree

6 files changed

+582
-133
lines changed

6 files changed

+582
-133
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": "CodeWhisperer: fix and improve closing symbols handling (brackets, parenthesis, quotes etc)"
4+
}

scripts/lint/testLint.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import glob from 'glob'
77
import Mocha from 'mocha'
8-
98
;(async () => {
109
try {
1110
console.log('Running linting tests...')

src/codewhisperer/commands/onAcceptance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export async function onAcceptance(acceptanceEntry: OnRecommendationAcceptanceEn
3535
* Mitigation to right context handling mainly for auto closing bracket use case
3636
*/
3737
try {
38-
await handleExtraBrackets(acceptanceEntry.editor, acceptanceEntry.recommendation, end)
38+
await handleExtraBrackets(acceptanceEntry.editor, acceptanceEntry.recommendation, end, start)
3939
} catch (error) {
4040
getLogger().error(`${error} in handleAutoClosingBrackets`)
4141
}

src/codewhisperer/commands/onInlineAcceptance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export async function onInlineAcceptance(
8484
try {
8585
// Do not handle extra bracket if there is a right context merge
8686
if (acceptanceEntry.recommendation === session.recommendations[acceptanceEntry.acceptIndex].content) {
87-
await handleExtraBrackets(acceptanceEntry.editor, acceptanceEntry.recommendation, end)
87+
await handleExtraBrackets(acceptanceEntry.editor, acceptanceEntry.recommendation, end, start)
8888
}
8989
await ImportAdderProvider.instance.onAcceptRecommendation(
9090
acceptanceEntry.editor,

src/codewhisperer/util/closingBracketUtil.ts

Lines changed: 203 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,71 +12,74 @@ interface bracketMapType {
1212
[k: string]: string
1313
}
1414

15-
const bracketMap: bracketMapType = {
15+
const quotes = ["'", '"', '`']
16+
const parenthesis = ['(', '[', '{', ')', ']', '}', '<', '>']
17+
18+
const closeToOpen: bracketMapType = {
1619
')': '(',
1720
']': '[',
1821
'}': '{',
22+
'>': '<',
1923
}
2024

21-
export const calculateBracketsLevel = (code: string, isRightContext: boolean = false) => {
22-
const bracketCounts: Map<string, number> = new Map([
23-
['(', 0],
24-
['[', 0],
25-
['{', 0],
26-
])
27-
const bracketsIndexLevel = []
28-
29-
for (let i = 0; i < code.length; i++) {
30-
const char = code[i]
31-
if (bracketCounts.has(char)) {
32-
const count = bracketCounts.get(char) || 0
33-
const newCount = count + 1
34-
bracketCounts.set(char, newCount)
35-
bracketsIndexLevel.push({
36-
char,
37-
count: newCount,
38-
idx: i,
39-
})
40-
} else if (char in bracketMap) {
41-
const correspondingBracket = bracketMap[char as keyof bracketMapType]
42-
const count = bracketCounts.get(correspondingBracket) || 0
43-
const newCount = count === 0 ? 0 : count - 1
44-
bracketCounts.set(bracketMap[char], newCount)
45-
bracketsIndexLevel.push({
46-
char: correspondingBracket,
47-
count: newCount,
48-
idx: i,
49-
})
50-
} else if (isRightContext && !(char in bracketMap) && !bracketCounts.has(char) && !/\s/.test(char)) {
51-
// we can stop processing right context when we encounter a char that is not a bracket nor white space
52-
break
53-
}
54-
}
55-
return bracketsIndexLevel
25+
const openToClose: bracketMapType = {
26+
'(': ')',
27+
'[': ']',
28+
'{': '}',
29+
'<': '>',
5630
}
5731

58-
export const getBracketsToRemove = (recommendation: string, rightContext: string) => {
59-
const recommendationBrackets = calculateBracketsLevel(recommendation)
60-
const rightContextBrackets = calculateBracketsLevel(rightContext, true)
61-
let i = 0
62-
let j = 0
63-
const toBeRemoved = []
32+
/**
33+
* @param endPosition: end position of the recommendation
34+
* @param startPosition: start position of the recommendation
35+
*/
36+
export async function handleExtraBrackets(
37+
editor: vscode.TextEditor,
38+
recommendation: string,
39+
endPosition: vscode.Position,
40+
startPosition: vscode.Position
41+
) {
42+
const endOffset = editor.document.offsetAt(endPosition)
43+
const startOffset = editor.document.offsetAt(startPosition)
44+
const leftContext = editor.document.getText(
45+
new vscode.Range(
46+
startPosition,
47+
editor.document.positionAt(Math.max(startOffset - CodeWhispererConstants.charactersLimit, 0))
48+
)
49+
)
6450

65-
while (i < recommendationBrackets.length && j < rightContextBrackets.length) {
66-
const { char: char1, count: level1 } = recommendationBrackets[i]
67-
const { char: char2, count: level2, idx: idx2 } = rightContextBrackets[j]
68-
if (char1 !== char2 || level1 !== level2) {
69-
i++
70-
continue
71-
}
72-
toBeRemoved.push(idx2)
73-
i++
74-
j++
51+
const rightContext = editor.document.getText(
52+
new vscode.Range(
53+
editor.document.positionAt(endOffset),
54+
editor.document.positionAt(endOffset + CodeWhispererConstants.charactersLimit)
55+
)
56+
)
57+
const bracketsToRemove = getBracketsToRemove(
58+
editor,
59+
recommendation,
60+
leftContext,
61+
rightContext,
62+
endPosition,
63+
startPosition
64+
)
65+
66+
const quotesToRemove = getQuotesToRemove(
67+
editor,
68+
recommendation,
69+
leftContext,
70+
rightContext,
71+
endPosition,
72+
startPosition
73+
)
74+
75+
const symbolsToRemove = [...bracketsToRemove, ...quotesToRemove]
76+
77+
if (symbolsToRemove.length) {
78+
await removeBracketsFromRightContext(editor, symbolsToRemove, endPosition)
7579
}
76-
return toBeRemoved
7780
}
7881

79-
export const removeBracketsFromRightContext = async (
82+
const removeBracketsFromRightContext = async (
8083
editor: vscode.TextEditor,
8184
idxToRemove: number[],
8285
endPosition: vscode.Position
@@ -110,20 +113,154 @@ export const removeBracketsFromRightContext = async (
110113
}
111114
}
112115

113-
export async function handleExtraBrackets(
116+
function getBracketsToRemove(
114117
editor: vscode.TextEditor,
115118
recommendation: string,
116-
endPosition: vscode.Position
119+
leftContext: string,
120+
rightContext: string,
121+
end: vscode.Position,
122+
start: vscode.Position
117123
) {
118-
const end = editor.document.offsetAt(endPosition)
119-
const rightContext = editor.document.getText(
120-
new vscode.Range(
121-
editor.document.positionAt(end),
122-
editor.document.positionAt(end + CodeWhispererConstants.charactersLimit)
123-
)
124-
)
125-
const bracketsToRemove = getBracketsToRemove(recommendation, rightContext)
126-
if (bracketsToRemove.length) {
127-
await removeBracketsFromRightContext(editor, bracketsToRemove, endPosition)
124+
const unpairedClosingsInReco = nonClosedClosingParen(recommendation)
125+
const unpairedOpeningsInLeftContext = nonClosedOpneingParen(leftContext, unpairedClosingsInReco.length)
126+
const unpairedClosingsInRightContext = nonClosedClosingParen(rightContext)
127+
128+
const toRemove: number[] = []
129+
130+
let i = 0
131+
let j = 0
132+
let k = 0
133+
while (i < unpairedOpeningsInLeftContext.length && j < unpairedClosingsInReco.length) {
134+
const opening = unpairedOpeningsInLeftContext[i]
135+
const closing = unpairedClosingsInReco[j]
136+
137+
const isPaired = closeToOpen[closing.char] === opening.char
138+
const rightContextCharToDelete = unpairedClosingsInRightContext[k]
139+
140+
if (isPaired) {
141+
if (rightContextCharToDelete && rightContextCharToDelete.char === closing.char) {
142+
const rightContextStart = editor.document.offsetAt(end) + 1
143+
const symbolPosition = editor.document.positionAt(
144+
rightContextStart + rightContextCharToDelete.strOffset
145+
)
146+
const lineCnt = recommendation.split('\n').length - 1
147+
const isSameline = symbolPosition.line - lineCnt === start.line
148+
149+
if (isSameline) {
150+
toRemove.push(rightContextCharToDelete.strOffset)
151+
}
152+
153+
k++
154+
}
155+
}
156+
157+
i++
158+
j++
159+
}
160+
161+
return toRemove
162+
}
163+
164+
function getQuotesToRemove(
165+
editor: vscode.TextEditor,
166+
recommendation: string,
167+
leftContext: string,
168+
rightContext: string,
169+
endPosition: vscode.Position,
170+
startPosition: vscode.Position
171+
) {
172+
let leftQuote: string | undefined = undefined
173+
let leftIndex: number | undefined = undefined
174+
for (let i = leftContext.length - 1; i >= 0; i--) {
175+
const char = leftContext[i]
176+
if (quotes.includes(char)) {
177+
leftQuote = char
178+
leftIndex = leftContext.length - i
179+
break
180+
}
181+
}
182+
183+
let rightQuote: string | undefined = undefined
184+
let rightIndex: number | undefined = undefined
185+
for (let i = 0; i < rightContext.length; i++) {
186+
const char = rightContext[i]
187+
if (quotes.includes(char)) {
188+
rightQuote = char
189+
rightIndex = i
190+
break
191+
}
192+
}
193+
194+
let quoteCountInReco = 0
195+
if (leftQuote && rightQuote && leftQuote === rightQuote) {
196+
for (const char of recommendation) {
197+
if (quotes.includes(char) && char === leftQuote) {
198+
quoteCountInReco++
199+
}
200+
}
201+
}
202+
203+
if (leftIndex !== undefined && rightIndex !== undefined && quoteCountInReco % 2 !== 0) {
204+
const p = editor.document.positionAt(editor.document.offsetAt(endPosition) + rightIndex)
205+
206+
if (endPosition.line === startPosition.line && endPosition.line === p.line) {
207+
return [rightIndex]
208+
}
209+
}
210+
211+
return []
212+
}
213+
214+
function nonClosedOpneingParen(str: string, cnt?: number): { char: string; strOffset: number }[] {
215+
const resultSet: { char: string; strOffset: number }[] = []
216+
const stack: string[] = []
217+
218+
for (let i = str.length - 1; i >= 0; i--) {
219+
const char = str[i]
220+
if (char! in parenthesis) {
221+
continue
222+
}
223+
224+
if (char in closeToOpen) {
225+
stack.push(char)
226+
if (cnt && cnt === resultSet.length) {
227+
return resultSet
228+
}
229+
} else if (char in openToClose) {
230+
if (stack.length !== 0 && stack[stack.length - 1] === openToClose[char]) {
231+
stack.pop()
232+
} else {
233+
resultSet.push({ char: char, strOffset: i })
234+
}
235+
}
128236
}
237+
238+
return resultSet
239+
}
240+
241+
function nonClosedClosingParen(str: string, cnt?: number): { char: string; strOffset: number }[] {
242+
const resultSet: { char: string; strOffset: number }[] = []
243+
const stack: string[] = []
244+
245+
for (let i = 0; i < str.length; i++) {
246+
const char = str[i]
247+
if (char! in parenthesis) {
248+
continue
249+
}
250+
251+
if (char in openToClose) {
252+
stack.push(char)
253+
if (cnt && cnt === resultSet.length) {
254+
return resultSet
255+
}
256+
} else if (char in closeToOpen) {
257+
if (stack.length !== 0 && stack[stack.length - 1] === closeToOpen[char]) {
258+
stack.pop()
259+
} else {
260+
resultSet.push({ char: char, strOffset: i })
261+
}
262+
}
263+
}
264+
265+
return resultSet
129266
}

0 commit comments

Comments
 (0)