Skip to content

Commit

Permalink
#2334 optimize prefix matcher for Doctrine querybuilder parameter, to…
Browse files Browse the repository at this point in the history
… reduce conflicting PhpStorm implementation
  • Loading branch information
Haehnchen committed Apr 14, 2024
1 parent a12ead4 commit 009dc56
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
import fr.adrienbrault.idea.symfony2plugin.doctrine.DoctrineUtil;
import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineModelField;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict.QueryBuilderCompletionContribution;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict.QueryBuilderCompletionContributionType;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict.QueryBuilderPropertyAlias;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict.QueryBuilderRelation;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.processor.QueryBuilderChainProcessor;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.util.MatcherUtil;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.util.QueryBuilderUtil;
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
Expand Down Expand Up @@ -108,7 +111,7 @@ protected void addCompletions(@NotNull CompletionParameters completionParameters
@Override
protected void addCompletions(@NotNull CompletionParameters completionParameters, @NotNull ProcessingContext processingContext, @NotNull CompletionResultSet completionResultSet) {
PsiElement psiElement = completionParameters.getOriginalPosition();
if (!Symfony2ProjectComponent.isEnabled(psiElement) || !(psiElement.getContext() instanceof StringLiteralExpression)) {
if (!Symfony2ProjectComponent.isEnabled(psiElement) || !(psiElement.getContext() instanceof StringLiteralExpression parent)) {
return;
}

Expand All @@ -122,6 +125,14 @@ protected void addCompletions(@NotNull CompletionParameters completionParameters

QueryBuilderMethodReferenceParser qb = getQueryBuilderParser(methodMatchParameter.getMethodReference());
QueryBuilderScopeContext collect = qb.collect();

String content = PsiElementUtils.getStringBeforeCursor(parent, completionParameters.getOffset());

// "test.test"
if (content != null && content.matches("^[\\w_.]+$")) {
completionResultSet = completionResultSet.withPrefixMatcher(content);
}

for (Map.Entry<String, List<QueryBuilderRelation>> parameter : collect.getRelationMap().entrySet()) {
for (QueryBuilderRelation relation : parameter.getValue()) {
LookupElementBuilder element = LookupElementBuilder
Expand Down Expand Up @@ -162,45 +173,19 @@ protected void addCompletions(@NotNull CompletionParameters completionParameters

QueryBuilderMethodReferenceParser qb = getQueryBuilderParser(methodMatchParameter.getMethodReference());
QueryBuilderScopeContext collect = qb.collect();
buildLookupElements(completionResultSet, collect);

for (QueryBuilderCompletionContribution contribution : QueryBuilderUtil.guestCompletionContribution(content)) {
if (contribution.type() == QueryBuilderCompletionContributionType.FUNCTION) {
for (Map.Entry<String, String> entry : DoctrineUtil.getDoctrineOrmFunctions(project).entrySet()) {
LookupElementBuilder lookup = LookupElementBuilder.create(entry.getKey().toUpperCase())
.withTypeText("FunctionNode")
.withIcon(Symfony2Icons.DOCTRINE_WEAK);

// "test.test"
if (content == null || content.isBlank() || content.matches("^[\\w+.]+$")) {
buildLookupElements(completionResultSet, collect);
return;
}

// "foo test.test"
Matcher matcher = Pattern.compile("(\\w+)\\.(\\w+)$").matcher(content);
if (matcher.find()) {
String table = matcher.group(1);
String field = matcher.group(2);

buildLookupElements(completionResultSet.withPrefixMatcher(table + "." + field), collect);
}

// "foo test."
Matcher matcher2 = Pattern.compile("(\\w+)\\.$").matcher(content);
if (matcher2.find()) {
String prefix = matcher2.group(1) + ".";
buildLookupElements(completionResultSet.withPrefixMatcher(prefix), collect);
}

// "foo, test.test"
// "(test.test"
Matcher matcher3 = Pattern.compile("[(|,]\\s*(\\w+)$").matcher(content);
if (matcher3.find()) {
String prefix = matcher3.group(1);
buildLookupElements(completionResultSet.withPrefixMatcher(prefix), collect);
}

for (Map.Entry<String, String> entry : DoctrineUtil.getDoctrineOrmFunctions(project).entrySet()) {
LookupElementBuilder lookup = LookupElementBuilder.create(entry.getKey().toUpperCase())
.withTypeText("FunctionNode")
.withIcon(Symfony2Icons.DOCTRINE_WEAK);

completionResultSet.addElement(lookup);
completionResultSet.addElement(lookup);
}
} else if (contribution.type() == QueryBuilderCompletionContributionType.PROPERTY) {
buildLookupElements(completionResultSet.withPrefixMatcher(contribution.prefix()), collect);
}
}
}
});
Expand All @@ -213,6 +198,7 @@ protected void addCompletions(@NotNull CompletionParameters completionParameters
return;
}

Project project = psiElement.getProject();
MethodMatcher.MethodMatchParameter methodMatchParameter = new MethodMatcher.StringParameterMatcher(psiElement.getContext())
.withSignature(WHERES)
.match();
Expand All @@ -230,29 +216,11 @@ protected void addCompletions(@NotNull CompletionParameters completionParameters
QueryBuilderScopeContext collect = qb.collect();

String content = PsiElementUtils.getStringBeforeCursor(parent, completionParameters.getOffset());
if (content == null || content.isBlank() || content.matches("^[\\w+.]+$")) {
buildLookupElements(completionResultSet, collect);
return;
}

Matcher matcher = Pattern.compile("(\\w+)\\.(\\w+)$").matcher(content);
if (matcher.find()) {
String table = matcher.group(1);
String field = matcher.group(2);

buildLookupElements(completionResultSet.withPrefixMatcher(table + "." + field), collect);
}

Matcher matcher2 = Pattern.compile("(\\w+)\\.$").matcher(content);
if (matcher2.find()) {
String prefix = matcher2.group(1) + ".";
buildLookupElements(completionResultSet.withPrefixMatcher(prefix), collect);
}

Matcher matcher3 = Pattern.compile("(AND|OR|WHERE|NOT|[!=><]+)\\s+(\\w+)$").matcher(content);
if (matcher3.find()) {
String prefix = matcher3.group(2);
buildLookupElements(completionResultSet.withPrefixMatcher(prefix), collect);
for (QueryBuilderCompletionContribution contribution : QueryBuilderUtil.guestCompletionContribution(content)) {
if (contribution.type() == QueryBuilderCompletionContributionType.PROPERTY) {
buildLookupElements(completionResultSet.withPrefixMatcher(contribution.prefix()), collect);
}
}

// $qb->andWhere('foo.id = ":foo_id"')
Expand All @@ -262,12 +230,12 @@ protected void addCompletions(@NotNull CompletionParameters completionParameters
private void addParameterNameCompletion(CompletionResultSet completionResultSet, String content) {
// test.test = :
// test.test =
Matcher matcher = Pattern.compile("(\\w+)\\.(\\w+)[\\s+]*[!=><]+[\\s+]*(?<colon>:*)$").matcher(content);
Matcher matcher = Pattern.compile("([\\w_]+)\\.([\\w_]+)[\\s+]*[!=><]+[\\s+]*(?<colon>:*)$").matcher(content);
boolean hasMatch = matcher.find();

if (!hasMatch) {
// test.test = :a
matcher = Pattern.compile("(\\w+)\\.(\\w+)[\\s+]*[!=><]+[\\s+]*(?<colon>:)(?<ident>\\w+)$").matcher(content);
matcher = Pattern.compile("([\\w_]+)\\.(\\w+)[\\s+]*[!=><]+[\\s+]*(?<colon>:)(?<ident>\\w+)$").matcher(content);
hasMatch = matcher.find();
if (hasMatch) {
completionResultSet = completionResultSet.withPrefixMatcher(":" + matcher.group("ident"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict;

import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.jetbrains.annotations.NotNull;

/**
* @author Daniel Espendiller <[email protected]>
*/
public record QueryBuilderCompletionContribution(@NotNull QueryBuilderCompletionContributionType type, @NotNull String prefix) {
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(this.type)
.append(this.prefix)
.toHashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict;

/**
* @author Daniel Espendiller <[email protected]>
*/
public enum QueryBuilderCompletionContributionType {
PROPERTY,
FUNCTION
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.util;

import fr.adrienbrault.idea.symfony2plugin.doctrine.ObjectRepositoryTypeProvider;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict.QueryBuilderCompletionContribution;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict.QueryBuilderCompletionContributionType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -66,4 +69,57 @@ public static String getFieldString(@NotNull String content, int offset) {

return field;
}

public static Collection<QueryBuilderCompletionContribution> guestCompletionContribution(@Nullable String content) {
Collection<QueryBuilderCompletionContribution> contributions = new ArrayList<>();

if (content == null || content.isBlank()) {
contributions.add(new QueryBuilderCompletionContribution(QueryBuilderCompletionContributionType.PROPERTY, ""));
contributions.add(new QueryBuilderCompletionContribution(QueryBuilderCompletionContributionType.FUNCTION, ""));
}

if (content != null) {
if (content.matches("^[\\w+_.]+$")) {
contributions.add(new QueryBuilderCompletionContribution(QueryBuilderCompletionContributionType.PROPERTY, content));
}

Matcher matcher = Pattern.compile("([\\w_]+)\\.([\\w_]+)$").matcher(content);
if (matcher.find()) {
String table = matcher.group(1);
String field = matcher.group(2);

contributions.add(new QueryBuilderCompletionContribution(QueryBuilderCompletionContributionType.PROPERTY, table + "." + field));
}

// "foo, test.test"
// "(test.test"
Matcher matcher3 = Pattern.compile("[(|,]\\s*([\\w_]+)$").matcher(content);
if (matcher3.find()) {
contributions.add(new QueryBuilderCompletionContribution(QueryBuilderCompletionContributionType.PROPERTY, matcher3.group(1)));
}

// "test"
// ", test"
Matcher matcher2 = Pattern.compile("[(|,]\\s*([\\w_]+)$").matcher(content);
if (matcher2.find()) {
contributions.add(new QueryBuilderCompletionContribution(QueryBuilderCompletionContributionType.FUNCTION, matcher2.group(1)));
} else {
Matcher matcherX = Pattern.compile("^([\\w_]+)$").matcher(content);
if (matcherX.find()) {
contributions.add(new QueryBuilderCompletionContribution(QueryBuilderCompletionContributionType.FUNCTION, matcherX.group(1)));
}
}

// "AND test"
// "AND test."
// "> test"
// "> test."
Matcher matcherY = Pattern.compile("(AND|OR|WHERE|NOT|=|>|<)\\s+([\\w_]+[.]*)$").matcher(content);
if (matcherY.find()) {
contributions.add(new QueryBuilderCompletionContribution(QueryBuilderCompletionContributionType.PROPERTY, matcherY.group(2)));
}
}

return new HashSet<>(contributions);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fr.adrienbrault.idea.symfony2plugin.tests.doctrine.querybuilder.util;

import com.intellij.openapi.util.Pair;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict.QueryBuilderCompletionContribution;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict.QueryBuilderCompletionContributionType;
import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.util.QueryBuilderUtil;
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;

Expand Down Expand Up @@ -53,4 +55,43 @@ public void testGetFieldString() {
assertEquals(item.getSecond(), string);
}
}

public void testGuestCompletionContribution() {
Collection<QueryBuilderCompletionContribution> contributions = QueryBuilderUtil.guestCompletionContribution("test_foo.te");
assertEquals(1, contributions.size());
assertTrue(contributions.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.te".equals(c.prefix())));

Collection<QueryBuilderCompletionContribution> contributions2 = QueryBuilderUtil.guestCompletionContribution("");
assertEquals(2, contributions2.size());
assertTrue(contributions2.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && c.prefix().isEmpty()));
assertTrue(contributions2.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.FUNCTION && c.prefix().isEmpty()));

Collection<QueryBuilderCompletionContribution> contributions3 = QueryBuilderUtil.guestCompletionContribution("test_foo.");
assertEquals(1, contributions3.size());
assertTrue(contributions3.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.".equals(c.prefix())));

Collection<QueryBuilderCompletionContribution> contributions4 = QueryBuilderUtil.guestCompletionContribution("foo, test_foo.test_foo");
assertEquals(1, contributions4.size());
assertTrue(contributions4.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.test_foo".equals(c.prefix())));

Collection<QueryBuilderCompletionContribution> contributions5 = QueryBuilderUtil.guestCompletionContribution("(test_foo.test_foo");
assertEquals(1, contributions5.size());
assertTrue(contributions5.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.test_foo".equals(c.prefix())));

Collection<QueryBuilderCompletionContribution> contributions6 = QueryBuilderUtil.guestCompletionContribution("test_foo");
assertEquals(2, contributions6.size());
assertTrue(contributions6.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.FUNCTION && "test_foo".equals(c.prefix())));

Collection<QueryBuilderCompletionContribution> contributions7 = QueryBuilderUtil.guestCompletionContribution("= test_foo.a");
assertEquals(1, contributions7.size());
assertTrue(contributions7.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.a".equals(c.prefix())));

Collection<QueryBuilderCompletionContribution> contributions8 = QueryBuilderUtil.guestCompletionContribution("= test_foo");
assertEquals(1, contributions8.size());
assertTrue(contributions8.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo".equals(c.prefix())));

Collection<QueryBuilderCompletionContribution> contributions9 = QueryBuilderUtil.guestCompletionContribution("AND test_foo.");
assertEquals(1, contributions9.size());
assertTrue(contributions9.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.".equals(c.prefix())));
}
}

0 comments on commit 009dc56

Please sign in to comment.