Skip to content

Commit 422d88b

Browse files
committed
fix: harden isTestoFile against stale stubs and out-of-content files
The original report showed an "Outdated stub in index" exception triggered by isTestoFile() called on a stale PsiFile from vendor/ - where the physical file was already deleted but the VFS hadn't refreshed yet, so virtualFile.isValid still returned true. A bare isValid check is therefore not enough. To make isTestoFile() robust: - Skip files outside project content (ProjectFileIndex.isInContent), excluded folders and IDE-ignored paths - this filters out vendor/ and similar locations before any PSI traversal happens, removing the root cause for the reported case. - Wrap the AST-loading traversal in a try/catch that rethrows ProcessCanceledException and downgrades any other Throwable (including StubTreeAndIndexUnmatchCoarseException) to a logged warning + false, so a transient index inconsistency can no longer propagate as an unhandled exception through callers (live templates, inspection suppressor, run config producer, etc.).
1 parent d2419b7 commit 422d88b

1 file changed

Lines changed: 23 additions & 5 deletions

File tree

  • src/main/kotlin/com/github/xepozz/testo

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

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

33
import com.github.xepozz.testo.tests.TestoTestDescriptor
4+
import com.intellij.openapi.diagnostic.Logger
5+
import com.intellij.openapi.progress.ProcessCanceledException
6+
import com.intellij.openapi.roots.ProjectFileIndex
47
import com.intellij.psi.PsiElement
58
import com.intellij.psi.PsiFile
69
import com.intellij.psi.util.PsiTreeUtil
@@ -12,6 +15,8 @@ import com.jetbrains.php.lang.psi.elements.ClassReference
1215
import com.jetbrains.php.lang.psi.elements.NewExpression
1316
import com.jetbrains.php.lang.psi.elements.PhpClass
1417

18+
private val LOG = Logger.getInstance("#com.github.xepozz.testo.mixin")
19+
1520
fun PsiElement.isTestoExecutable() = isTestoFunction() || isTestoMethod() || isTestoBench()
1621

1722
fun PsiElement.isTestoBench() = when(this) {
@@ -58,11 +63,24 @@ fun PsiFile.isTestoFile(): Boolean {
5863
if (this !is PhpFile) return false
5964
val vFile = virtualFile ?: return false
6065
if (!vFile.isValid) return false
61-
return TestoTestDescriptor.isTestClassName(name.substringBeforeLast("."))
62-
|| isTestoClassFile()
63-
|| isTestoFunctionFile()
64-
|| isTestBenchFile()
65-
|| isTestoConfigFile()
66+
67+
val fileIndex = ProjectFileIndex.getInstance(project)
68+
if (!fileIndex.isInContent(vFile)) return false
69+
if (fileIndex.isExcluded(vFile)) return false
70+
if (fileIndex.isUnderIgnored(vFile)) return false
71+
72+
return try {
73+
TestoTestDescriptor.isTestClassName(name.substringBeforeLast("."))
74+
|| isTestoClassFile()
75+
|| isTestoFunctionFile()
76+
|| isTestBenchFile()
77+
|| isTestoConfigFile()
78+
} catch (e: ProcessCanceledException) {
79+
throw e
80+
} catch (e: Throwable) {
81+
LOG.warn("Failed to determine whether ${vFile.path} is a Testo file", e)
82+
false
83+
}
6684
}
6785

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

0 commit comments

Comments
 (0)