Skip to content

Commit ff34d62

Browse files
committed
feat: better data provider support
1 parent 8101545 commit ff34d62

File tree

5 files changed

+171
-141
lines changed

5 files changed

+171
-141
lines changed

src/main/kotlin/com/github/xepozz/testo/index/TestoDataProviderUtils.kt

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

3-
import com.github.xepozz.testo.isTestoDataProvider
3+
import com.github.xepozz.testo.isTestoDataProviderLike
44
import com.intellij.psi.search.GlobalSearchScopesCore
55
import com.intellij.util.indexing.FileBasedIndex
66
import com.jetbrains.php.PhpIndex
77
import com.jetbrains.php.lang.psi.elements.Function
88
import com.jetbrains.php.lang.psi.elements.Method
99

1010
object TestoDataProviderUtils {
11-
fun isDataProvider(method: Method): Boolean {
12-
if (!method.isTestoDataProvider()) return false
11+
fun isDataProvider(function: Function): Boolean {
12+
if (!function.isTestoDataProviderLike()) return false
1313

1414
return FileBasedIndex.getInstance()
1515
.getValues(
1616
TestoDataProvidersIndex.KEY,
17-
method.name,
18-
GlobalSearchScopesCore.projectTestScope(method.project)
17+
function.name,
18+
GlobalSearchScopesCore.projectTestScope(function.project)
1919
)
2020
.isNotEmpty()
2121
}
2222

2323
fun findDataProviderUsages(method: Function): List<Method> {
24-
if (!method.isTestoDataProvider()) return emptyList()
24+
if (!method.isTestoDataProviderLike()) return emptyList()
2525
val phpIndex = PhpIndex.getInstance(method.project)
2626

2727
return FileBasedIndex.getInstance()

src/main/kotlin/com/github/xepozz/testo/index/TestoDataProvidersIndex.kt

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.github.xepozz.testo.index
22

3+
import com.github.xepozz.testo.isTestoClass
34
import com.intellij.openapi.util.Pair
45
import com.intellij.openapi.util.text.StringUtil
5-
import com.intellij.testIntegration.TestFinderHelper
66
import com.intellij.util.indexing.DataIndexer
77
import com.intellij.util.indexing.FileBasedIndex
88
import com.intellij.util.indexing.FileBasedIndexExtension
@@ -16,6 +16,7 @@ import com.jetbrains.php.lang.psi.elements.Method
1616
import com.jetbrains.php.lang.psi.elements.PhpAttribute
1717
import com.jetbrains.php.lang.psi.stubs.indexes.expectedArguments.PhpExpectedFunctionArgument
1818
import com.jetbrains.php.lang.psi.stubs.indexes.expectedArguments.PhpExpectedFunctionScalarArgument
19+
import com.jetbrains.rd.util.printlnError
1920
import java.io.DataInput
2021
import java.io.DataOutput
2122
import java.io.IOException
@@ -26,18 +27,16 @@ class TestoDataProvidersIndex : FileBasedIndexExtension<String, TestoDataProvide
2627
override fun getName() = KEY
2728

2829
override fun getIndexer() = DataIndexer<String, TestoDataProvidersIndexType, FileContent?> { inputData ->
29-
val map: MutableMap<String, TestoDataProvidersIndexType> = HashMap()
30+
val map = mutableMapOf<String, TestoDataProvidersIndexType>()
3031

3132
for (testClass in PhpPsiUtil.findAllClasses(inputData.psiFile)) {
32-
if (TestFinderHelper.isTest(testClass)) {
33-
for (method in testClass.ownMethods) {
34-
val dataProviders = mutableSetOf<Pair<String, String>>()
35-
dataProviders.addAll(getDataProvidersFromAttributes(method))
36-
37-
for (dataProvider in dataProviders) {
38-
map.computeIfAbsent(dataProvider.second) { mutableSetOf() }
39-
.add(DataProviderUsage(testClass.fqn, method.name, dataProvider.first))
40-
}
33+
if (!testClass.isTestoClass()) continue
34+
for (method in testClass.ownMethods) {
35+
val dataProviders = getDataProvidersFromAttributes(method)
36+
37+
for (dataProvider in dataProviders) {
38+
map.computeIfAbsent(dataProvider.second) { mutableSetOf() }
39+
.add(DataProviderUsage(testClass.fqn, method.name, dataProvider.first))
4140
}
4241
}
4342
}
@@ -108,14 +107,42 @@ class TestoDataProvidersIndex : FileBasedIndexExtension<String, TestoDataProvide
108107
private fun getDataProvidersFromAttributes(method: Method): MutableSet<Pair<String, String>> {
109108
val result = mutableSetOf<Pair<String, String>>()
110109

110+
val targetFQN = method.containingClass?.fqn ?: method.fqn
111+
111112
for (dataProvider in method.getAttributes(PHPUNIT_DATA_PROVIDER_ATTRIBUTE)) {
112113
val argument = getAttributeArgument(dataProvider, "provider", 0) ?: continue
113114
val methodNameArg = argument as? PhpExpectedFunctionScalarArgument ?: continue
114-
val containingClass = method.containingClass ?: continue
115-
if (methodNameArg.isStringLiteral) {
116-
result.add(
117-
Pair.create(containingClass.fqn, StringUtil.unquoteString(methodNameArg.value))
115+
val attributeValue = methodNameArg.value
116+
117+
when {
118+
methodNameArg.isStringLiteral -> result.add(
119+
Pair.create(targetFQN, StringUtil.unquoteString(attributeValue))
118120
)
121+
122+
attributeValue.startsWith("[") && attributeValue.endsWith("]") -> {
123+
// todo: replace with PSI creation
124+
val classMethodPair = attributeValue
125+
.substring(1, attributeValue.length - 1)
126+
.split(",")
127+
.map { it.trim() }
128+
if (classMethodPair.size != 2) continue
129+
130+
val classFQN = when {
131+
classMethodPair.first() in arrayOf("self::class", "static::class") -> targetFQN
132+
else -> classMethodPair.first()
133+
}
134+
135+
result.add(
136+
Pair.create(classFQN, StringUtil.unquoteString(classMethodPair.last()))
137+
)
138+
}
139+
140+
else -> {
141+
printlnError("Unknown data provider type: $attributeValue")
142+
// result.add(
143+
// Pair.create(targetFQN, attributeValue)
144+
// )
145+
}
119146
}
120147
}
121148

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,26 @@ import com.jetbrains.php.lang.psi.elements.PhpClass
1212

1313
fun PsiElement.isTestoExecutable() = isTestoFunction() || isTestoMethod()
1414

15-
fun PsiElement.isTestoFunction() = when {
16-
this is Function -> hasAttribute(TestoClasses.TEST)
15+
fun PsiElement.isTestoFunction() = when(this) {
16+
is Function -> hasAttribute(TestoClasses.TEST)
1717
else -> false
1818
}
1919

20-
fun PsiElement.isTestoMethod() = when {
21-
this is Method -> modifier.isPublic && name.startsWith("test") || hasAttribute(TestoClasses.TEST)
20+
fun PsiElement.isTestoMethod() = when(this) {
21+
is Method -> modifier.isPublic && name.startsWith("test") || hasAttribute(TestoClasses.TEST)
2222
else -> false
2323
}
2424

25-
fun PsiElement.isTestoDataProvider() = when {
26-
this is Method -> modifier.isPublic && modifier.isStatic
25+
fun PsiElement.isTestoDataProviderLike() = when (this) {
26+
is Method -> modifier.isPublic && modifier.isStatic
27+
is Function -> true
2728
else -> false
2829
}
2930

3031
fun PhpAttributesOwner.hasAttribute(fqn: String) = getAttributes(fqn).isNotEmpty()
3132

3233
fun PsiElement.isTestoClass() = when (this) {
33-
is PhpClass -> TestoTestDescriptor.isTestClassName(name) || methods.any { it.isTestoMethod() }
34+
is PhpClass -> TestoTestDescriptor.isTestClassName(name) || ownMethods.any { it.isTestoMethod() }
3435
else -> false
3536
}
3637

src/main/kotlin/com/github/xepozz/testo/tests/TestoTestRunLineMarkerProvider.kt

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.github.xepozz.testo.tests
33
import com.github.xepozz.testo.index.TestoDataProviderUtils
44
import com.github.xepozz.testo.isTestoClass
55
import com.github.xepozz.testo.isTestoExecutable
6-
import com.github.xepozz.testo.tests.actions.TestoRunCommandAction
76
import com.intellij.execution.lineMarker.RunLineMarkerContributor
87
import com.intellij.openapi.project.Project
98
import com.intellij.psi.PsiElement
@@ -34,7 +33,7 @@ class TestoTestRunLineMarkerProvider : RunLineMarkerContributor() {
3433
getTestStateIcon(getLocationHint(element), element.project, false),
3534
)
3635

37-
element is Method && TestoDataProviderUtils.isDataProvider(element) -> withExecutorActions(
36+
element is Function && TestoDataProviderUtils.isDataProvider(element) -> withExecutorActions(
3837
getTestStateIcon(getDataProviderLocationHint(element), element.project, false),
3938
)
4039

@@ -44,23 +43,14 @@ class TestoTestRunLineMarkerProvider : RunLineMarkerContributor() {
4443
}
4544

4645
companion object Companion {
47-
val RUN_TEST_TOOLTIP_PROVIDER = { it: PsiElement -> "Run Testo" }
48-
49-
private fun getInfo(url: String, project: Project, isClass: Boolean) =
50-
Info(
51-
getTestStateIcon(url, project, isClass),
52-
arrayOf(TestoRunCommandAction("")),
53-
RUN_TEST_TOOLTIP_PROVIDER
54-
)
55-
5646
fun getLocationHint(element: Function) = when (element) {
5747
is Method -> getLocationHint(element.containingClass!!) + "::" + element.name
5848
else -> getLocationHint(element.containingFile) + "::" + element.fqn
5949
}
6050

6151
fun getLocationHint(element: PhpClass) = getLocationHint(element.containingFile) + "::" + element.fqn
6252
fun getLocationHint(file: PsiFile) = "${TestoFrameworkType.SCHEMA}://" + getFilePathDeploymentAware(file)
63-
fun getDataProviderLocationHint(method: Method) = getLocationHint(method) + "::" + method.name
53+
fun getDataProviderLocationHint(function: Function) = getLocationHint(function) + "::" + function.name
6454

6555
fun getFilePathDeploymentAware(psiFile: PsiFile): String {
6656
val localPath = psiFile.virtualFile.path

0 commit comments

Comments
 (0)