Skip to content

Commit 6d81827

Browse files
authored
Merge pull request #191 from cedricziel/complete-extbase-foo
Autocomplete extbase property names in queries
2 parents 948c0b2 + 1666e91 commit 6d81827

File tree

6 files changed

+208
-28
lines changed

6 files changed

+208
-28
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.cedricziel.idea.typo3.extbase;
2+
3+
import com.intellij.patterns.PlatformPatterns;
4+
import com.intellij.patterns.PsiElementPattern;
5+
import com.intellij.psi.PsiElement;
6+
import com.jetbrains.php.lang.psi.elements.MethodReference;
7+
import com.jetbrains.php.lang.psi.elements.ParameterList;
8+
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
public class ExtbasePatterns {
12+
@NotNull
13+
public static PsiElementPattern.Capture<PsiElement> stringArgumentOnMethodCallPattern() {
14+
return PlatformPatterns.psiElement()
15+
.withParent(
16+
PlatformPatterns.psiElement(StringLiteralExpression.class).withParent(
17+
PlatformPatterns.psiElement(ParameterList.class).withParent(
18+
PlatformPatterns.psiElement(MethodReference.class)
19+
)
20+
)
21+
);
22+
}
23+
}

src/main/java/com/cedricziel/idea/typo3/extbase/ExtbaseUtils.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
public class ExtbaseUtils {
1818
public static final String TYPO3_CMS_EXTBASE_DOMAIN_OBJECT_ABSTRACT_ENTITY = "TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity";
1919

20+
public static final String[] NON_QUERYABLE_ENTITY_FIELDS = {
21+
"_isClone",
22+
"_cleanProperties",
23+
};
24+
2025
@Nullable
2126
public static PhpClass getBaseRepositoryClass(@NotNull Project project) {
2227
Iterator<PhpClass> iterator = PhpIndex.getInstance(project).getClassesByFQN(TYPO3_CMS_EXTBASE_PERSISTENCE_REPOSITORY).iterator();

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
package com.cedricziel.idea.typo3.extbase.persistence;
22

3+
import com.cedricziel.idea.typo3.extbase.ExtbasePatterns;
4+
import com.cedricziel.idea.typo3.extbase.ExtbaseUtils;
35
import com.cedricziel.idea.typo3.util.ExtbaseUtility;
46
import com.intellij.codeInsight.completion.*;
57
import com.intellij.codeInsight.lookup.LookupElement;
68
import com.intellij.codeInsight.lookup.LookupElementPresentation;
79
import com.intellij.patterns.PlatformPatterns;
810
import com.intellij.psi.PsiElement;
11+
import com.intellij.psi.util.PsiTreeUtil;
912
import com.intellij.util.ProcessingContext;
1013
import com.jetbrains.php.PhpClassHierarchyUtils;
1114
import com.jetbrains.php.PhpIcons;
1215
import com.jetbrains.php.PhpIndex;
16+
import com.jetbrains.php.completion.PhpLookupElement;
1317
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
1418
import com.jetbrains.php.lang.psi.elements.*;
1519
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
1620
import org.apache.commons.lang.StringUtils;
1721
import org.jetbrains.annotations.NotNull;
1822

23+
import java.util.Arrays;
1924
import java.util.Collection;
2025
import java.util.Iterator;
2126

2227
public class ExtbasePersistenceCompletionContributor extends CompletionContributor {
2328
public ExtbasePersistenceCompletionContributor() {
2429
this.extend(CompletionType.BASIC, PlatformPatterns.psiElement(PhpTokenTypes.IDENTIFIER), new ExtbaseRepositoryMagicMethodsCompletionProvider());
30+
this.extend(CompletionType.BASIC, ExtbasePatterns.stringArgumentOnMethodCallPattern(), new ExtbaseQueryBuilderCompletionProvider());
2531
}
2632

2733
public static class ExtbaseRepositoryMagicMethodsCompletionProvider extends CompletionProvider<CompletionParameters> {
@@ -153,4 +159,58 @@ public void renderElement(LookupElementPresentation presentation) {
153159
});
154160
}
155161
}
162+
163+
public static class ExtbaseQueryBuilderCompletionProvider extends CompletionProvider<CompletionParameters> {
164+
165+
public static String QUERY_BUILDER = "TYPO3\\CMS\\Extbase\\Persistence\\QueryInterface";
166+
167+
public static String[] QUERY_BUILDER_METHODS = {
168+
"equals",
169+
"like",
170+
"contains",
171+
"in",
172+
"lessThan",
173+
"lessThanOrEqual",
174+
"greaterThan",
175+
"greaterThanOrEqual",
176+
"isEmpty",
177+
};
178+
179+
@Override
180+
protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet result) {
181+
PsiElement element = parameters.getOriginalPosition();
182+
183+
Method containingMethod = (Method) PsiTreeUtil.findFirstParent(element, x -> PlatformPatterns.psiElement(Method.class).accepts(x));
184+
MethodReference methodReference = (MethodReference) PsiTreeUtil.findFirstParent(element, x -> PlatformPatterns.psiElement(MethodReference.class).accepts(x));
185+
186+
if (containingMethod == null || methodReference == null) {
187+
return;
188+
}
189+
190+
Method method = (Method) methodReference.resolve();
191+
if (method == null || method.getContainingClass() == null) {
192+
return;
193+
}
194+
195+
if (!Arrays.asList(QUERY_BUILDER_METHODS).contains(method.getName())) {
196+
return;
197+
}
198+
199+
PhpClass repositoryClass = containingMethod.getContainingClass();
200+
if (repositoryClass == null || !ExtbaseUtils.isRepositoryClass(repositoryClass)) {
201+
return;
202+
}
203+
204+
PhpClass containingClass = method.getContainingClass();
205+
if (!containingClass.getPresentableFQN().equals(QUERY_BUILDER)) {
206+
return;
207+
}
208+
209+
String potentialModelClass = ExtbaseUtility.convertRepositoryFQNToEntityFQN(repositoryClass.getFQN());
210+
Collection<PhpClass> classesByFQN = PhpIndex.getInstance(element.getProject()).getClassesByFQN(potentialModelClass);
211+
for (PhpClass x: classesByFQN) {
212+
x.getFields().stream().filter(field -> !Arrays.asList(ExtbaseUtils.NON_QUERYABLE_ENTITY_FIELDS).contains(field.getName())).forEach(field -> result.addElement(new PhpLookupElement(field)));
213+
}
214+
}
215+
}
156216
}

src/test/java/com/cedricziel/idea/typo3/AbstractTestCase.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl;
44
import com.intellij.codeInsight.lookup.LookupElement;
5+
import com.intellij.codeInsight.lookup.LookupElementPresentation;
56
import com.intellij.codeInspection.*;
67
import com.intellij.lang.LanguageAnnotators;
78
import com.intellij.lang.annotation.Annotation;
@@ -176,4 +177,38 @@ protected void assertNotHasVariant(String fileName, String content, String expec
176177
}
177178
}
178179
}
180+
181+
protected void assertContainsLookupElementWithText(LookupElement[] lookupElements, String title) {
182+
for (LookupElement lookupElement: lookupElements) {
183+
LookupElementPresentation presentation = new LookupElementPresentation();
184+
lookupElement.renderElement(presentation);
185+
if (presentation.getItemText().equals(title)) {
186+
return;
187+
}
188+
}
189+
190+
fail("No such element");
191+
}
192+
193+
protected void assertContainsLookupElementWithText(LookupElement[] lookupElements, @NotNull String title, @NotNull String tailText, @NotNull String typeText) {
194+
for (LookupElement lookupElement: lookupElements) {
195+
LookupElementPresentation presentation = new LookupElementPresentation();
196+
lookupElement.renderElement(presentation);
197+
if (presentation.getItemText().equals(title) && presentation.getTailText().equals(tailText) && presentation.getTypeText().contains(typeText)) {
198+
return;
199+
}
200+
}
201+
202+
fail("No such element");
203+
}
204+
205+
protected void assertNotContainsLookupElementWithText(LookupElement[] lookupElements, @NotNull String title) {
206+
for (LookupElement lookupElement: lookupElements) {
207+
LookupElementPresentation presentation = new LookupElementPresentation();
208+
lookupElement.renderElement(presentation);
209+
if (presentation.getItemText().equals(title)) {
210+
fail("Element shouldnt be present but is");
211+
}
212+
}
213+
}
179214
}

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

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

3+
import com.cedricziel.idea.typo3.AbstractTestCase;
34
import com.intellij.codeInsight.lookup.LookupElement;
4-
import com.intellij.codeInsight.lookup.LookupElementPresentation;
5-
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
6-
import org.jetbrains.annotations.NotNull;
75

8-
public class ExtbasePersistenceCompletionContributorTest extends LightCodeInsightFixtureTestCase {
6+
public class ExtbasePersistenceCompletionContributorTest extends AbstractTestCase {
97
@Override
108
protected String getTestDataPath() {
119
return "testData/com/cedricziel/idea/typo3/extbase/persistence";
@@ -65,25 +63,36 @@ public void testCanCompleteExtbaseRepositoryMagicMethodsOnMembers() {
6563
assertNotContainsLookupElementWithText(lookupElements, "countByPublishers");
6664
}
6765

68-
private void assertContainsLookupElementWithText(LookupElement[] lookupElements, @NotNull String title, @NotNull String tailText, @NotNull String typeText) {
69-
for (LookupElement lookupElement : lookupElements) {
70-
LookupElementPresentation presentation = new LookupElementPresentation();
71-
lookupElement.renderElement(presentation);
72-
if (presentation.getItemText().equals(title) && presentation.getTailText().equals(tailText) && presentation.getTypeText().contains(typeText)) {
73-
return;
74-
}
75-
}
66+
public void testCanCompleteExtbaseDomainModelFieldsInQuery() {
67+
myFixture.copyFileToProject("PersistenceMocks.php");
7668

77-
fail("No such element");
78-
}
69+
LookupElement[] lookupElements;
70+
71+
myFixture.configureByText(
72+
"foo.php",
73+
"<?php\n" +
74+
"namespace My\\Extension\\Domain\\Repository {\n" +
75+
" class BookRepository extends \\TYPO3\\CMS\\Extbase\\Persistence\\Repository {\n" +
76+
" public function fooBar() {\n" +
77+
" /** @var \\TYPO3\\CMS\\Extbase\\Persistence\\QueryInterface $q */" +
78+
" $q = $this->createQuery();\n" +
79+
" $q->matching(\n" +
80+
" $q->equals('<caret>')\n" +
81+
" );\n" +
82+
" }\n" +
83+
" }\n" +
84+
"}"
85+
);
86+
87+
lookupElements = myFixture.completeBasic();
7988

80-
private void assertNotContainsLookupElementWithText(LookupElement[] lookupElements, @NotNull String title) {
81-
for (LookupElement lookupElement : lookupElements) {
82-
LookupElementPresentation presentation = new LookupElementPresentation();
83-
lookupElement.renderElement(presentation);
84-
if (presentation.getItemText().equals(title)) {
85-
fail("Element shouldnt be present but is");
86-
}
87-
}
89+
assertContainsLookupElementWithText(lookupElements, "author");
90+
assertContainsLookupElementWithText(lookupElements, "uid");
91+
assertContainsLookupElementWithText(lookupElements, "pid");
92+
assertNotContainsLookupElementWithText(lookupElements, "_cleanProperties");
93+
assertNotContainsLookupElementWithText(lookupElements, "_isClone");
94+
assertContainsLookupElementWithText(lookupElements, "_versionedUid");
95+
assertContainsLookupElementWithText(lookupElements, "_languageUid");
96+
assertContainsLookupElementWithText(lookupElements, "_localizedUid");
8897
}
8998
}

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

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,63 @@
44
class Repository {}
55
class QueryResultInterface {}
66
class ObjectStorage {}
7+
8+
class Query implements \TYPO3\CMS\Extbase\Persistence\QueryInterface {
9+
public function equals($tableName, $operand) {}
10+
public function matching($condition){}
11+
}
712
}
813

914
namespace TYPO3\CMS\Extbase\Persistence {
1015
class Repository {}
1116
class QueryResultInterface {}
1217
class ObjectStorage {}
18+
interface QueryInterface {
19+
public function equals($tableName, $operand);
20+
public function matching($condition);
21+
}
1322
}
1423

1524
namespace TYPO3\CMS\Extbase\DomainObject {
16-
class AbstractEntity {
25+
class AbstractDomainObject {
26+
/**
27+
* @var int The uid of the record. The uid is only unique in the context of the database table.
28+
*/
29+
protected $uid;
30+
31+
/**
32+
* @var int The uid of the localized record. Holds the uid of the record in default language (the translationOrigin).
33+
*/
34+
protected $_localizedUid;
35+
36+
/**
37+
* @var int The uid of the language of the object. This is the uid of the language record in the table sys_language.
38+
*/
39+
protected $_languageUid;
40+
41+
/**
42+
* @var int The uid of the versioned record.
43+
*/
44+
protected $_versionedUid;
45+
46+
/**
47+
* @var int The id of the page the record is "stored".
48+
*/
49+
protected $pid;
50+
51+
/**
52+
* TRUE if the object is a clone
53+
*
54+
* @var bool
55+
*/
56+
private $_isClone = false;
57+
58+
/**
59+
* @var array An array holding the clean property values. Set right after reconstitution of the object
60+
*/
61+
private $_cleanProperties = [];
62+
}
63+
class AbstractEntity extends AbstractDomainObject {
1764
public function getUid() {}
1865
}
1966
}
@@ -47,9 +94,10 @@ public function getAuthor() {
4794
}
4895

4996
namespace My\Extension\Domain\Repository {
50-
class BookRepository extends \TYPO3\CMS\Extbase\Persistence\Repository {}
51-
}
52-
53-
namespace TYPO3\CMS\Extbase\Persistence\Generic {
54-
class Query {}
97+
class BookRepository extends \TYPO3\CMS\Extbase\Persistence\Repository {
98+
/**
99+
* @return \TYPO3\CMS\Extbase\Persistence\QueryInterface
100+
*/
101+
public function createQuery(){}
102+
}
55103
}

0 commit comments

Comments
 (0)