Skip to content

Commit 6363651

Browse files
committed
provide a line marker to navigate to a related test classes, based on the naming Haehnchen#16
1 parent 6efec71 commit 6363651

File tree

6 files changed

+177
-1
lines changed

6 files changed

+177
-1
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package de.espend.idea.php.phpunit.linemarker;
2+
3+
import com.intellij.codeInsight.daemon.LineMarkerInfo;
4+
import com.intellij.codeInsight.daemon.LineMarkerProvider;
5+
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder;
6+
import com.intellij.psi.PsiElement;
7+
import com.jetbrains.php.PhpIcons;
8+
import com.jetbrains.php.PhpIndex;
9+
import com.jetbrains.php.lang.psi.elements.PhpClass;
10+
import com.jetbrains.php.phpunit.PhpUnitUtil;
11+
import de.espend.idea.php.phpunit.utils.PhpElementsUtil;
12+
import org.apache.commons.lang.StringUtils;
13+
import org.jetbrains.annotations.NotNull;
14+
import org.jetbrains.annotations.Nullable;
15+
16+
import java.util.*;
17+
18+
/**
19+
* Attach a test class to a given class name
20+
*
21+
* @author Daniel Espendiller <daniel@espendiller.net>
22+
*/
23+
public class RelatedTestCaseLineMarkerProvider implements LineMarkerProvider {
24+
@Nullable
25+
@Override
26+
public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
27+
return null;
28+
}
29+
30+
@Override
31+
public void collectSlowLineMarkers(@NotNull List<PsiElement> psiElements, @NotNull Collection<LineMarkerInfo> result) {
32+
// we need project element; so get it from first item
33+
if(psiElements.size() == 0) {
34+
return;
35+
}
36+
37+
for(PsiElement psiElement: psiElements) {
38+
if (PhpElementsUtil.getClassNamePattern().accepts(psiElement)) {
39+
result.addAll(visitClassName(psiElement));
40+
}
41+
}
42+
}
43+
44+
private Collection<LineMarkerInfo> visitClassName(@NotNull PsiElement psiElement) {
45+
PsiElement phpClass = psiElement.getContext();
46+
if (!(phpClass instanceof PhpClass) || PhpUnitUtil.isTestClass((PhpClass) phpClass)) {
47+
return Collections.emptyList();
48+
}
49+
50+
String className = StringUtils.stripStart(((PhpClass) phpClass).getPresentableFQN(), "\\");
51+
52+
Collection<String> testClasses = getTestClassesViaPath(className);
53+
if (testClasses.size() == 0) {
54+
return Collections.emptyList();
55+
}
56+
57+
PhpIndex instance = PhpIndex.getInstance(psiElement.getProject());
58+
59+
Set<PhpClass> phpClasses = new HashSet<>();
60+
for (String testClass : testClasses) {
61+
Collection<PhpClass> anyByFQN = instance.getAnyByFQN("\\" + testClass);
62+
for (PhpClass aClass : anyByFQN) {
63+
if (PhpUnitUtil.isTestClass(aClass)) {
64+
phpClasses.add(aClass);
65+
}
66+
}
67+
}
68+
69+
if (phpClasses.size() == 0) {
70+
return Collections.emptyList();
71+
}
72+
73+
NavigationGutterIconBuilder<PsiElement> builder = NavigationGutterIconBuilder.create(PhpIcons.PHP_TEST_CLASS)
74+
.setTargets(phpClasses)
75+
.setTooltipText("Navigate to Test Class");
76+
77+
return Collections.singletonList(builder.createLineMarkerInfo(psiElement));
78+
}
79+
80+
/**
81+
* Try you find a test class which based on the class namespace
82+
*
83+
* Extend class name with a test namespace in between:
84+
* - App\Foobar => App\FoobarTest
85+
* - App\Foobar => App\Test\FoobarTest
86+
* - App\Foobar => App\Tests\FoobarTest
87+
* - App\Foobar => Test\App\FoobarTest
88+
*/
89+
@NotNull
90+
private static Collection<String> getTestClassesViaPath(@NotNull String className) {
91+
List<String> classNameParts = Arrays.asList(className.split("\\\\"));
92+
93+
classNameParts.set(classNameParts.size() - 1, classNameParts.get(classNameParts.size() - 1) + "Test");
94+
95+
Collection<String> testClasses = new HashSet<>();
96+
testClasses.add(className + "Test");
97+
for (int i = 0; i < classNameParts.size(); i++) {
98+
List<String> strings = new ArrayList<>(classNameParts);
99+
strings.add(i, "Test");
100+
testClasses.add(StringUtils.join(strings, "\\"));
101+
102+
List<String> strings2 = new ArrayList<>(classNameParts);
103+
strings2.add(i, "Tests");
104+
testClasses.add(StringUtils.join(strings2, "\\"));
105+
}
106+
107+
return testClasses;
108+
}
109+
}

src/main/java/de/espend/idea/php/phpunit/utils/PhpElementsUtil.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package de.espend.idea.php.phpunit.utils;
22

3+
import com.intellij.patterns.PlatformPatterns;
4+
import com.intellij.patterns.PsiElementPattern;
35
import com.intellij.psi.PsiElement;
46
import com.intellij.psi.PsiReference;
7+
import com.intellij.psi.PsiWhiteSpace;
58
import com.intellij.psi.ResolveResult;
69
import com.jetbrains.php.PhpIndex;
710
import com.jetbrains.php.codeInsight.PhpCodeInsightUtil;
11+
import com.jetbrains.php.lang.PhpLanguage;
12+
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
813
import com.jetbrains.php.lang.psi.elements.*;
914
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
1015
import com.jetbrains.php.refactoring.PhpAliasImporter;
@@ -181,4 +186,18 @@ public static String insertUseIfNecessary(@NotNull PsiElement scope, @NotNull St
181186

182187
return null;
183188
}
189+
190+
/**
191+
* class "Foo" extends
192+
*/
193+
static public PsiElementPattern.Capture<PsiElement> getClassNamePattern() {
194+
return PlatformPatterns
195+
.psiElement(PhpTokenTypes.IDENTIFIER)
196+
.afterLeafSkipping(
197+
PlatformPatterns.psiElement(PsiWhiteSpace.class),
198+
PlatformPatterns.psiElement(PhpTokenTypes.kwCLASS)
199+
)
200+
.withParent(PhpClass.class)
201+
.withLanguage(PhpLanguage.INSTANCE);
202+
}
184203
}

src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565

6666
<psi.referenceContributor language="PHP" implementation="com.phpuaca.reference.StringReferenceContributor"/>
6767

68+
<codeInsight.lineMarkerProvider language="PHP" implementationClass="de.espend.idea.php.phpunit.linemarker.RelatedTestCaseLineMarkerProvider"/>
69+
6870
<intentionAction>
6971
<className>de.espend.idea.php.phpunit.intention.TestRunIntentionAction</className>
7072
<category>PHPUnit</category>

src/test/java/de/espend/idea/php/phpunit/tests/PhpUnitLightCodeInsightFixtureTestCase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void assertLineMarker(@NotNull PsiElement psiElement, @NotNull LineMarker
5252

5353
final List<PsiElement> elements = collectPsiElementsRecursive(psiElement);
5454

55-
for (LineMarkerProvider lineMarkerProvider : LineMarkerProviders.INSTANCE.allForLanguage(psiElement.getLanguage())) {
55+
for (LineMarkerProvider lineMarkerProvider : LineMarkerProviders.getInstance().allForLanguage(psiElement.getLanguage())) {
5656
Collection<LineMarkerInfo> lineMarkerInfos = new ArrayList<LineMarkerInfo>();
5757
lineMarkerProvider.collectSlowLineMarkers(elements, lineMarkerInfos);
5858

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package de.espend.idea.php.phpunit.tests.linemarker;
2+
3+
import com.intellij.psi.PsiFile;
4+
import com.jetbrains.php.lang.psi.PhpPsiElementFactory;
5+
import de.espend.idea.php.phpunit.tests.PhpUnitLightCodeInsightFixtureTestCase;
6+
7+
/**
8+
* @author Daniel Espendiller <daniel@espendiller.net>
9+
* @see de.espend.idea.php.phpunit.linemarker.RelatedTestCaseLineMarkerProvider
10+
*/
11+
public class RelatedTestCaseLineMarkerProviderTest extends PhpUnitLightCodeInsightFixtureTestCase {
12+
public void setUp() throws Exception {
13+
super.setUp();
14+
myFixture.copyFileToProject("RelatedTestCaseLineMarkerProvider.php");
15+
}
16+
17+
public String getTestDataPath() {
18+
return "src/test/java/de/espend/idea/php/phpunit/tests/linemarker/fixtures";
19+
}
20+
21+
public void testThatClassNameProvidesALineMarkerToItsTestCase() {
22+
PsiFile psiFileFromText = PhpPsiElementFactory.createPsiFileFromText(getProject(), "<?php\n" +
23+
"namespace Foo\\Bar\\Car;\n" +
24+
"class Foobar {}\n"
25+
);
26+
27+
assertLineMarker(psiFileFromText, new LineMarker.ToolTipEqualsAssert("Navigate to Test Class"));
28+
}
29+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Foo\Test\Bar\Car
4+
{
5+
use PHPUnit\Framework\TestCase;
6+
7+
class FoobarTest extends TestCase
8+
{
9+
}
10+
}
11+
12+
namespace PHPUnit\Framework
13+
{
14+
abstract class TestCase
15+
{
16+
}
17+
}

0 commit comments

Comments
 (0)