Skip to content

Commit 7ab716a

Browse files
feat(CodeWhisperer): add cwspr jsx file support (#2859)
* Add cwspr jsx file support * Refactor programming language system we use in cwspr code base. - Unify languageId to use CodewhispererLanguage as possible - Use vscode languageId less as we can to reduce complexity - Transform vscodeLanguageId into CodewhispererLanguage early in the lifecycle of cwspr invocation (extractContextForCodeWhisperer()) Co-authored-by: JadenSimon <[email protected]>
1 parent 7385e33 commit 7ab716a

File tree

12 files changed

+252
-126
lines changed

12 files changed

+252
-126
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "CodeWhisperer now supports .jsx files"
4+
}

src/codewhisperer/activation.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,13 @@ export async function activate(context: ExtContext): Promise<void> {
207207
RecommendationHandler.instance.clearRecommendations()
208208
}),
209209

210-
vscode.languages.registerHoverProvider(CodeWhispererConstants.supportedLanguages, referenceHoverProvider),
210+
vscode.languages.registerHoverProvider([...CodeWhispererConstants.supportedLanguages], referenceHoverProvider),
211211
vscode.window.registerWebviewViewProvider(ReferenceLogViewProvider.viewType, referenceLogViewProvider),
212212
showReferenceLog.register(context),
213-
vscode.languages.registerCodeLensProvider(CodeWhispererConstants.supportedLanguages, referenceCodeLensProvider)
213+
vscode.languages.registerCodeLensProvider(
214+
[...CodeWhispererConstants.supportedLanguages],
215+
referenceCodeLensProvider
216+
)
214217
)
215218

216219
function activateSecurityScan() {
@@ -293,14 +296,14 @@ export async function activate(context: ExtContext): Promise<void> {
293296
)?.countTotalTokens(e)
294297
/**
295298
* Handle this keystroke event only when
296-
* 1. It is in current non plaintext active editor
299+
* 1. It is in current active editor with cwspr supported file types
297300
* 2. It is not a backspace
298301
* 3. It is not caused by CodeWhisperer editing
299302
* 4. It is not from undo/redo.
300303
*/
301304
if (
302305
e.document === vscode.window.activeTextEditor?.document &&
303-
runtimeLanguageContext.convertLanguage(e.document.languageId) !== 'plaintext' &&
306+
runtimeLanguageContext.isLanguageSupported(e.document.languageId) &&
304307
e.contentChanges.length != 0 &&
305308
!vsCodeState.isCodeWhispererEditing &&
306309
!JSON.stringify(e).includes('reason')
@@ -392,7 +395,7 @@ export async function activate(context: ExtContext): Promise<void> {
392395
// request access C9
393396
requestAccessCloud9.register(context.extensionContext.globalState),
394397
updateCloud9TreeNodes.register(context.extensionContext.globalState),
395-
vscode.languages.registerCompletionItemProvider(CodeWhispererConstants.supportedLanguages, {
398+
vscode.languages.registerCompletionItemProvider([...CodeWhispererConstants.supportedLanguages], {
396399
async provideCompletionItems(
397400
document: vscode.TextDocument,
398401
position: vscode.Position,
@@ -425,7 +428,7 @@ export async function activate(context: ExtContext): Promise<void> {
425428

426429
if (
427430
e.document === vscode.window.activeTextEditor?.document &&
428-
runtimeLanguageContext.convertLanguage(e.document.languageId) !== 'plaintext' &&
431+
runtimeLanguageContext.isLanguageSupported(e.document.languageId) &&
429432
e.contentChanges.length != 0 &&
430433
!vsCodeState.isCodeWhispererEditing
431434
) {

src/codewhisperer/models/constants.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ export const javascript = 'javascript'
6969

7070
export const typescript = 'typescript'
7171

72-
export const supportedLanguages = ['java', 'python', 'javascript', 'typescript']
72+
export const plaintext = 'plaintext'
73+
74+
// use vscode languageId here
75+
export const supportedLanguages = ['java', 'python', 'javascript', 'javascriptreact', 'typescript'] as const
76+
77+
export type SupportedLanguage = typeof supportedLanguages[number]
7378

7479
/**
7580
* Prompt

src/codewhisperer/service/completionProvider.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ export function getCompletionItem(
4141
completionItem.preselect = true
4242
completionItem.sortText = String(recommendationIndex + 1).padStart(10, '0')
4343
completionItem.range = new vscode.Range(start, position)
44-
let languageId = document.languageId
45-
languageId = languageId === CodeWhispererConstants.typescript ? CodeWhispererConstants.javascript : languageId
46-
const languageContext = runtimeLanguageContext.getLanguageContext(languageId)
44+
const languageContext = runtimeLanguageContext.getLanguageContext(document.languageId)
4745
let references = undefined
4846
if (recommendationDetail.references != undefined && recommendationDetail.references.length > 0) {
4947
references = recommendationDetail.references

src/codewhisperer/service/inlineCompletion.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,7 @@ export class InlineCompletion {
114114
{ undoStopAfter: false, undoStopBefore: false }
115115
)
116116
.then(async () => {
117-
let languageId = editor?.document?.languageId
118-
languageId =
119-
languageId === CodeWhispererConstants.typescript ? CodeWhispererConstants.javascript : languageId
120-
const languageContext = runtimeLanguageContext.getLanguageContext(languageId)
117+
const languageContext = runtimeLanguageContext.getLanguageContext(editor.document.languageId)
121118
const index = this.items[this.position].index
122119
const acceptArguments = [
123120
this._range,
@@ -405,12 +402,7 @@ export class InlineCompletion {
405402
this.setRange(new vscode.Range(editor.selection.active, editor.selection.active))
406403
try {
407404
await this.showRecommendation(editor)
408-
let languageId = editor?.document?.languageId
409-
languageId =
410-
languageId === CodeWhispererConstants.typescript
411-
? CodeWhispererConstants.javascript
412-
: languageId
413-
const languageContext = runtimeLanguageContext.getLanguageContext(languageId)
405+
const languageContext = runtimeLanguageContext.getLanguageContext(editor.document.languageId)
414406
telemetry.codewhisperer_perceivedLatency.emit({
415407
codewhispererRequestId: RecommendationHandler.instance.requestId,
416408
codewhispererSessionId: RecommendationHandler.instance.sessionId,

src/codewhisperer/service/recommendationHandler.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,10 @@ export class RecommendationHandler {
153153
* Validate request
154154
*/
155155
if (EditorContext.validateRequest(req)) {
156+
const mappedReq = runtimeLanguageContext.mapToRuntimeLanguage(req)
156157
const codewhispererPromise = pagination
157-
? client.listRecommendations(req)
158-
: client.generateRecommendations(req)
158+
? client.listRecommendations(mappedReq)
159+
: client.generateRecommendations(mappedReq)
159160
shouldRecordServiceInvocation = true
160161
const resp = await this.getServerResponse(
161162
triggerType,
@@ -183,11 +184,7 @@ export class RecommendationHandler {
183184
getLogger().info('Invalid Request : ', JSON.stringify(req, undefined, EditorContext.getTabSize()))
184185
getLogger().verbose(`Invalid Request : ${JSON.stringify(req, undefined, EditorContext.getTabSize())}`)
185186
errorCode = `Invalid Request`
186-
if (
187-
!CodeWhispererConstants.supportedLanguages.includes(
188-
req.fileContext.programmingLanguage.languageName
189-
)
190-
) {
187+
if (!runtimeLanguageContext.isLanguageSupported(req.fileContext.programmingLanguage.languageName)) {
191188
this.errorMessagePrompt = `${req.fileContext.programmingLanguage.languageName} is currently not supported by CodeWhisperer`
192189
}
193190
}
@@ -210,8 +207,7 @@ export class RecommendationHandler {
210207
}
211208
} finally {
212209
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
213-
const languageId = editor?.document?.languageId
214-
const languageContext = runtimeLanguageContext.getLanguageContext(languageId)
210+
const languageContext = runtimeLanguageContext.getLanguageContext(editor.document.languageId)
215211
getLogger().verbose(
216212
`Request ID: ${requestId}, timestamp(epoch): ${Date.now()}, timezone: ${timezone}, datetime: ${new Date().toLocaleString(
217213
[],
@@ -284,7 +280,7 @@ export class RecommendationHandler {
284280
requestId,
285281
sessionId,
286282
page,
287-
editor?.document.languageId
283+
editor.document.languageId
288284
)
289285
}
290286
this.requestId = requestId

src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import globals from '../../shared/extensionGlobals'
1010
import { vsCodeState } from '../models/model'
1111
import { distance } from 'fastest-levenshtein'
1212
import { CodewhispererLanguage, telemetry } from '../../shared/telemetry/telemetry'
13+
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
1314

1415
interface CodeWhispererToken {
1516
range: vscode.Range
@@ -170,7 +171,7 @@ export class CodeWhispererCodeCoverageTracker {
170171
// ignore no contentChanges. ignore contentChanges from other plugins (formatters)
171172
// only include contentChanges from user action
172173
if (
173-
!CodeWhispererConstants.supportedLanguages.includes(e.document.languageId) ||
174+
!runtimeLanguageContext.isLanguageSupported(e.document.languageId) ||
174175
vsCodeState.isCodeWhispererEditing ||
175176
e.contentChanges.length !== 1
176177
)
@@ -189,7 +190,7 @@ export class CodeWhispererCodeCoverageTracker {
189190

190191
public static readonly instances = new Map<string, CodeWhispererCodeCoverageTracker>()
191192
public static getTracker(language: string, memento: vscode.Memento): CodeWhispererCodeCoverageTracker | undefined {
192-
if (CodeWhispererConstants.supportedLanguages.includes(language)) {
193+
if (runtimeLanguageContext.isLanguageSupported(language)) {
193194
const instance = this.instances.get(language) ?? new this(language as CodewhispererLanguage, memento)
194195
this.instances.set(language, instance)
195196
return instance

src/codewhisperer/util/editorContext.ts

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,13 @@ import * as codewhispererClient from '../client/codewhisperer'
88
import * as path from 'path'
99
import * as CodeWhispererConstants from '../models/constants'
1010
import { getTabSizeSetting } from '../../shared/utilities/editorUtilities'
11-
import { runtimeLanguageContext } from './runtimeLanguageContext'
1211
import { TelemetryHelper } from './telemetryHelper'
1312
import { getLogger } from '../../shared/logger/logger'
13+
import { runtimeLanguageContext } from './runtimeLanguageContext'
1414

1515
let tabSize: number = getTabSizeSetting()
16+
1617
export function extractContextForCodeWhisperer(editor: vscode.TextEditor): codewhispererClient.FileContext {
17-
let editorFileContext: codewhispererClient.FileContext = {
18-
filename: getFileName(editor),
19-
programmingLanguage: {
20-
languageName: editor.document.languageId,
21-
},
22-
leftFileContent: '',
23-
rightFileContent: '',
24-
}
2518
const document = editor.document
2619
const curPos = editor.selection.active
2720
const offset = document.offsetAt(curPos)
@@ -40,39 +33,24 @@ export function extractContextForCodeWhisperer(editor: vscode.TextEditor): codew
4033
document.positionAt(offset + CodeWhispererConstants.charactersLimit)
4134
)
4235
)
43-
editorFileContext = {
36+
37+
return {
4438
filename: getFileName(editor),
4539
programmingLanguage: {
4640
languageName:
47-
editor.document.languageId === CodeWhispererConstants.typescript
48-
? CodeWhispererConstants.javascript
49-
: editor.document.languageId,
41+
runtimeLanguageContext.mapVscLanguageToCodeWhispererLanguage(editor.document.languageId) ??
42+
editor.document.languageId,
5043
},
5144
leftFileContent: caretLeftFileContext,
5245
rightFileContent: caretRightFileContext,
53-
}
54-
return editorFileContext
46+
} as codewhispererClient.FileContext
5547
}
5648

5749
export function getFileName(editor: vscode.TextEditor): string {
5850
const fileName = path.basename(editor.document.fileName)
5951
return fileName.substring(0, CodeWhispererConstants.filenameCharsLimit)
6052
}
6153

62-
export function getProgrammingLanguage(editor: vscode.TextEditor | undefined): codewhispererClient.ProgrammingLanguage {
63-
let programmingLanguage: codewhispererClient.ProgrammingLanguage = {
64-
languageName: '',
65-
}
66-
if (editor !== undefined) {
67-
const languageId = editor?.document?.languageId
68-
const languageContext = runtimeLanguageContext.getLanguageContext(languageId)
69-
programmingLanguage = {
70-
languageName: languageContext.language,
71-
}
72-
}
73-
return programmingLanguage
74-
}
75-
7654
export function buildListRecommendationRequest(
7755
editor: vscode.TextEditor,
7856
nextToken: string
@@ -129,7 +107,7 @@ export function validateRequest(
129107
req.fileContext.programmingLanguage.languageName == undefined ||
130108
req.fileContext.programmingLanguage.languageName.length < 1 ||
131109
req.fileContext.programmingLanguage.languageName.length > 128 ||
132-
!CodeWhispererConstants.supportedLanguages.includes(req.fileContext.programmingLanguage.languageName)
110+
!runtimeLanguageContext.isLanguageSupported(req.fileContext.programmingLanguage.languageName)
133111
)
134112
const isFileNameValid = !(req.fileContext.filename == undefined || req.fileContext.filename.length < 1)
135113
const isFileContextValid = !(

src/codewhisperer/util/runtimeLanguageContext.ts

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,95 @@
22
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5+
56
import { CodewhispererLanguage } from '../../shared/telemetry/telemetry.gen'
7+
import { createConstantMap, ConstantMap } from '../../shared/utilities/tsUtils'
8+
import * as codewhispererClient from '../client/codewhisperer'
69
import * as CodeWhispererConstants from '../models/constants'
710

8-
interface RuntimeLanguageContextData {
11+
export class RuntimeLanguageContext {
912
/**
10-
* collection of all language runtime versions
13+
* A map storing cwspr supporting programming language with key: vscLanguageId and value: cwsprLanguageId
14+
* Key: vscLanguageId
15+
* Value: CodeWhispererLanguageId
1116
*/
12-
languageContexts: {
13-
[language in CodewhispererLanguage as string]: {
14-
/**
15-
* the language of the current file
16-
*/
17-
language: CodewhispererLanguage
18-
}
17+
private supportedLanguageMap: ConstantMap<CodeWhispererConstants.SupportedLanguage, CodewhispererLanguage>
18+
19+
// A set contains vscode languageId and CodeWhispererLanguage
20+
private supportedLanguageSet = new Set<string>()
21+
22+
constructor() {
23+
this.supportedLanguageMap = createConstantMap<CodeWhispererConstants.SupportedLanguage, CodewhispererLanguage>({
24+
java: 'java',
25+
python: 'python',
26+
javascriptreact: 'jsx',
27+
typescript: 'javascript',
28+
javascript: 'javascript',
29+
})
30+
31+
const values = Array.from(this.supportedLanguageMap.values())
32+
const keys = Array.from(this.supportedLanguageMap.keys())
33+
values.forEach(item => this.supportedLanguageSet.add(item))
34+
keys.forEach(item => this.supportedLanguageSet.add(item))
1935
}
20-
}
2136

22-
export class RuntimeLanguageContext {
23-
private runtimeLanguageContext: RuntimeLanguageContextData = {
24-
languageContexts: {
25-
['plaintext']: {
26-
language: 'plaintext',
27-
},
28-
['java']: {
29-
language: 'java',
30-
},
31-
['python']: {
32-
language: 'python',
33-
},
34-
['javascript']: {
35-
language: 'javascript',
36-
},
37-
},
37+
/**
38+
*
39+
* @param vscLanguageId : official vscode languageId
40+
* @returns corresponding CodewhispererLanguage ID if any, otherwise undefined
41+
*/
42+
public mapVscLanguageToCodeWhispererLanguage(vscLanguageId?: string): CodewhispererLanguage | undefined {
43+
return this.supportedLanguageMap.get(vscLanguageId) ?? undefined
3844
}
3945

40-
public getLanguageContext(languageId?: string) {
41-
const languageName = this.convertLanguage(languageId)
42-
if (languageName in this.runtimeLanguageContext.languageContexts) {
43-
return this.runtimeLanguageContext.languageContexts[languageName]
46+
/**
47+
* @param vscLanguageId : official vscode languageId
48+
* @returns An object with a field language: CodewhispererLanguage, if no corresponding CodewhispererLanguage ID, plaintext is returned
49+
*/
50+
public getLanguageContext(vscLanguageId?: string): { language: CodewhispererLanguage } {
51+
return { language: this.mapVscLanguageToCodeWhispererLanguage(vscLanguageId) ?? 'plaintext' }
52+
}
53+
54+
/**
55+
* Mapping the field ProgrammingLanguage of codewhisperer generateRecommendationRequest | listRecommendationRequest to
56+
* its Codewhisperer runtime language e.g. jsx -> typescript, typescript -> typescript
57+
* @param request : cwspr generateRecommendationRequest | ListRecommendationRequest
58+
* @returns request with source language name mapped to cwspr runtime language
59+
*/
60+
public mapToRuntimeLanguage<
61+
T extends codewhispererClient.ListRecommendationsRequest | codewhispererClient.GenerateRecommendationsRequest
62+
>(request: T): T {
63+
const fileContext = request.fileContext
64+
const childLanguage = request.fileContext.programmingLanguage
65+
let parentLanguage: codewhispererClient.ProgrammingLanguage
66+
switch (childLanguage.languageName) {
67+
case 'typescript':
68+
parentLanguage = { languageName: CodeWhispererConstants.javascript }
69+
break
70+
case 'jsx':
71+
parentLanguage = { languageName: CodeWhispererConstants.javascript }
72+
break
73+
default:
74+
parentLanguage = childLanguage
75+
break
4476
}
77+
4578
return {
46-
language: languageName as CodewhispererLanguage,
79+
...request,
80+
fileContext: {
81+
...fileContext,
82+
programmingLanguage: parentLanguage,
83+
},
4784
}
4885
}
4986

50-
public convertLanguage(languageId?: string) {
51-
languageId = languageId === CodeWhispererConstants.typescript ? CodeWhispererConstants.javascript : languageId
52-
if (!languageId) {
53-
return 'plaintext'
54-
}
55-
56-
return languageId
87+
/**
88+
*
89+
* @param languageId: either vscodeLanguageId or CodewhispererLanguage
90+
* @returns ture if the language is supported by CodeWhisperer otherwise false
91+
*/
92+
public isLanguageSupported(languageId: string): boolean {
93+
return this.supportedLanguageSet.has(languageId)
5794
}
5895
}
5996

0 commit comments

Comments
 (0)