Skip to content

Commit 5592c10

Browse files
committed
fix: guard isTestoFile against invalid VirtualFile
TestoIconProvider could trigger an "Outdated stub in index" exception when invoked on a stale PsiFile whose underlying physical file no longer existed (e.g. a deleted vendor/autoload.php still cached in the stub index). isTestoFile() walked the AST via PsiTreeUtil before any validity check, forcing AST load and crashing on the stub mismatch. Short-circuit isTestoFile() if the file is not a PhpFile, has no VirtualFile, or its VirtualFile is no longer valid - avoiding any PSI traversal in those cases.
1 parent b320de1 commit 5592c10

2 files changed

Lines changed: 28 additions & 3 deletions

File tree

src/main/kotlin/com/github/xepozz/testo/mixin.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,15 @@ fun PsiElement.isTestoClass() = when (this) {
4343
else -> false
4444
}
4545

46-
fun PsiFile.isTestoFile() = when (this) {
47-
is PhpFile -> TestoTestDescriptor.isTestClassName(name.substringBeforeLast(".")) || isTestoClassFile() || isTestoFunctionFile() || isTestBenchFile() || isTestoConfigFile()
48-
else -> false
46+
fun PsiFile.isTestoFile(): Boolean {
47+
if (this !is PhpFile) return false
48+
val vFile = virtualFile ?: return false
49+
if (!vFile.isValid) return false
50+
return TestoTestDescriptor.isTestClassName(name.substringBeforeLast("."))
51+
|| isTestoClassFile()
52+
|| isTestoFunctionFile()
53+
|| isTestBenchFile()
54+
|| isTestoConfigFile()
4955
}
5056

5157
fun PhpFile.isTestoConfigFile() = PsiTreeUtil.findChildrenOfType(this, ClassReference::class.java)

src/test/kotlin/com/github/xepozz/testo/MixinPsiTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.xepozz.testo
22

3+
import com.intellij.openapi.application.WriteAction
34
import com.intellij.psi.util.PsiTreeUtil
45
import com.intellij.testFramework.TestDataPath
56
import com.intellij.testFramework.fixtures.BasePlatformTestCase
@@ -155,6 +156,24 @@ class MixinPsiTest : BasePlatformTestCase() {
155156
assertTrue("File containing test class should be a Testo file", psiFile.isTestoFile())
156157
}
157158

159+
fun testIsTestoFile_invalidVirtualFile_returnsFalse() {
160+
val psiFile = myFixture.configureByText(
161+
"DeletedTest.php",
162+
"""<?php class DeletedTest { public function testSomething(): void {} }"""
163+
) as PhpFile
164+
165+
assertTrue("Precondition: file should initially be detected as a Testo file", psiFile.isTestoFile())
166+
167+
val vFile = psiFile.virtualFile!!
168+
WriteAction.runAndWait<Throwable> { vFile.delete(this) }
169+
170+
assertFalse("VirtualFile should be invalid after deletion", vFile.isValid)
171+
assertFalse(
172+
"isTestoFile must return false when the underlying VirtualFile is invalid",
173+
psiFile.isTestoFile()
174+
)
175+
}
176+
158177
// ---- isTestoExecutable ----
159178

160179
fun testIsTestoExecutable_testMethod() {

0 commit comments

Comments
 (0)