Skip to content

Commit

Permalink
Add Prophecy reveal type provider #9 and optimize prophesize class fi…
Browse files Browse the repository at this point in the history
…lter #8
  • Loading branch information
Haehnchen committed May 2, 2017
1 parent d4219cb commit 6e51b20
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 17 deletions.
1 change: 1 addition & 0 deletions META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<extensions defaultExtensionNs="com.jetbrains.php">
<typeProvider3 implementation="de.espend.idea.php.phpunit.type.MockProphecyTypeProvider"/>
<typeProvider3 implementation="de.espend.idea.php.phpunit.type.ProphecyTypeProvider"/>
<typeProvider3 implementation="de.espend.idea.php.phpunit.type.RevealProphecyTypeProvider"/>
</extensions>

<extensions defaultExtensionNs="com.intellij">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
import java.util.*;

public class MockProphecyTypeProvider implements PhpTypeProvider3 {
static final char CHAR = '元';
static char TRIM_KEY = '\u0192';
public static final char CHAR = '元';
public static char TRIM_KEY = '\u0192';

private static Collection<String> PHPUNIT_CLASSES = new HashSet<String>() {{
add("\\PHPUnit\\Framework\\TestCase");
Expand Down
39 changes: 26 additions & 13 deletions src/de/espend/idea/php/phpunit/type/ProphecyTypeProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.lang.psi.PhpPsiUtil;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider3;
import de.espend.idea.php.phpunit.type.utils.PhpTypeProviderUtil;
import de.espend.idea.php.phpunit.type.utils.ProphecyTypeUtil;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
Expand All @@ -17,6 +20,8 @@
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class ProphecyTypeProvider implements PhpTypeProvider3 {
private static char TRIM_KEY = '\u1536';

@Override
public char getKey() {
return '\u1530';
Expand All @@ -34,18 +39,9 @@ public PhpType getType(PsiElement element) {

// filter phpunit test methods
if(containingClass != null && containingClass.getName().endsWith("Test")) {
boolean isProphesize = classReference.getType().getTypes().stream().anyMatch(s -> {
boolean b = s.startsWith("#" + MockProphecyTypeProvider.CHAR);
if (!b) {
return false;
}

int i = s.indexOf(MockProphecyTypeProvider.TRIM_KEY);
return i >= 0 && s.substring(2, i).endsWith("prophesize");
});

if(isProphesize) {
return (new PhpType()).add("\\Prophecy\\Prophecy\\MethodProphecy");
String prophesize = ProphecyTypeUtil.getProphesizeSignatureFromTypes(classReference.getType().getTypes());
if(prophesize != null) {
return new PhpType().add("#" + this.getKey() + prophesize + TRIM_KEY + ((MethodReference) element).getName());
}
}
}
Expand All @@ -57,6 +53,23 @@ public PhpType getType(PsiElement element) {

@Override
public Collection<? extends PhpNamedElement> getBySignature(String expression, Set<String> visited, int depth, Project project) {
return null;
// SIGNATURE.METHOD_NAME
String[] split = expression.split(String.valueOf(TRIM_KEY));
if(split.length != 2) {
return null;
}

PhpIndex phpIndex = PhpIndex.getInstance(project);

String resolvedParameter = PhpTypeProviderUtil.getResolvedParameter(phpIndex, split[0]);
if(resolvedParameter == null) {
return null;
}

if(phpIndex.getAnyByFQN(resolvedParameter).stream().noneMatch(phpClass -> phpClass.findMethodByName(split[1]) != null)) {
return null;
}

return phpIndex.getAnyByFQN("\\Prophecy\\Prophecy\\MethodProphecy");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package de.espend.idea.php.phpunit.type;

import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.lang.psi.PhpPsiUtil;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider3;
import de.espend.idea.php.phpunit.type.utils.PhpTypeProviderUtil;
import de.espend.idea.php.phpunit.type.utils.ProphecyTypeUtil;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Set;

/**
* $foobar = $this->prophesize(Foobar::class);
* $foobar->reveal();
*
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class RevealProphecyTypeProvider implements PhpTypeProvider3 {
@Override
public char getKey() {
return '\u1537';
}

@Nullable
@Override
public PhpType getType(PsiElement element) {
if(element instanceof MethodReference && "reveal".equals(((MethodReference) element).getName())) {
PhpExpression classReference = ((MethodReference) element).getClassReference();
if(classReference instanceof Variable || classReference instanceof MethodReference) {
Method method = PhpPsiUtil.getParentByCondition(element, Method.INSTANCEOF);
if(method != null) {
PhpClass containingClass = method.getContainingClass();

// filter phpunit test methods
if(containingClass != null && containingClass.getName().endsWith("Test")) {
String prophesize = ProphecyTypeUtil.getProphesizeSignatureFromTypes(classReference.getType().getTypes());
if(prophesize != null) {
return new PhpType().add("#" + this.getKey() + prophesize);
}
}
}
}
}

return null;
}

@Override
public Collection<? extends PhpNamedElement> getBySignature(String expression, Set<String> visited, int depth, Project project) {
PhpIndex phpIndex = PhpIndex.getInstance(project);

String resolvedParameter = PhpTypeProviderUtil.getResolvedParameter(phpIndex, expression);
if(resolvedParameter == null) {
return null;
}

return phpIndex.getAnyByFQN(resolvedParameter);
}
}
29 changes: 29 additions & 0 deletions src/de/espend/idea/php/phpunit/type/utils/ProphecyTypeUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package de.espend.idea.php.phpunit.type.utils;

import de.espend.idea.php.phpunit.type.MockProphecyTypeProvider;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.function.Function;

/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class ProphecyTypeUtil {
/**
* #元#M#C\FooTest.prophesizeƒ#K#C\Foo.class => #K#C\Foo.class
*/
public static String getProphesizeSignatureFromTypes(@NotNull Collection<String> types) {
return types.stream().filter(s -> {
boolean b = s.startsWith("#" + MockProphecyTypeProvider.CHAR);
if (!b) {
return false;
}

int i = s.indexOf(MockProphecyTypeProvider.TRIM_KEY);
return i >= 0 && s.substring(2, i).endsWith("prophesize");
}).findFirst().map(s ->
s.substring(s.indexOf(MockProphecyTypeProvider.TRIM_KEY) + 1)
).orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ public void assertPhpReferenceResolveTo(LanguageFileType languageFileType, Strin
assertTrue(pattern.accepts(resolve));
}

public void assertPhpReferenceNotResolveTo(LanguageFileType languageFileType, String configureByText, ElementPattern<?> pattern) {
myFixture.configureByText(languageFileType, configureByText);
PsiElement psiElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset());

psiElement = PsiTreeUtil.getParentOfType(psiElement, PhpReference.class);
if (psiElement == null) {
fail("Element is not PhpReference.");
}

assertFalse(pattern.accepts(((PhpReference) psiElement).resolve()));
}

@NotNull
private List<PsiElement> collectPsiElementsRecursive(@NotNull PsiElement psiElement) {
final List<PsiElement> elements = new ArrayList<PsiElement>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,13 @@ public void testResolveForProphecyMock() {
PlatformPatterns.psiElement(Method.class).withName("bar")
);
}

public void testResolveForProphecyMockWithStringClass() {
assertPhpReferenceResolveTo(PhpFileType.INSTANCE,
"<?php" +
"/** @var $t \\PHPUnit\\Framework\\TestCase $d */\n" +
"$t->prophesize('Foo')->b<caret>ar();",
PlatformPatterns.psiElement(Method.class).withName("bar")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,48 @@ public void testThatProphesizeForVariableIsResolved() {
" public function testFoobar()\n" +
" {\n" +
" $foo = $this->prophesize(Foo::class);\n" +
" $foo->find()->will<caret>Return();\n" +
" $foo->getBar()->will<caret>Return();\n" +
" }\n" +
" }",
PlatformPatterns.psiElement(Method.class).withName("willReturn")
);
}

public void testThatProphesizeForVariableWithStringClassIsResolved() {
assertPhpReferenceResolveTo(PhpFileType.INSTANCE, "<?php\n" +
"class FooTest extends \\PHPUnit\\Framework\\TestCase\n" +
" {\n" +
" public function testFoobar()\n" +
" {\n" +
" $foo = $this->prophesize('Foo');\n" +
" $foo->getBar()->will<caret>Return();\n" +
" }\n" +
" }",
PlatformPatterns.psiElement(Method.class).withName("willReturn")
);
}

public void testThatProphesizeForVariableIsNotResolvedForUnknownMethods() {
assertPhpReferenceNotResolveTo(PhpFileType.INSTANCE, "<?php\n" +
"class FooTest extends \\PHPUnit\\Framework\\TestCase\n" +
" {\n" +
" public function testFoobar()\n" +
" {\n" +
" $foo = $this->prophesize(Foo::class);\n" +
" $foo->unknown()->will<caret>Return();\n" +
" }\n" +
" }",
PlatformPatterns.psiElement()
);
}

public void testThatProphesizeForMethodReferenceIsResolved() {
assertPhpReferenceResolveTo(PhpFileType.INSTANCE, "<?php\n" +
"class FooTest extends \\PHPUnit\\Framework\\TestCase\n" +
" {\n" +
" public function testFoobar()\n" +
" {\n" +
" $foo = $this->prophesize(Foo::class)->find()->will<caret>Return();\n" +
" $foo = $this->prophesize(Foo::class)->getBar()->will<caret>Return();\n" +
" }\n" +
" }",
PlatformPatterns.psiElement(Method.class).withName("willReturn")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.espend.idea.php.phpunit.tests.type;

import com.intellij.patterns.PlatformPatterns;
import com.jetbrains.php.lang.PhpFileType;
import com.jetbrains.php.lang.psi.elements.Method;
import de.espend.idea.php.phpunit.tests.PhpUnitLightCodeInsightFixtureTestCase;

import java.io.File;

/**
* @author Daniel Espendiller <daniel@espendiller.net>
* @see de.espend.idea.php.phpunit.type.RevealProphecyTypeProvider
*/
public class RevealProphecyTypeProviderTest extends PhpUnitLightCodeInsightFixtureTestCase {
public void setUp() throws Exception {
super.setUp();
myFixture.copyFileToProject("RevealProphecyTypeProvider.php");
}

public String getTestDataPath() {
return new File(this.getClass().getResource("fixtures").getFile()).getAbsolutePath();
}

public void testThatRevealIsResolved() {
assertPhpReferenceResolveTo(PhpFileType.INSTANCE, "<?php\n" +
"class FooTest extends \\PHPUnit\\Framework\\TestCase\n" +
" {\n" +
" public function testFoobar()\n" +
" {\n" +
" $foo = $this->prophesize(Foo::class);\n" +
" $foo->reveal()->getB<caret>ar();\n" +
" }\n" +
" }",
PlatformPatterns.psiElement(Method.class).withName("getBar")
);
}

public void testThatRevealIsResolvedForStringClass() {
assertPhpReferenceResolveTo(PhpFileType.INSTANCE, "<?php\n" +
"class FooTest extends \\PHPUnit\\Framework\\TestCase\n" +
" {\n" +
" public function testFoobar()\n" +
" {\n" +
" $foo = $this->prophesize('Foo');\n" +
" $foo->reveal()->getB<caret>ar();\n" +
" }\n" +
" }",
PlatformPatterns.psiElement(Method.class).withName("getBar")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace PHPUnit\Framework
{

use Prophecy\Prophecy\ObjectProphecy;

class TestCase extends \PHPUnit_Framework_MockObject_InvocationMocker
{
/**
* @param null $classOrInterface
* @return \Prophecy\Prophecy\ObjectProphecy
*/
protected function prophesize($classOrInterface = null)
{
return new ObjectProphecy();
}
};
}

namespace Prophecy\Prophecy
{
class ObjectProphecy
{
public function reveal()
{
}
}
}

namespace
{
class Foo
{
public function getBar()
{
}
}
}

0 comments on commit 6e51b20

Please sign in to comment.