33import com .intellij .codeInsight .completion .CompletionResultSet ;
44import com .intellij .patterns .PatternCondition ;
55import com .intellij .patterns .PlatformPatterns ;
6+ import com .intellij .patterns .PsiElementPattern ;
67import com .intellij .psi .PsiElement ;
78import com .intellij .util .ProcessingContext ;
89import com .jetbrains .php .lang .documentation .phpdoc .lexer .PhpDocTokenTypes ;
910import com .jetbrains .php .lang .documentation .phpdoc .parser .PhpDocElementTypes ;
1011import 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 ;
1115import com .jetbrains .php .lang .psi .elements .StringLiteralExpression ;
1216import de .espend .idea .php .annotation .util .AnnotationUtil ;
1317import fr .adrienbrault .idea .symfony2plugin .codeInsight .GotoCompletionProvider ;
1620import fr .adrienbrault .idea .symfony2plugin .codeInsight .GotoCompletionRegistrarParameter ;
1721import fr .adrienbrault .idea .symfony2plugin .security .utils .VoterUtil ;
1822import fr .adrienbrault .idea .symfony2plugin .util .PhpElementsUtil ;
23+ import org .apache .commons .lang .StringUtils ;
1924import org .jetbrains .annotations .NotNull ;
2025
2126import java .util .Collection ;
2934 */
3035public 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}
0 commit comments