Skip to content

Commit bb040ec

Browse files
committed
add support for "createPartialMock" method references
1 parent f28ef92 commit bb040ec

File tree

7 files changed

+244
-8
lines changed

7 files changed

+244
-8
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ class Foo extends \PHPUnit\Framework\TestCase
6161
}
6262
```
6363

64+
```php
65+
class Foo extends \PHPUnit\Framework\TestCase
66+
{
67+
public function testFoo()
68+
{
69+
$bar = $this->createPartialMock(\\Foo\Bar::class, ['<caret>']);
70+
}
71+
}
72+
```
73+
6474
```php
6575
class Foo extends \PHPUnit\Framework\TestCase
6676
{

src/main/java/de/espend/idea/php/phpunit/completion/PhpUnitCompletionContributor.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
*/
2222
public class PhpUnitCompletionContributor extends CompletionContributor {
2323
public PhpUnitCompletionContributor() {
24-
extend(CompletionType.BASIC, PatternUtil.getMethodReferenceWithParameterInsideTokenStringPattern(), new CompletionProvider<CompletionParameters>() {
24+
extend(CompletionType.BASIC, PatternUtil.getMethodReferenceWithParameterInsideTokenStringPattern(), new CompletionProvider<>() {
2525
@Override
2626
protected void addCompletions(@NotNull CompletionParameters completionParameters, @NotNull ProcessingContext processingContext, @NotNull CompletionResultSet resultSet) {
2727
PsiElement psiElement = completionParameters.getPosition();
@@ -30,13 +30,7 @@ protected void addCompletions(@NotNull CompletionParameters completionParameters
3030
if (parent instanceof StringLiteralExpression) {
3131
String parameter = PhpUnitPluginUtil.findCreateMockParameterOnParameterScope((StringLiteralExpression) parent);
3232
if (parameter != null) {
33-
for (PhpClass phpClass : PhpIndex.getInstance(psiElement.getProject()).getAnyByFQN(parameter)) {
34-
resultSet.addAllElements(phpClass.getMethods().stream()
35-
.filter(method -> !method.getAccess().isPublic() || !method.getName().startsWith("__"))
36-
.map((Function<Method, LookupElement>) PhpLookupElement::new)
37-
.collect(Collectors.toSet())
38-
);
39-
}
33+
resultSet.addAllElements(PhpUnitPluginUtil.getMockableMethods(psiElement.getProject(), parameter));
4034
}
4135
}
4236
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package de.espend.idea.php.phpunit.reference;
2+
3+
import com.intellij.codeInsight.completion.*;
4+
import com.intellij.patterns.PlatformPatterns;
5+
import com.intellij.patterns.PsiElementPattern.Capture;
6+
import com.intellij.psi.*;
7+
import com.intellij.psi.util.PsiTreeUtil;
8+
import com.intellij.util.ProcessingContext;
9+
import com.jetbrains.php.lang.psi.elements.*;
10+
import de.espend.idea.php.phpunit.utils.PhpElementsUtil;
11+
import de.espend.idea.php.phpunit.utils.PhpUnitPluginUtil;
12+
import org.apache.commons.lang.StringUtils;
13+
import org.jetbrains.annotations.NotNull;
14+
15+
/**
16+
* "$this->createPartialMock(Foo::class, ['foobar']);"
17+
*
18+
* @author Daniel Espendiller <daniel@espendiller.net>
19+
*/
20+
public class PhpUnitCreatePartialMock {
21+
public static class Completion extends CompletionContributor {
22+
public Completion() {
23+
extend(CompletionType.BASIC, PlatformPatterns.psiElement().withParent(getArrayParameterPattern()), new CompletionProvider<>() {
24+
@Override
25+
protected void addCompletions(@NotNull CompletionParameters completionParameters, @NotNull ProcessingContext processingContext, @NotNull CompletionResultSet resultSet) {
26+
PsiElement psiElement1 = completionParameters.getPosition();
27+
28+
PsiElement psiElement = psiElement1.getParent();
29+
if (!(psiElement instanceof StringLiteralExpression)) {
30+
return;
31+
}
32+
33+
MethodReference parentOfType = PsiTreeUtil.getParentOfType(psiElement, MethodReference.class);
34+
if (parentOfType == null) {
35+
return;
36+
}
37+
38+
if(PhpElementsUtil.isMethodReferenceInstanceOf(parentOfType, "\\PHPUnit\\Framework\\TestCase", "createPartialMock") ||
39+
PhpElementsUtil.isMethodReferenceInstanceOf(parentOfType, "PHPUnit_Framework_TestCase", "createPartialMock")
40+
) {
41+
PsiElement originalClassName = parentOfType.getParameter("originalClassName", 0);
42+
43+
if (originalClassName == null) {
44+
return;
45+
}
46+
47+
String stringValue = PhpElementsUtil.getStringValue(originalClassName);
48+
if (StringUtils.isBlank(stringValue)) {
49+
return;
50+
}
51+
52+
resultSet.addAllElements(PhpUnitPluginUtil.getMockableMethods(psiElement.getProject(), stringValue));
53+
}
54+
}
55+
});
56+
}
57+
}
58+
59+
public static class ReferenceContributor extends PsiReferenceContributor {
60+
@Override
61+
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferenceRegistrar) {
62+
psiReferenceRegistrar.registerReferenceProvider(getArrayParameterPattern(),
63+
new PsiReferenceProvider() {
64+
@NotNull
65+
@Override
66+
public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
67+
if (!(psiElement instanceof StringLiteralExpression)) {
68+
return new PsiReference[0];
69+
}
70+
71+
String contents = ((StringLiteralExpression) psiElement).getContents();
72+
if (contents.isBlank()) {
73+
return new PsiReference[0];
74+
}
75+
76+
MethodReference parentOfType = PsiTreeUtil.getParentOfType(psiElement, MethodReference.class);
77+
if (parentOfType == null) {
78+
return new PsiReference[0];
79+
}
80+
81+
if(PhpElementsUtil.isMethodReferenceInstanceOf(parentOfType, "\\PHPUnit\\Framework\\TestCase", "createPartialMock") ||
82+
PhpElementsUtil.isMethodReferenceInstanceOf(parentOfType, "PHPUnit_Framework_TestCase", "createPartialMock")
83+
) {
84+
PsiElement originalClassName = parentOfType.getParameter("originalClassName", 0);
85+
86+
if (originalClassName == null) {
87+
return new PsiReference[0];
88+
}
89+
90+
String stringValue = PhpElementsUtil.getStringValue(originalClassName);
91+
if (StringUtils.isBlank(stringValue)) {
92+
return new PsiReference[0];
93+
}
94+
95+
return new PsiReference[] {
96+
new PhpClassMethodReference((StringLiteralExpression) psiElement, contents, stringValue)
97+
};
98+
}
99+
100+
return new PsiReference[0];
101+
}
102+
}
103+
);
104+
}
105+
}
106+
107+
private static @NotNull Capture<StringLiteralExpression> getArrayParameterPattern() {
108+
return PlatformPatterns.psiElement(StringLiteralExpression.class)
109+
.withParent(PlatformPatterns.psiElement(PhpPsiElement.class)
110+
.withParent(PlatformPatterns.psiElement(ArrayCreationExpression.class)
111+
.withParent(PlatformPatterns.psiElement(ParameterList.class)
112+
.withParent(MethodReference.class))));
113+
114+
}
115+
}

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

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

3+
import com.intellij.codeInsight.completion.CompletionResultSet;
4+
import com.intellij.codeInsight.lookup.LookupElement;
35
import com.intellij.execution.ProgramRunnerUtil;
46
import com.intellij.execution.actions.ConfigurationContext;
57
import com.intellij.execution.actions.ConfigurationFromContext;
68
import com.intellij.execution.actions.RunConfigurationProducer;
79
import com.intellij.execution.executors.DefaultDebugExecutor;
810
import com.intellij.openapi.editor.Document;
11+
import com.intellij.openapi.project.Project;
912
import com.intellij.openapi.roots.ProjectRootManager;
1013
import com.intellij.openapi.vfs.VfsUtil;
1114
import com.intellij.openapi.vfs.VirtualFile;
@@ -14,6 +17,8 @@
1417
import com.intellij.psi.PsiFile;
1518
import com.intellij.psi.codeStyle.CodeStyleManager;
1619
import com.intellij.psi.util.PsiTreeUtil;
20+
import com.jetbrains.php.PhpIndex;
21+
import com.jetbrains.php.completion.PhpLookupElement;
1722
import com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes;
1823
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
1924
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
@@ -26,6 +31,10 @@
2631
import org.jetbrains.annotations.NotNull;
2732
import org.jetbrains.annotations.Nullable;
2833

34+
import java.util.ArrayList;
35+
import java.util.Collection;
36+
import java.util.stream.Collectors;
37+
2938
/**
3039
* @author Daniel Espendiller <daniel@espendiller.net>
3140
*/
@@ -163,4 +172,19 @@ public static void insertExpectedException(@NotNull Document document, @NotNull
163172

164173
addScope.getParent().addAfter(statement, addScope);
165174
}
175+
176+
public static Collection<LookupElement> getMockableMethods(@NotNull Project project, @NotNull String parameter) {
177+
Collection<LookupElement> elements = new ArrayList<>();
178+
179+
for (PhpClass phpClass : PhpIndex.getInstance(project).getAnyByFQN(parameter)) {
180+
elements.addAll(phpClass.getMethods().stream()
181+
.filter(method -> !method.getAccess().isPublic() || !method.getName().startsWith("__"))
182+
.map((java.util.function.Function<Method, LookupElement>) PhpLookupElement::new)
183+
.collect(Collectors.toSet())
184+
);
185+
}
186+
187+
return elements;
188+
}
189+
166190
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@
8282

8383
<psi.referenceContributor language="PHP" implementation="com.phpuaca.reference.StringReferenceContributor"/>
8484

85+
<psi.referenceContributor language="PHP" implementation="de.espend.idea.php.phpunit.reference.PhpUnitCreatePartialMock$ReferenceContributor"/>
86+
<completion.contributor language="PHP" implementationClass="de.espend.idea.php.phpunit.reference.PhpUnitCreatePartialMock$Completion"/>
87+
8588
<codeInsight.lineMarkerProvider language="PHP" implementationClass="de.espend.idea.php.phpunit.linemarker.RelatedTestCaseLineMarkerProvider"/>
8689

8790
<errorHandler implementation="de.espend.idea.php.phpunit.ide.PluginErrorReporterSubmitter"/>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package de.espend.idea.php.phpunit.references;
2+
3+
import com.intellij.patterns.PlatformPatterns;
4+
import com.jetbrains.php.lang.PhpFileType;
5+
import com.jetbrains.php.lang.psi.elements.Method;
6+
import de.espend.idea.php.phpunit.PhpUnitLightCodeInsightFixtureTestCase;
7+
8+
/**
9+
* @author Daniel Espendiller <daniel@espendiller.net>
10+
* @see de.espend.idea.php.phpunit.reference.PhpUnitCreatePartialMock
11+
*/
12+
public class PhpUnitCreatePartialMockTest extends PhpUnitLightCodeInsightFixtureTestCase {
13+
public void setUp() throws Exception {
14+
super.setUp();
15+
myFixture.copyFileToProject("PhpUnitCreatePartialMock.php");
16+
}
17+
18+
public String getTestDataPath() {
19+
return "src/test/java/de/espend/idea/php/phpunit/references/fixtures";
20+
}
21+
22+
public void testThatReferencesForClassMethodAreProvided() {
23+
assertReferencesMatch(PhpFileType.INSTANCE, "<?php\n" +
24+
"class Foo extends \\PHPUnit\\Framework\\TestCase\n" +
25+
"{\n" +
26+
" public function foobar()\n" +
27+
" {\n" +
28+
" $this->createPartialMock(\\Foo\\Bar::class, ['getFoo<caret>bar']);\n" +
29+
" }\n" +
30+
"}",
31+
PlatformPatterns.psiElement(Method.class).withName("getFoobar")
32+
);
33+
34+
assertReferencesMatch(PhpFileType.INSTANCE, "<?php\n" +
35+
"class Foo extends \\PHPUnit\\Framework\\TestCase\n" +
36+
"{\n" +
37+
" public function foobar()\n" +
38+
" {\n" +
39+
" $this->createPartialMock('Foo\\Bar', ['getFoo<caret>bar']);\n" +
40+
" }\n" +
41+
"}",
42+
PlatformPatterns.psiElement(Method.class).withName("getFoobar")
43+
);
44+
}
45+
46+
public void testThatChainingCreateMockProvidesMethodCompletion() {
47+
assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +
48+
"class Foo extends \\PHPUnit\\Framework\\TestCase\n" +
49+
"{\n" +
50+
" public function foobar()\n" +
51+
" {\n" +
52+
" $this->createPartialMock(\\Foo\\Bar::class, ['<caret>']);\n" +
53+
" }\n" +
54+
"}",
55+
"getFoobar"
56+
);
57+
58+
assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +
59+
"class Foo extends \\PHPUnit\\Framework\\TestCase\n" +
60+
"{\n" +
61+
" public function foobar()\n" +
62+
" {\n" +
63+
" $this->createPartialMock('Foo\\Bar', ['<caret>']);\n" +
64+
" }\n" +
65+
"}",
66+
"getFoobar"
67+
);
68+
}
69+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Foo
4+
{
5+
class Bar
6+
{
7+
public function getFoobar()
8+
{
9+
}
10+
}
11+
}
12+
13+
namespace PHPUnit\Framework
14+
{
15+
abstract class TestCase
16+
{
17+
protected function createPartialMock(string $originalClassName, array $methods): MockObject
18+
{
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)