Skip to content

Commit ee61a91

Browse files
committed
#1429 fix security matcher starting string replacement and support php attributes
1 parent 46a9925 commit ee61a91

File tree

2 files changed

+81
-17
lines changed

2 files changed

+81
-17
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/security/AnnotationExpressionGotoCompletionRegistrar.java

+59-17
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import com.intellij.codeInsight.completion.CompletionResultSet;
44
import com.intellij.patterns.PatternCondition;
55
import com.intellij.patterns.PlatformPatterns;
6+
import com.intellij.patterns.PsiElementPattern;
67
import com.intellij.psi.PsiElement;
78
import com.intellij.util.ProcessingContext;
89
import com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes;
910
import com.jetbrains.php.lang.documentation.phpdoc.parser.PhpDocElementTypes;
1011
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
12+
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
13+
import com.jetbrains.php.lang.psi.elements.ParameterList;
14+
import com.jetbrains.php.lang.psi.elements.PhpAttribute;
1115
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
1216
import de.espend.idea.php.annotation.util.AnnotationUtil;
1317
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
@@ -16,6 +20,7 @@
1620
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
1721
import fr.adrienbrault.idea.symfony2plugin.security.utils.VoterUtil;
1822
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
23+
import org.apache.commons.lang.StringUtils;
1924
import org.jetbrains.annotations.NotNull;
2025

2126
import java.util.Collection;
@@ -29,22 +34,43 @@
2934
*/
3035
public class AnnotationExpressionGotoCompletionRegistrar implements GotoCompletionRegistrar {
3136

32-
private static final String SECURITY_ANNOTATION = "Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Security";
37+
private static final String SECURITY_ANNOTATION = "\\Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Security";
3338

3439
@Override
3540
public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
3641
// "@Security("is_granted('POST_SHOW', post) and has_role('ROLE_ADMIN')")"
3742
registrar.register(
38-
PlatformPatterns.psiElement(PhpDocTokenTypes.DOC_STRING)
43+
PlatformPatterns.or(getDocTagStringPattern(), getAttributeStringPattern()),
44+
MyGotoCompletionProvider::new
45+
);
46+
}
47+
48+
@NotNull
49+
private PsiElementPattern.Capture<PsiElement> getAttributeStringPattern() {
50+
// #[Security("is_granted('POST_SHOW')")]
51+
return PlatformPatterns.psiElement().withElementType(PlatformPatterns.elementType().or(
52+
PhpTokenTypes.STRING_LITERAL_SINGLE_QUOTE,
53+
PhpTokenTypes.STRING_LITERAL
54+
))
55+
.withParent(PlatformPatterns.psiElement(StringLiteralExpression.class)
56+
.withParent(PlatformPatterns.psiElement(ParameterList.class)
57+
.withParent(PlatformPatterns.psiElement(PhpAttribute.class)
58+
.with(PhpDocInstancePatternCondition.INSTANCE)
59+
)
60+
)
61+
);
62+
}
63+
64+
@NotNull
65+
private PsiElementPattern.Capture<PsiElement> getDocTagStringPattern() {
66+
return PlatformPatterns.psiElement(PhpDocTokenTypes.DOC_STRING)
3967
.withParent(PlatformPatterns.psiElement(StringLiteralExpression.class)
4068
.withParent(PlatformPatterns.psiElement(PhpDocElementTypes.phpDocAttributeList)
4169
.withParent(PlatformPatterns.psiElement(PhpDocTag.class)
4270
.with(PhpDocInstancePatternCondition.INSTANCE)
4371
)
4472
)
45-
),
46-
MyGotoCompletionProvider::new
47-
);
73+
);
4874
}
4975

5076
/**
@@ -64,12 +90,13 @@ public void getLookupElements(@NotNull GotoCompletionProviderLookupArguments arg
6490
// find caret position:
6591
// - "has_role('"
6692
// - "has_role('YAML_ROLE_"
67-
if(!blockNamePrefix.matches("^.*(has_role|is_granted)\\s*\\(\\s*'[\\w-]*$")) {
93+
if(!blockNamePrefix.matches("^.*(has_role|is_granted)\\s*\\(\\s*['|\"][\\w-]*$")) {
6894
return;
6995
}
7096

7197
// clear prefix caret string; for a clean completion independent from inside content
72-
CompletionResultSet myResultSet = resultSet.withPrefixMatcher("");
98+
String substring = blockNamePrefix.replaceAll("^(.*(has_role|is_granted)\\s*\\(\\s*['|\"])", "");
99+
CompletionResultSet myResultSet = resultSet.withPrefixMatcher(substring);
73100

74101
VoterUtil.LookupElementPairConsumer consumer = new VoterUtil.LookupElementPairConsumer();
75102
VoterUtil.visitAttribute(getProject(), consumer);
@@ -79,17 +106,27 @@ public void getLookupElements(@NotNull GotoCompletionProviderLookupArguments arg
79106
@NotNull
80107
@Override
81108
public Collection<PsiElement> getPsiTargets(PsiElement element) {
82-
if(getElement().getNode().getElementType() != PhpDocTokenTypes.DOC_STRING) {
83-
return Collections.emptyList();
109+
String contents = null;
110+
if(getElement().getNode().getElementType() == PhpDocTokenTypes.DOC_STRING) {
111+
// @Security
112+
PsiElement parent = getElement().getParent();
113+
if(!(parent instanceof StringLiteralExpression)) {
114+
return Collections.emptyList();
115+
}
116+
117+
contents = ((StringLiteralExpression) parent).getContents();
118+
} else {
119+
// @Security
120+
PsiElement parent = getElement().getParent();
121+
if (parent instanceof StringLiteralExpression) {
122+
contents = ((StringLiteralExpression) parent).getContents();
123+
}
84124
}
85125

86-
PsiElement parent = getElement().getParent();
87-
if(!(parent instanceof StringLiteralExpression)) {
126+
if (StringUtils.isBlank(contents)) {
88127
return Collections.emptyList();
89128
}
90129

91-
String contents = ((StringLiteralExpression) parent).getContents();
92-
93130
Collection<String> roles = new HashSet<>();
94131
for (String regex : new String[]{"is_granted\\s*\\(\\s*['|\"]([^'\"]+)['|\"]\\s*[\\)|,]", "has_role\\s*\\(\\s*['|\"]([^'\"]+)['|\"]\\s*\\)"}) {
95132
Matcher matcher = Pattern.compile(regex).matcher(contents);
@@ -118,16 +155,21 @@ public Collection<PsiElement> getPsiTargets(PsiElement element) {
118155
* Check if given PhpDocTag is instance of given Annotation class
119156
*/
120157
private static class PhpDocInstancePatternCondition extends PatternCondition<PsiElement> {
121-
private static PhpDocInstancePatternCondition INSTANCE = new PhpDocInstancePatternCondition();
158+
private static final PhpDocInstancePatternCondition INSTANCE = new PhpDocInstancePatternCondition();
122159

123160
PhpDocInstancePatternCondition() {
124-
super("PhpDoc Annotation Instance");
161+
super("PhpDoc/Attribute Instance");
125162
}
126163

127164
@Override
128165
public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext processingContext) {
129-
return psiElement instanceof PhpDocTag
130-
&& PhpElementsUtil.isEqualClassName(AnnotationUtil.getAnnotationReference((PhpDocTag) psiElement), SECURITY_ANNOTATION);
166+
if (psiElement instanceof PhpDocTag) {
167+
return PhpElementsUtil.isEqualClassName(AnnotationUtil.getAnnotationReference((PhpDocTag) psiElement), SECURITY_ANNOTATION);
168+
} else if (psiElement instanceof PhpAttribute) {
169+
return SECURITY_ANNOTATION.equals(((PhpAttribute) psiElement).getFQN());
170+
}
171+
172+
return false;
131173
}
132174
}
133175
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/security/AnnotationExpressionGotoCompletionRegistrarTest.java

+22
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ public void testSecurityAnnotationProvidesCompletion() {
7474
);
7575
}
7676

77+
public void testSecurityAttributeProvidesCompletion() {
78+
assertCompletionContains(
79+
"test.php",
80+
"<?php\n" +
81+
"use Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Security;\n" +
82+
"#[Security('is_granted(\"YAML_ROLE<caret>_USER_FOOBAR\", post)')]\n" +
83+
"function test() {};\n",
84+
"YAML_ROLE_USER_FOOBAR"
85+
);
86+
}
87+
7788
public void testSecurityAnnotationProvidesRoleNavigation() {
7889
assertNavigationMatch(
7990
"test.php",
@@ -123,4 +134,15 @@ public void testSecurityAnnotationProvidesRoleNavigation() {
123134
PlatformPatterns.psiElement()
124135
);
125136
}
137+
138+
public void testSecurityAttributeProvidesRoleNavigation() {
139+
assertNavigationMatch(
140+
"test.php",
141+
"<?php\n" +
142+
"use Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Security;\n" +
143+
"#[Security('is_granted(\"YAML_ROLE<caret>_USER_FOOBAR\", post)')]\n" +
144+
"function test() {};\n",
145+
PlatformPatterns.psiElement()
146+
);
147+
}
126148
}

0 commit comments

Comments
 (0)