From 5073618ed99a73d2a6baedbe5f262a56c0d2aff5 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sun, 14 Apr 2024 13:46:22 +0200 Subject: [PATCH] #2334 optimize prefix matcher for Doctrine querybuilder parameter, to reduce conflicting PhpStorm implementation --- .../QueryBuilderCompletionContributor.java | 98 +++++++------------ .../QueryBuilderCompletionContribution.java | 17 ++++ ...ueryBuilderCompletionContributionType.java | 9 ++ .../querybuilder/util/QueryBuilderUtil.java | 63 ++++++++++++ ...QueryBuilderCompletionContributorTest.java | 6 +- .../util/QueryBuilderUtilTest.java | 49 ++++++++++ 6 files changed, 176 insertions(+), 66 deletions(-) create mode 100644 src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/dict/QueryBuilderCompletionContribution.java create mode 100644 src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/dict/QueryBuilderCompletionContributionType.java diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/QueryBuilderCompletionContributor.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/QueryBuilderCompletionContributor.java index 6d471cdd5..73a91d1bd 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/QueryBuilderCompletionContributor.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/QueryBuilderCompletionContributor.java @@ -17,15 +17,19 @@ 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; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.regex.Matcher; @@ -108,7 +112,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; } @@ -122,6 +126,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> parameter : collect.getRelationMap().entrySet()) { for (QueryBuilderRelation relation : parameter.getValue()) { LookupElementBuilder element = LookupElementBuilder @@ -162,45 +174,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 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 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); + } } } }); @@ -230,44 +216,28 @@ 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); + for (QueryBuilderCompletionContribution contribution : QueryBuilderUtil.guestCompletionContribution(content)) { + if (contribution.type() == QueryBuilderCompletionContributionType.PROPERTY) { + buildLookupElements(completionResultSet.withPrefixMatcher(contribution.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); + if (content != null) { + // $qb->andWhere('foo.id = ":foo_id"') + addParameterNameCompletion(completionResultSet.withPrefixMatcher(""), content); } - - // $qb->andWhere('foo.id = ":foo_id"') - addParameterNameCompletion(completionResultSet.withPrefixMatcher(""), content); } - private void addParameterNameCompletion(CompletionResultSet completionResultSet, String content) { + private void addParameterNameCompletion(@NotNull CompletionResultSet completionResultSet, @NotNull String content) { // test.test = : // test.test = - Matcher matcher = Pattern.compile("(\\w+)\\.(\\w+)[\\s+]*[!=><]+[\\s+]*(?:*)$").matcher(content); + Matcher matcher = Pattern.compile("([\\w_]+)\\.([\\w_]+)[\\s+]*[!=><]+[\\s+]*(?:*)$").matcher(content); boolean hasMatch = matcher.find(); if (!hasMatch) { // test.test = :a - matcher = Pattern.compile("(\\w+)\\.(\\w+)[\\s+]*[!=><]+[\\s+]*(?:)(?\\w+)$").matcher(content); + matcher = Pattern.compile("([\\w_]+)\\.(\\w+)[\\s+]*[!=><]+[\\s+]*(?:)(?\\w+)$").matcher(content); hasMatch = matcher.find(); if (hasMatch) { completionResultSet = completionResultSet.withPrefixMatcher(":" + matcher.group("ident")); diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/dict/QueryBuilderCompletionContribution.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/dict/QueryBuilderCompletionContribution.java new file mode 100644 index 000000000..c2188319f --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/dict/QueryBuilderCompletionContribution.java @@ -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 + */ +public record QueryBuilderCompletionContribution(@NotNull QueryBuilderCompletionContributionType type, @NotNull String prefix) { + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(this.type) + .append(this.prefix) + .toHashCode(); + } +} diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/dict/QueryBuilderCompletionContributionType.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/dict/QueryBuilderCompletionContributionType.java new file mode 100644 index 000000000..96732ff72 --- /dev/null +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/dict/QueryBuilderCompletionContributionType.java @@ -0,0 +1,9 @@ +package fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dict; + +/** + * @author Daniel Espendiller + */ +public enum QueryBuilderCompletionContributionType { + PROPERTY, + FUNCTION +} diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/util/QueryBuilderUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/util/QueryBuilderUtil.java index 4d54883e0..fc309e4d1 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/util/QueryBuilderUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/querybuilder/util/QueryBuilderUtil.java @@ -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; @@ -66,4 +69,64 @@ public static String getFieldString(@NotNull String content, int offset) { return field; } + + public static Collection guestCompletionContribution(@Nullable String content) { + Collection 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))); + } + } + + // "(test + // "( test + Matcher matcherU = Pattern.compile("\\(\\s*([\\w_]+)$").matcher(content); + if (matcherU.find()) { + contributions.add(new QueryBuilderCompletionContribution(QueryBuilderCompletionContributionType.PROPERTY, matcherU.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); + } } diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/querybuilder/QueryBuilderCompletionContributorTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/querybuilder/QueryBuilderCompletionContributorTest.java index 380a3af7c..f747a598a 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/querybuilder/QueryBuilderCompletionContributorTest.java +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/querybuilder/QueryBuilderCompletionContributorTest.java @@ -1,5 +1,7 @@ package fr.adrienbrault.idea.symfony2plugin.tests.doctrine.querybuilder; +import com.intellij.codeInsight.lookup.LookupElementPresentation; +import com.jetbrains.php.lang.PhpFileType; import fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.QueryBuilderCompletionContributor; import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase; import org.jetbrains.annotations.NotNull; @@ -42,8 +44,8 @@ public void testCompletionForSelect() { public void testCompletionForSelectField() { assertCompletionContains( "test.php", - createQueryBuilderWrap("$qb->select('foobar, foobar.');"), - "foobar.id", "foobar.name" + createQueryBuilderWrap("$qb->select('foobar, foobar.name');"), + "foobar.name" ); } diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/querybuilder/util/QueryBuilderUtilTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/querybuilder/util/QueryBuilderUtilTest.java index df83c3391..8a76461f6 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/querybuilder/util/QueryBuilderUtilTest.java +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/querybuilder/util/QueryBuilderUtilTest.java @@ -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; @@ -53,4 +55,51 @@ public void testGetFieldString() { assertEquals(item.getSecond(), string); } } + + public void testGuestCompletionContribution() { + Collection t1 = QueryBuilderUtil.guestCompletionContribution("test_foo.te"); + assertEquals(1, t1.size()); + assertTrue(t1.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.te".equals(c.prefix()))); + + Collection t2 = QueryBuilderUtil.guestCompletionContribution(""); + assertEquals(2, t2.size()); + assertTrue(t2.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && c.prefix().isEmpty())); + assertTrue(t2.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.FUNCTION && c.prefix().isEmpty())); + + Collection t3 = QueryBuilderUtil.guestCompletionContribution("test_foo."); + assertEquals(1, t3.size()); + assertTrue(t3.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.".equals(c.prefix()))); + + Collection t4 = QueryBuilderUtil.guestCompletionContribution("foo, test_foo.test_foo"); + assertEquals(1, t4.size()); + assertTrue(t4.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.test_foo".equals(c.prefix()))); + + Collection t5 = QueryBuilderUtil.guestCompletionContribution("(test_foo.test_foo"); + assertEquals(1, t5.size()); + assertTrue(t5.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.test_foo".equals(c.prefix()))); + + Collection t6 = QueryBuilderUtil.guestCompletionContribution("test_foo"); + assertEquals(2, t6.size()); + assertTrue(t6.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.FUNCTION && "test_foo".equals(c.prefix()))); + + Collection t7 = QueryBuilderUtil.guestCompletionContribution("= test_foo.a"); + assertEquals(1, t7.size()); + assertTrue(t7.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.a".equals(c.prefix()))); + + Collection t8 = QueryBuilderUtil.guestCompletionContribution("= test_foo"); + assertEquals(1, t8.size()); + assertTrue(t8.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo".equals(c.prefix()))); + + Collection t9 = QueryBuilderUtil.guestCompletionContribution("AND test_foo."); + assertEquals(1, t9.size()); + assertTrue(t9.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.".equals(c.prefix()))); + + Collection t10 = QueryBuilderUtil.guestCompletionContribution("FOO(test_foo.fo"); + assertEquals(1, t10.size()); + assertTrue(t10.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.fo".equals(c.prefix()))); + + Collection t11 = QueryBuilderUtil.guestCompletionContribution("FOO(test_foo.fo"); + assertEquals(1, t11.size()); + assertTrue(t11.stream().anyMatch(c -> c.type() == QueryBuilderCompletionContributionType.PROPERTY && "test_foo.fo".equals(c.prefix()))); + } }