Skip to content

Commit 2bbe607

Browse files
committed
Fix recursive calling of type provider by refraining from index lookups
1 parent 606f52e commit 2bbe607

File tree

6 files changed

+179
-67
lines changed

6 files changed

+179
-67
lines changed
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/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
}

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

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
<?php
22

3+
namespace TYPO3\CMS\Extbase\Persistence\Generic {
4+
class Repository {}
5+
class QueryResultInterface {}
6+
class ObjectStorage {}
7+
}
8+
39
namespace TYPO3\CMS\Extbase\Persistence {
410
class Repository {}
511
class QueryResultInterface {}
612
class ObjectStorage {}
713
}
814

915
namespace TYPO3\CMS\Extbase\DomainObject {
10-
class AbstractEntity {}
16+
class AbstractEntity {
17+
public function getUid() {}
18+
}
1119
}
1220

1321
namespace My\Extension\Domain\Model {
@@ -31,9 +39,17 @@ class Book extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
3139
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\My\Extension\Domain\Model>
3240
*/
3341
protected $publishers;
42+
43+
public function getAuthor() {
44+
return $this->author;
45+
}
3446
}
3547
}
3648

3749
namespace My\Extension\Domain\Repository {
3850
class BookRepository extends \TYPO3\CMS\Extbase\Persistence\Repository {}
3951
}
52+
53+
namespace TYPO3\CMS\Extbase\Persistence\Generic {
54+
class Query {}
55+
}

0 commit comments

Comments
 (0)