Skip to content

Commit 5be522c

Browse files
authored
Merge pull request #190 from cedricziel/fix-return-type-provider
Fix recursive calling of type provider by refraining from index lookups
2 parents 606f52e + 44ee922 commit 5be522c

File tree

11 files changed

+216
-75
lines changed

11 files changed

+216
-75
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ cache:
1616

1717
matrix:
1818
include:
19-
- env: IDEA_VERSION="IU-182.2757.3" PHP_PLUGIN_VERSION="182.2574.13" PSI_VIEWER_PLUGIN_VERSION=182.2757.2 DEPLOY=true
19+
- env: IDEA_VERSION="IU-182.3341.11" PHP_PLUGIN_VERSION="182.3341.34" PSI_VIEWER_PLUGIN_VERSION=182.2757.2 DEPLOY=true
2020
# - env: IDEA_VERSION="IU-173.4127.17" PHP_PLUGIN_VERSION="173.4127.13"
2121
# allow_failures:
2222
# - env: IDEA_VERSION="IU-173.4127.17" PHP_PLUGIN_VERSION="173.4127.13"

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ buildscript {
99
}
1010

1111
plugins {
12-
id "org.jetbrains.intellij" version "0.3.3"
12+
id "org.jetbrains.intellij" version "0.3.4"
1313
id 'com.palantir.git-version' version "0.9.1"
1414
}
1515

gradle.properties

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
ideaVersion = IU-182.2757.3
2-
phpPluginVersion = 182.2574.13
1+
ideaVersion = IU-182.3341.11
2+
phpPluginVersion = 182.3341.34
33
psiViewerPluginVersion = 182.2757.2
4+
45
#ideaVersion = IU-2018.1.4
56
#phpPluginVersion = 181.5087.24
67
#ideaVersion = IU-2017.3.3
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.cedricziel.idea.typo3.extbase;
2+
3+
import com.intellij.openapi.project.Project;
4+
import com.intellij.psi.PsiElement;
5+
import com.jetbrains.php.PhpClassHierarchyUtils;
6+
import com.jetbrains.php.PhpIndex;
7+
import com.jetbrains.php.lang.psi.elements.PhpClass;
8+
import com.jetbrains.php.lang.psi.elements.PhpClassMember;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
11+
12+
import java.util.Collection;
13+
import java.util.Iterator;
14+
15+
import static com.cedricziel.idea.typo3.extbase.persistence.ExtbasePersistenceCompletionContributor.ExtbaseRepositoryMagicMethodsCompletionProvider.TYPO3_CMS_EXTBASE_PERSISTENCE_REPOSITORY;
16+
17+
public class ExtbaseUtils {
18+
public static final String TYPO3_CMS_EXTBASE_DOMAIN_OBJECT_ABSTRACT_ENTITY = "TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity";
19+
20+
@Nullable
21+
public static PhpClass getBaseRepositoryClass(@NotNull Project project) {
22+
Iterator<PhpClass> iterator = PhpIndex.getInstance(project).getClassesByFQN(TYPO3_CMS_EXTBASE_PERSISTENCE_REPOSITORY).iterator();
23+
if (!iterator.hasNext()) {
24+
return null;
25+
}
26+
27+
PhpClass repositoryClass = iterator.next();
28+
if (repositoryClass == null) {
29+
return null;
30+
}
31+
32+
return repositoryClass;
33+
}
34+
35+
public static boolean isRepositoryClass(@NotNull PhpClass phpClass) {
36+
PhpClass baseRepositoryClass = getBaseRepositoryClass(phpClass.getProject());
37+
if (baseRepositoryClass == null) {
38+
return false;
39+
}
40+
41+
return PhpClassHierarchyUtils.isSuperClass(baseRepositoryClass, phpClass, true);
42+
}
43+
44+
private boolean isEntityClass(PsiElement psiElement) {
45+
PhpClass containingClass = ((PhpClassMember) psiElement).getContainingClass();
46+
if (containingClass == null) {
47+
return false;
48+
}
49+
50+
Collection<PhpClass> classesByFQN = PhpIndex.getInstance(psiElement.getProject()).getClassesByFQN(TYPO3_CMS_EXTBASE_DOMAIN_OBJECT_ABSTRACT_ENTITY);
51+
if (classesByFQN.isEmpty()) {
52+
return false;
53+
}
54+
55+
PhpClass abstractEntityClass = classesByFQN.iterator().next();
56+
return PhpClassHierarchyUtils.isSuperClass(abstractEntityClass, containingClass, true);
57+
}
58+
}

src/main/java/com/cedricziel/idea/typo3/extbase/persistence/ExtbaseModelCollectionReturnTypeProvider.java

Lines changed: 83 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import com.intellij.openapi.project.DumbService;
44
import com.intellij.openapi.project.Project;
5+
import com.intellij.patterns.PlatformPatterns;
56
import com.intellij.psi.PsiElement;
6-
import com.jetbrains.php.PhpClassHierarchyUtils;
7+
import com.intellij.psi.PsiRecursiveElementVisitor;
78
import com.jetbrains.php.PhpIndex;
89
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
910
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocParamTag;
@@ -12,53 +13,23 @@
1213
import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider3;
1314
import org.jetbrains.annotations.Nullable;
1415

16+
import java.util.ArrayList;
1517
import java.util.Collection;
18+
import java.util.List;
1619
import java.util.Set;
1720
import java.util.regex.Matcher;
1821
import java.util.regex.Pattern;
1922

2023
public class ExtbaseModelCollectionReturnTypeProvider implements PhpTypeProvider3 {
2124

22-
public static final String TYPO3_CMS_EXTBASE_DOMAIN_OBJECT_ABSTRACT_ENTITY = "TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity";
23-
24-
@Override
25-
public char getKey() {
26-
return '\u0278';
27-
}
2825

2926
@Nullable
30-
@Override
31-
public PhpType getType(PsiElement psiElement) {
32-
if (DumbService.getInstance(psiElement.getProject()).isDumb()) {
33-
return null;
34-
}
35-
36-
if (!(psiElement instanceof Field) && !isGetter(psiElement)) {
37-
return null;
38-
}
39-
40-
if (!isEntityClass(psiElement)) {
41-
return null;
42-
}
43-
44-
return extractReturnType(psiElement);
45-
}
46-
47-
private PhpType extractReturnType(PsiElement psiElement) {
48-
Field field;
49-
if (psiElement instanceof MethodReference) {
50-
field = extractFieldFromGetter((MethodReference) psiElement);
51-
} else if (psiElement instanceof Method) {
52-
field = extractFieldFromGetter((Method) psiElement);
53-
} else {
54-
field = ((Field) psiElement);
55-
}
56-
57-
if (field == null) {
27+
private static PhpType inferTypeFromClassMember(PhpClassMember classMember) {
28+
if (classMember == null) {
5829
return null;
5930
}
6031

61-
PhpDocComment docComment = field.getDocComment();
32+
PhpDocComment docComment = classMember.getDocComment();
6233
if (docComment == null) {
6334
return null;
6435
}
@@ -85,6 +56,31 @@ private PhpType extractReturnType(PsiElement psiElement) {
8556
return phpType;
8657
}
8758

59+
@Override
60+
public char getKey() {
61+
return '\u0278';
62+
}
63+
64+
@Nullable
65+
@Override
66+
public PhpType getType(PsiElement psiElement) {
67+
if (DumbService.getInstance(psiElement.getProject()).isDumb()) {
68+
return null;
69+
}
70+
71+
if (psiElement instanceof FieldReference) {
72+
String signature = ((FieldReference) psiElement).getSignature();
73+
return new PhpType().add("#" + this.getKey() + signature);
74+
}
75+
76+
if (psiElement instanceof MethodReference && ((MethodReference) psiElement).getName().startsWith("get")) {
77+
String signature = ((MethodReference) psiElement).getSignature();
78+
return new PhpType().add("#" + this.getKey() + signature);
79+
}
80+
81+
return null;
82+
}
83+
8884
private Field extractFieldFromGetter(MethodReference methodReference) {
8985
String name = methodReference.getName();
9086
if (name == null) {
@@ -127,27 +123,62 @@ private Field extractFieldFromGetter(Method method) {
127123
return containingClass.findFieldByName(propertyName, false);
128124
}
129125

130-
private boolean isEntityClass(PsiElement psiElement) {
131-
PhpClass containingClass = ((PhpClassMember) psiElement).getContainingClass();
132-
if (containingClass == null) {
133-
return false;
134-
}
126+
@Override
127+
public Collection<? extends PhpNamedElement> getBySignature(String expression, Set<String> visited, int depth, Project project) {
128+
Collection<? extends PhpNamedElement> bySignature = PhpIndex.getInstance(project).getBySignature(expression);
129+
130+
List<PhpNamedElement> phpNamedElements = new ArrayList<>();
131+
for (PhpNamedElement phpNamedElement: bySignature) {
132+
if (phpNamedElement instanceof Field) {
133+
PhpType type = inferTypeFromClassMember((Field) phpNamedElement);
134+
phpNamedElement.getType().add(type);
135+
136+
phpNamedElements.add(phpNamedElement);
137+
} else if (phpNamedElement instanceof Method) {
138+
phpNamedElement.getType().add(inferTypeFromClassMember((Method) phpNamedElement));
139+
phpNamedElement.getType().add(inferTypeFromClassMember(extractFieldFromGetter((Method) phpNamedElement)));
135140

136-
Collection<PhpClass> classesByFQN = PhpIndex.getInstance(psiElement.getProject()).getClassesByFQN(TYPO3_CMS_EXTBASE_DOMAIN_OBJECT_ABSTRACT_ENTITY);
137-
if (classesByFQN.isEmpty()) {
138-
return false;
141+
MethodReturnTypeVisitor visitor = new MethodReturnTypeVisitor();
142+
visitor.visitElement(phpNamedElement);
143+
144+
phpNamedElement.getType().add(visitor.getType());
145+
146+
phpNamedElements.add(phpNamedElement);
147+
} else if (phpNamedElement instanceof MethodReference) {
148+
phpNamedElement.getType().add(inferTypeFromClassMember(extractFieldFromGetter((MethodReference) phpNamedElement)));
149+
150+
phpNamedElements.add(phpNamedElement);
151+
}
139152
}
140153

141-
PhpClass abstractEntityClass = classesByFQN.iterator().next();
142-
return PhpClassHierarchyUtils.isSuperClass(abstractEntityClass, containingClass, true);
154+
return phpNamedElements;
143155
}
144156

145-
private boolean isGetter(PsiElement psiElement) {
146-
return (psiElement instanceof Method) && ((Method) psiElement).getName().startsWith("get");
147-
}
157+
private static class MethodReturnTypeVisitor extends PsiRecursiveElementVisitor {
158+
private final PhpType type;
148159

149-
@Override
150-
public Collection<? extends PhpNamedElement> getBySignature(String expression, Set<String> visited, int depth, Project project) {
151-
return PhpIndex.getInstance(project).getBySignature(expression);
160+
public MethodReturnTypeVisitor() {
161+
super();
162+
163+
this.type = new PhpType();
164+
}
165+
166+
@Override
167+
public void visitElement(PsiElement element) {
168+
super.visitElement(element);
169+
170+
if (PlatformPatterns.psiElement(FieldReference.class).withParent(PhpReturn.class).accepts(element)) {
171+
Field f = (Field) ((FieldReference) element).resolve();
172+
if (f == null) {
173+
return;
174+
}
175+
176+
this.type.add(inferTypeFromClassMember(f));
177+
}
178+
}
179+
180+
public PhpType getType() {
181+
return type;
182+
}
152183
}
153184
}

src/main/java/com/cedricziel/idea/typo3/extbase/persistence/ExtbasePersistenceReferenceResolver.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ public Collection<? extends PhpNamedElement> resolve(PhpReference phpReference)
3434
return Collections.emptyList();
3535
}
3636

37-
Collection<PhpClass> classesByFQN = PhpIndex.getInstance(phpReference.getProject()).getClassesByFQN(variable.getType().toStringResolved());
37+
Collection<PhpClass> classesByFQN = new ArrayList<>();
38+
39+
for (String s: variable.getType().getTypes()) {
40+
classesByFQN.addAll(PhpIndex.getInstance(phpReference.getProject()).getClassesByFQN(s));
41+
}
42+
3843
if (classesByFQN.isEmpty()) {
3944
return Collections.emptyList();
4045
}

src/test/java/com/cedricziel/idea/typo3/extbase/persistence/ExtbaseModelCollectionReturnTypeProviderTest.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22

33
import com.intellij.psi.PsiElement;
44
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
5-
import com.jetbrains.php.lang.psi.elements.Field;
65
import com.jetbrains.php.lang.psi.elements.Method;
7-
8-
import java.util.Set;
6+
import com.jetbrains.php.lang.psi.elements.MethodReference;
97

108
public class ExtbaseModelCollectionReturnTypeProviderTest extends LightCodeInsightFixtureTestCase {
119
@Override
@@ -17,23 +15,31 @@ public void testResolvesObjectStoragePropertiesToObjectTypes() {
1715
myFixture.copyFileToProject("PersistenceMocks.php");
1816
myFixture.configureByFile("FieldTypeProvider.php");
1917

20-
PsiElement elementAtCaret = myFixture.getElementAtCaret();
18+
int caretOffset = myFixture.getCaretOffset();
19+
PsiElement elementAtCaret = myFixture.getFile().findElementAt(caretOffset).getParent();
20+
21+
assertInstanceOf(elementAtCaret, MethodReference.class);
2122

22-
assertInstanceOf(elementAtCaret, Field.class);
23+
MethodReference methodReference = (MethodReference) elementAtCaret;
24+
Method m = (Method) methodReference.resolve();
2325

24-
Set<String> types = ((Field) elementAtCaret).getInferredType().getTypes();
25-
assertTrue(types.contains("\\My\\Extension\\Domain\\Model\\Book[]"));
26+
String fqn = m.getContainingClass().getFQN();
27+
assertTrue(fqn.equals("\\My\\Extension\\Domain\\Model\\Book"));
2628
}
2729

2830
public void testResolvesObjectStoragePropertiesToObjectTypesOnGetters() {
2931
myFixture.copyFileToProject("PersistenceMocks.php");
3032
myFixture.configureByFile("MethodTypeProvider.php");
3133

32-
PsiElement elementAtCaret = myFixture.getElementAtCaret();
34+
int caretOffset = myFixture.getCaretOffset();
35+
PsiElement elementAtCaret = myFixture.getFile().findElementAt(caretOffset).getParent();
36+
37+
assertInstanceOf(elementAtCaret, MethodReference.class);
3338

34-
assertInstanceOf(elementAtCaret, Method.class);
39+
MethodReference methodReference = (MethodReference) elementAtCaret;
40+
Method m = (Method) methodReference.resolve();
3541

36-
Set<String> types = ((Method) elementAtCaret).getInferredType().getTypes();
37-
assertTrue(types.contains("\\My\\Extension\\Domain\\Model\\Book[]"));
42+
String fqn = m.getContainingClass().getFQN();
43+
assertTrue(fqn.equals("\\My\\Extension\\Domain\\Model\\Book"));
3844
}
3945
}

src/test/java/com/cedricziel/idea/typo3/extbase/persistence/ExtbasePersistenceReferenceResolverTest.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
package com.cedricziel.idea.typo3.extbase.persistence;
22

33
import com.intellij.psi.PsiElement;
4+
import com.intellij.psi.PsiReference;
45
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
56
import com.jetbrains.php.lang.psi.elements.Field;
7+
import com.jetbrains.php.lang.psi.elements.MethodReference;
8+
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
9+
import com.jetbrains.php.lang.psi.elements.PhpReference;
10+
import com.jetbrains.php.lang.psi.resolve.PhpReferenceResolver;
11+
12+
import java.util.Collection;
613

714
public class ExtbasePersistenceReferenceResolverTest extends LightCodeInsightFixtureTestCase {
815
@Override
@@ -24,9 +31,25 @@ public void testCanNavigateToPropertiesFromMagicMethodsOnMembers() {
2431
myFixture.copyFileToProject("PersistenceMocks.php");
2532
myFixture.configureByFile("RepositoryMagicMethodNavigationOnMember.php");
2633

27-
PsiElement elementAtCaret = myFixture.getElementAtCaret();
34+
int caretOffset = myFixture.getCaretOffset();
35+
PsiElement elementAtCaret = myFixture.getFile().findElementAt(caretOffset).getParent();
2836

29-
assertInstanceOf(elementAtCaret, Field.class);
30-
assertEquals("author", ((Field) elementAtCaret).getName());
37+
assertInstanceOf(elementAtCaret, MethodReference.class);
38+
39+
for (PsiReference ref: elementAtCaret.getReferences()) {
40+
if (ref instanceof PhpReference) {
41+
for (PhpReferenceResolver resolver: PhpReferenceResolver.EP_NAME.getExtensions()) {
42+
Collection<? extends PhpNamedElement> resolve = resolver.resolve((PhpReference) ref);
43+
44+
for (PhpNamedElement phpNamedElement: resolve) {
45+
if (phpNamedElement.getName().equals("author") && phpNamedElement instanceof Field) {
46+
return;
47+
}
48+
}
49+
}
50+
}
51+
}
52+
53+
fail("Could not resolve to correct object");
3154
}
3255
}

testData/com/cedricziel/idea/typo3/extbase/persistence/FieldTypeProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ class FieldTypeProvider extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
1111

1212
public function getBook()
1313
{
14-
foreach ($this-><caret>book as $b) {
15-
$b->getAuthor();
14+
foreach ($this->book as $b) {
15+
$b->get<caret>Author();
1616
}
1717
}
1818
}

testData/com/cedricziel/idea/typo3/extbase/persistence/MethodTypeProvider.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public function getBook() : ObjectStorage
1818

1919
public function foo()
2020
{
21-
return $this-><caret>getBook();
21+
$book = $this->getBook()[0];
22+
return $book->get<caret>Author();
2223
}
2324
}
2425

0 commit comments

Comments
 (0)