Skip to content

Commit af5fdc0

Browse files
committed
Refactor attribute scope validation by introducing PhpAttributeScopeValidator, consolidating logic previously scattered across classes.
1 parent 3484b4f commit af5fdc0

File tree

3 files changed

+304
-107
lines changed

3 files changed

+304
-107
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/completion/PhpAttributeCompletionContributor.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull
8383

8484
Collection<LookupElement> lookupElements = new ArrayList<>();
8585

86-
// Check if we're before a public method (using shared logic from PhpAttributeCompletionPopupHandlerCompletionConfidence)
87-
Method method = PhpAttributeCompletionPopupHandlerCompletionConfidence.getMethod(position);
86+
// Check if we're before a public method (using shared scope validator)
87+
Method method = PhpAttributeScopeValidator.getMethod(position);
8888
if (method != null) {
8989
// Method-level attribute completions
9090
PhpClass containingClass = method.getContainingClass();
@@ -101,7 +101,7 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull
101101
}
102102
} else {
103103
// Check if we're before a property/field
104-
Field field = PhpAttributeCompletionPopupHandlerCompletionConfidence.getField(position);
104+
Field field = PhpAttributeScopeValidator.getField(position);
105105
if (field != null) {
106106
// Property-level attribute completions
107107
PhpClass containingClass = field.getContainingClass();
@@ -110,7 +110,7 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull
110110
}
111111
} else {
112112
// Check if we're before a class
113-
PhpClass phpClass = PhpAttributeCompletionPopupHandlerCompletionConfidence.getPhpClass(position);
113+
PhpClass phpClass = PhpAttributeScopeValidator.getPhpClass(position);
114114
if (phpClass != null) {
115115
// Class-level attribute completions
116116
if (AddRouteAttributeIntention.isControllerClass(phpClass)) {
@@ -124,11 +124,8 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull
124124
}
125125
}
126126

127-
// Stop here - don't show other completions when typing "#" for attributes
128-
if (!lookupElements.isEmpty()) {
129-
result.addAllElements(lookupElements);
130-
result.stopHere();
131-
}
127+
result.addAllElements(lookupElements);
128+
result.stopHere();
132129
}
133130

134131
/**
@@ -504,20 +501,20 @@ public void handleInsert(@NotNull InsertionContext context, @NotNull LookupEleme
504501
return;
505502
}
506503

507-
// Determine the target context (method, field, or class) dynamically
504+
// Determine the target context (method, field, or class) dynamically using shared scope validator
508505
PhpClass phpClass;
509-
Method targetMethod = PhpAttributeCompletionPopupHandlerCompletionConfidence.getMethod(originalElement);
506+
Method targetMethod = PhpAttributeScopeValidator.getMethod(originalElement);
510507
if (targetMethod != null) {
511508
// We're in a method context
512509
phpClass = targetMethod.getContainingClass();
513510
} else {
514511
// Try field context
515-
Field targetField = PhpAttributeCompletionPopupHandlerCompletionConfidence.getField(originalElement);
512+
Field targetField = PhpAttributeScopeValidator.getField(originalElement);
516513
if (targetField != null) {
517514
phpClass = targetField.getContainingClass();
518515
} else {
519516
// Try class context
520-
phpClass = PhpAttributeCompletionPopupHandlerCompletionConfidence.getPhpClass(originalElement);
517+
phpClass = PhpAttributeScopeValidator.getPhpClass(originalElement);
521518
if (phpClass == null) {
522519
return;
523520
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/completion/PhpAttributeCompletionPopupHandlerCompletionConfidence.java

Lines changed: 11 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,9 @@
99
import com.intellij.psi.PsiFile;
1010
import com.intellij.psi.PsiWhiteSpace;
1111
import com.intellij.util.ThreeState;
12-
import com.jetbrains.php.lang.parser.PhpElementTypes;
1312
import com.jetbrains.php.lang.psi.PhpFile;
14-
import com.jetbrains.php.lang.psi.PhpPsiUtil;
15-
import com.jetbrains.php.lang.psi.elements.*;
1613
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
1714
import org.jetbrains.annotations.NotNull;
18-
import org.jetbrains.annotations.Nullable;
1915

2016
public class PhpAttributeCompletionPopupHandlerCompletionConfidence {
2117
/**
@@ -28,19 +24,23 @@ public static class PhpAttributeCompletionConfidence extends CompletionConfidenc
2824
@NotNull
2925
@Override
3026
public ThreeState shouldSkipAutopopup(@NotNull Editor editor, @NotNull PsiElement contextElement, @NotNull PsiFile psiFile, int offset) {
31-
if (offset <= 0 || !(psiFile instanceof PhpFile) || !Symfony2ProjectComponent.isEnabled(editor.getProject())) {
27+
if (offset <= 0 || !(psiFile instanceof PhpFile)) {
3228
return ThreeState.UNSURE;
3329
}
3430

35-
// Check if we're before a method, class, or field
36-
if (getMethod(contextElement) == null && getPhpClass(contextElement) == null && getField(contextElement) == null) {
31+
Project project = editor.getProject();
32+
if (!Symfony2ProjectComponent.isEnabled(project)) {
3733
return ThreeState.UNSURE;
3834
}
3935

4036
// Check if there's a "#" before the cursor in the document
4137
CharSequence documentText = editor.getDocument().getCharsSequence();
4238
if (documentText.charAt(offset - 1) == '#' && psiFile.findElementAt(offset - 2) instanceof PsiWhiteSpace) {
43-
return ThreeState.NO;
39+
// Check if we should provide attribute completions for this context
40+
// (controller class, twig component, twig extension, etc.)
41+
if (PhpAttributeScopeValidator.shouldProvideAttributeCompletions(contextElement, project)) {
42+
return ThreeState.NO;
43+
}
4444
}
4545

4646
return ThreeState.UNSURE;
@@ -71,97 +71,14 @@ public static class PhpAttributeAutoPopupHandler extends TypedHandlerDelegate {
7171
return Result.CONTINUE;
7272
}
7373

74-
// Check if we're before a method, class, or field
75-
if (getMethod(element) == null && getField(element) == null && getPhpClass(element) == null) {
74+
// Check if we should provide attribute completions for this context
75+
// (controller class, twig component, twig extension, etc.)
76+
if (!PhpAttributeScopeValidator.shouldProvideAttributeCompletions(element, project)) {
7677
return Result.CONTINUE;
7778
}
7879

7980
AutoPopupController.getInstance(project).scheduleAutoPopup(editor);
8081
return Result.STOP;
8182
}
8283
}
83-
84-
/**
85-
* Finds a public method associated with the given element.
86-
* Returns the method if the element is a child of a method or if the next sibling is a method.
87-
*
88-
* @param element The PSI element to check
89-
* @return The public method if found, null otherwise
90-
*/
91-
public static @Nullable Method getMethod(@NotNull PsiElement element) {
92-
Method foundMethod = null;
93-
94-
if (element.getParent() instanceof Method method) {
95-
foundMethod = method;
96-
} else if (PhpPsiUtil.getNextSiblingIgnoreWhitespace(element, true) instanceof Method method) {
97-
foundMethod = method;
98-
}
99-
100-
return foundMethod != null && foundMethod.getAccess().isPublic()
101-
? foundMethod
102-
: null;
103-
}
104-
105-
/**
106-
* Finds a PhpClass associated with the given element.
107-
* Returns the class if the element is a child of a class or if the next sibling is a class.
108-
* Also handles cases where we're in the middle of an attribute list.
109-
*
110-
* @param element The PSI element to check
111-
* @return The PhpClass if found, null otherwise
112-
*/
113-
public static @Nullable PhpClass getPhpClass(@NotNull PsiElement element) {
114-
if (element.getParent() instanceof PhpClass phpClass) {
115-
return phpClass;
116-
}
117-
118-
// with use statement given
119-
PsiElement nextSiblingIgnoreWhitespace = PhpPsiUtil.getNextSiblingIgnoreWhitespace(element, true);
120-
if (nextSiblingIgnoreWhitespace instanceof PhpClass phpClass) {
121-
return phpClass;
122-
}
123-
124-
// no use statements
125-
if (nextSiblingIgnoreWhitespace != null && nextSiblingIgnoreWhitespace.getNode().getElementType() == PhpElementTypes.NON_LAZY_GROUP_STATEMENT) {
126-
if (nextSiblingIgnoreWhitespace.getFirstChild() instanceof PhpClass phpClass) {
127-
return phpClass;
128-
}
129-
}
130-
131-
return null;
132-
}
133-
134-
/**
135-
* Finds a Field (property) associated with the given element.
136-
* Returns the field if the element is a child of a field or if the next sibling is a field.
137-
*
138-
* @param element The PSI element to check
139-
* @return The Field if found, null otherwise
140-
*/
141-
public static @Nullable Field getField(@NotNull PsiElement element) {
142-
PsiElement nextSiblingIgnoreWhitespace = PhpPsiUtil.getNextSiblingIgnoreWhitespace(element, true);
143-
if (nextSiblingIgnoreWhitespace instanceof PhpModifierList phpModifierList && phpModifierList.hasPublic()) {
144-
if (phpModifierList.getNextPsiSibling() instanceof Field field) {
145-
return field;
146-
}
147-
}
148-
149-
if (nextSiblingIgnoreWhitespace instanceof PhpPsiElement phpPsiElement) {
150-
PhpPsiElement firstPsiChild = phpPsiElement.getFirstPsiChild();
151-
if (firstPsiChild instanceof PhpModifierList phpModifierList && phpModifierList.hasPublic()) {
152-
PhpPsiElement nextPsiSibling = phpModifierList.getNextPsiSibling();
153-
154-
if (nextPsiSibling instanceof Field field) {
155-
return field;
156-
} else if(nextPsiSibling instanceof PhpFieldType phpFieldType) {
157-
PhpPsiElement nextPsiSibling1 = phpFieldType.getNextPsiSibling();
158-
if (nextPsiSibling1 instanceof Field field1) {
159-
return field1;
160-
}
161-
}
162-
}
163-
}
164-
165-
return null;
166-
}
16784
}

0 commit comments

Comments
 (0)