Skip to content

Commit 186725e

Browse files
committed
Fix VirtualFile nullability in chat module and segregate test files
- BrowserConnector.kt: Fix 2 VirtualFile null checks - LanguageExtractor.kt: Add null check with plaintext fallback - FocusAreaContextExtractor.kt: Add null check with unknown fallback - Segregate CodeInsightsSettingsFacadeTest.kt and CodeWhispererUtilTest.kt (ProjectExtension removed in 2025.3)
1 parent 2f95779 commit 186725e

File tree

7 files changed

+180
-4
lines changed

7 files changed

+180
-4
lines changed

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ class BrowserConnector(
232232
SEND_CHAT_COMMAND_PROMPT -> {
233233
val requestFromUi = serializer.deserializeChatMessages<SendChatPromptRequest>(node)
234234
val editor = FileEditorManager.getInstance(project).selectedTextEditor
235-
val textDocumentIdentifier = editor?.let { TextDocumentIdentifier(toUriString(it.virtualFile)) }
235+
val textDocumentIdentifier = editor?.virtualFile?.let { TextDocumentIdentifier(toUriString(it)) }
236236
val cursorState = editor?.let { LspEditorUtil.getCursorState(it) }
237237

238238
val enrichmentParams = mapOf(
@@ -362,7 +362,7 @@ class BrowserConnector(
362362

363363
CHAT_INSERT_TO_CURSOR -> {
364364
val editor = FileEditorManager.getInstance(project).selectedTextEditor
365-
val textDocumentIdentifier = editor?.let { TextDocumentIdentifier(toUriString(it.virtualFile)) }
365+
val textDocumentIdentifier = editor?.virtualFile?.let { TextDocumentIdentifier(toUriString(it)) }
366366
val cursorPosition = editor?.let { LspEditorUtil.getCursorPosition(it) }
367367

368368
val enrichmentParams = mapOf(

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/file/util/LanguageExtractor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmi
1010
class LanguageExtractor {
1111
fun extractLanguageNameFromCurrentFile(editor: Editor): String =
1212
runReadAction {
13-
editor.virtualFile.programmingLanguage().languageId
13+
editor.virtualFile?.programmingLanguage()?.languageId ?: "plaintext"
1414
}
1515
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/editor/context/focusArea/FocusAreaContextExtractor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class FocusAreaContextExtractor(private val fqnWebviewAdapter: FqnWebviewAdapter
114114
languageExtractor.extractLanguageNameFromCurrentFile(editor)
115115
}
116116
val fileText = editor.document.text
117-
val fileName = editor.virtualFile.name
117+
val fileName = editor.virtualFile?.name ?: "unknown"
118118

119119
// Offset the selection range to the start of the trimmedFileText
120120
val selectionInsideTrimmedFileTextRange = codeSelectionRange.let {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.codewhisperer.util
5+
6+
import com.intellij.codeInsight.CodeInsightSettings
7+
import com.intellij.openapi.Disposable
8+
import com.intellij.openapi.application.ApplicationManager
9+
import com.intellij.openapi.util.Disposer
10+
import com.intellij.testFramework.HeavyPlatformTestCase
11+
import com.intellij.testFramework.replaceService
12+
import org.assertj.core.api.Assertions.assertThat
13+
import org.mockito.kotlin.spy
14+
import org.mockito.kotlin.times
15+
import org.mockito.kotlin.verify
16+
17+
class CodeInsightsSettingsFacadeTest : HeavyPlatformTestCase() {
18+
private lateinit var settings: CodeInsightSettings
19+
private lateinit var sut: CodeInsightsSettingsFacade
20+
21+
override fun setUp() {
22+
super.setUp()
23+
sut = spy(CodeInsightsSettingsFacade())
24+
settings = spy { CodeInsightSettings() }
25+
26+
ApplicationManager.getApplication().replaceService(
27+
CodeInsightSettings::class.java,
28+
settings,
29+
testRootDisposable
30+
)
31+
}
32+
33+
fun testDisableCodeInsightUntilShouldRevertWhenParentIsDisposed() {
34+
@Suppress("ObjectLiteralToLambda") // JUnit 3 doesn't support SAM lambdas
35+
val myFakePopup = object : Disposable { override fun dispose() {} }
36+
Disposer.register(testRootDisposable, myFakePopup)
37+
38+
// assume users' enable the following two codeinsight functionalities
39+
settings.TAB_EXITS_BRACKETS_AND_QUOTES = true
40+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isTrue
41+
settings.AUTOCOMPLETE_ON_CODE_COMPLETION = true
42+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isTrue
43+
44+
// codewhisperer disable them while popup is shown
45+
sut.disableCodeInsightUntil(myFakePopup)
46+
47+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isFalse
48+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isFalse
49+
assertThat(sut.pendingRevertCounts).isEqualTo(2)
50+
51+
// popup is closed and disposed
52+
Disposer.dispose(myFakePopup)
53+
54+
// revert changes made by codewhisperer
55+
verify(sut, times(2)).revertAll()
56+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isTrue
57+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isTrue
58+
}
59+
60+
fun testRevertAllShouldRevertBackAllChangesMadeByCodewhisperer() {
61+
settings.TAB_EXITS_BRACKETS_AND_QUOTES = true
62+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isTrue
63+
settings.AUTOCOMPLETE_ON_CODE_COMPLETION = true
64+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isTrue
65+
66+
sut.disableCodeInsightUntil(testRootDisposable)
67+
68+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isFalse
69+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isFalse
70+
71+
assertThat(sut.pendingRevertCounts).isEqualTo(2)
72+
73+
sut.revertAll()
74+
assertThat(sut.pendingRevertCounts).isEqualTo(0)
75+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isTrue
76+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isTrue
77+
}
78+
79+
fun testDisableCodeInsightUntilShouldAlwaysFlushPendingRevertsBeforeMakingNextChanges() {
80+
@Suppress("ObjectLiteralToLambda") // JUnit 3 doesn't support SAM lambdas
81+
val myFakePopup = object : Disposable { override fun dispose() {} }
82+
Disposer.register(testRootDisposable, myFakePopup)
83+
84+
@Suppress("ObjectLiteralToLambda") // JUnit 3 doesn't support SAM lambdas
85+
val myAnotherFakePopup = object : Disposable { override fun dispose() {} }
86+
Disposer.register(testRootDisposable, myAnotherFakePopup)
87+
88+
// assume users' enable the following two codeinsight functionalities
89+
settings.TAB_EXITS_BRACKETS_AND_QUOTES = true
90+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isTrue
91+
settings.AUTOCOMPLETE_ON_CODE_COMPLETION = true
92+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isTrue
93+
94+
// codewhisperer disable them while popup_1 is shown
95+
sut.disableCodeInsightUntil(myFakePopup)
96+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isFalse
97+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isFalse
98+
assertThat(sut.pendingRevertCounts).isEqualTo(2)
99+
verify(sut, times(1)).revertAll()
100+
101+
// unexpected issue happens and popup_1 is not disposed correctly and popup_2 is created
102+
sut.disableCodeInsightUntil(myAnotherFakePopup)
103+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isFalse
104+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isFalse
105+
// should still be 2 because previous ones should be reverted before preceding next changes
106+
assertThat(sut.pendingRevertCounts).isEqualTo(2)
107+
verify(sut, times(1 + 1)).revertAll()
108+
109+
Disposer.dispose(myAnotherFakePopup)
110+
111+
assertThat(sut.pendingRevertCounts).isEqualTo(0)
112+
verify(sut, times(1 + 1 + 1)).revertAll()
113+
assertThat(settings.TAB_EXITS_BRACKETS_AND_QUOTES).isTrue
114+
assertThat(settings.AUTO_POPUP_COMPLETION_LOOKUP).isTrue
115+
}
116+
117+
fun testDisposeShouldCallRevertAllToRevertAllChangesMadeByCodeWhisperer() {
118+
sut.dispose()
119+
verify(sut).revertAll()
120+
}
121+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.codewhisperer.util
5+
6+
import com.intellij.openapi.application.ApplicationManager
7+
import com.intellij.testFramework.HeavyPlatformTestCase
8+
import com.intellij.testFramework.replaceService
9+
import io.mockk.every
10+
import io.mockk.mockk
11+
import io.mockk.mockkStatic
12+
import io.mockk.verify
13+
import software.aws.toolkits.core.utils.test.aString
14+
import software.aws.toolkits.jetbrains.core.credentials.ManagedBearerSsoConnection
15+
import software.aws.toolkits.jetbrains.core.credentials.ReauthSource
16+
import software.aws.toolkits.jetbrains.core.credentials.ToolkitAuthManager
17+
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
18+
import software.aws.toolkits.jetbrains.core.credentials.reauthConnectionIfNeeded
19+
import software.aws.toolkits.jetbrains.core.region.MockRegionProviderRule
20+
21+
class CodeWhispererUtilTest : HeavyPlatformTestCase() {
22+
private val mockRegionProviderExtension = MockRegionProviderRule()
23+
24+
override fun setUp() {
25+
super.setUp()
26+
mockRegionProviderExtension.apply(
27+
object : org.junit.runners.model.Statement() {
28+
override fun evaluate() {}
29+
},
30+
org.junit.runner.Description.EMPTY
31+
).evaluate()
32+
}
33+
34+
fun testReconnectCodeWhispererRespectsConnectionSettings() {
35+
mockkStatic(::reauthConnectionIfNeeded)
36+
val mockConnectionManager = mockk<ToolkitConnectionManager>(relaxed = true)
37+
val mockConnection = mockk<ManagedBearerSsoConnection>()
38+
project.replaceService(ToolkitConnectionManager::class.java, mockConnectionManager, testRootDisposable)
39+
ApplicationManager.getApplication().replaceService(ToolkitAuthManager::class.java, mockk(relaxed = true), testRootDisposable)
40+
val startUrl = aString()
41+
val region = mockRegionProviderExtension.createAwsRegion().id
42+
val scopes = listOf(aString(), aString())
43+
44+
every { mockConnectionManager.activeConnectionForFeature(any()) } returns mockConnection
45+
every { mockConnection.startUrl } returns startUrl
46+
every { mockConnection.region } returns region
47+
every { mockConnection.scopes } returns scopes
48+
49+
CodeWhispererUtil.reconnectCodeWhisperer(project)
50+
51+
verify {
52+
reauthConnectionIfNeeded(project, mockConnection, isReAuth = true, reauthSource = ReauthSource.CODEWHISPERER)
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)