2 * Copyright 2006 Sascha Weinreuter
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org
.intellij
.plugins
.intelliLang
.pattern
;
18 import com
.intellij
.codeInspection
.LocalInspectionTool
;
19 import com
.intellij
.codeInspection
.LocalQuickFix
;
20 import com
.intellij
.codeInspection
.ProblemDescriptor
;
21 import com
.intellij
.codeInspection
.ProblemsHolder
;
22 import com
.intellij
.ide
.DataManager
;
23 import com
.intellij
.openapi
.project
.Project
;
24 import com
.intellij
.openapi
.util
.Comparing
;
25 import com
.intellij
.openapi
.util
.Key
;
26 import com
.intellij
.openapi
.util
.text
.StringUtil
;
27 import com
.intellij
.psi
.*;
28 import com
.intellij
.psi
.util
.CachedValue
;
29 import com
.intellij
.psi
.util
.CachedValueProvider
;
30 import com
.intellij
.psi
.util
.CachedValuesManager
;
31 import com
.intellij
.psi
.util
.PsiTreeUtil
;
32 import com
.intellij
.refactoring
.JavaRefactoringActionHandlerFactory
;
33 import com
.intellij
.refactoring
.RefactoringActionHandler
;
34 import com
.intellij
.util
.SmartList
;
35 import org
.intellij
.plugins
.intelliLang
.Configuration
;
36 import org
.intellij
.plugins
.intelliLang
.util
.AnnotateFix
;
37 import org
.intellij
.plugins
.intelliLang
.util
.AnnotationUtilEx
;
38 import org
.intellij
.plugins
.intelliLang
.util
.PsiUtilEx
;
39 import org
.intellij
.plugins
.intelliLang
.util
.SubstitutedExpressionEvaluationHelper
;
40 import org
.jetbrains
.annotations
.NonNls
;
41 import org
.jetbrains
.annotations
.NotNull
;
42 import org
.jetbrains
.annotations
.Nullable
;
46 import java
.awt
.event
.ItemEvent
;
47 import java
.awt
.event
.ItemListener
;
48 import java
.text
.MessageFormat
;
49 import java
.util
.List
;
50 import java
.util
.regex
.Pattern
;
51 import java
.util
.regex
.PatternSyntaxException
;
54 * Inspection that validates if string literals, compile-time constants or
55 * substituted expressions match the pattern of the context they're used in.
57 public class PatternValidator
extends LocalInspectionTool
{
58 private static final Key
<CachedValue
<Pattern
>> COMPLIED_PATTERN
= Key
.create("COMPILED_PATTERN");
60 public boolean CHECK_NON_CONSTANT_VALUES
= true;
62 private final Configuration myConfiguration
;
64 public PatternValidator() {
65 myConfiguration
= Configuration
.getInstance();
68 public boolean isEnabledByDefault() {
73 public String
getGroupDisplayName() {
74 return InspectionProvider
.PATTERN_VALIDATION
;
78 public String
getDisplayName() {
79 return "Validate Annotated Patterns";
83 public JComponent
createOptionsPanel() {
84 final JPanel jPanel
= new JPanel(new BorderLayout());
85 final JCheckBox jCheckBox
= new JCheckBox("Flag non compile-time constant expressions");
86 jCheckBox
.setToolTipText(
87 "If checked, the inspection will flag expressions with unknown values " + "and offer to add a substitution (@Subst) annotation");
88 jCheckBox
.setSelected(CHECK_NON_CONSTANT_VALUES
);
89 jCheckBox
.addItemListener(new ItemListener() {
90 public void itemStateChanged(ItemEvent e
) {
91 CHECK_NON_CONSTANT_VALUES
= jCheckBox
.isSelected();
94 jPanel
.add(jCheckBox
, BorderLayout
.NORTH
);
100 public String
getShortName() {
101 return "PatternValidation";
105 public PsiElementVisitor
buildVisitor(@NotNull final ProblemsHolder holder
, boolean isOnTheFly
) {
106 return new JavaElementVisitor() {
108 public final void visitReferenceExpression(PsiReferenceExpression expression
) {
109 visitExpression(expression
);
113 public void visitExpression(PsiExpression expression
) {
114 final PsiElement element
= expression
.getParent();
115 if (element
instanceof PsiExpressionList
) {
116 // this checks method arguments
117 check(expression
, holder
, false);
119 else if (element
instanceof PsiNameValuePair
) {
120 final PsiNameValuePair valuePair
= (PsiNameValuePair
)element
;
121 final String name
= valuePair
.getName();
122 if (name
== null || name
.equals(PsiAnnotation
.DEFAULT_REFERENCED_METHOD_NAME
)) {
123 // check whether @Subst complies with pattern
124 check(expression
, holder
, true);
130 public void visitReturnStatement(PsiReturnStatement statement
) {
131 final PsiExpression returnValue
= statement
.getReturnValue();
132 if (returnValue
!= null) {
133 check(returnValue
, holder
, false);
138 public void visitVariable(PsiVariable var
) {
139 final PsiExpression initializer
= var
.getInitializer();
140 if (initializer
!= null) {
141 // variable/field initializer
142 check(initializer
, holder
, false);
147 public void visitAssignmentExpression(PsiAssignmentExpression expression
) {
148 final PsiExpression e
= expression
.getRExpression();
150 check(e
, holder
, false);
152 visitExpression(expression
);
155 private void check(@NotNull PsiExpression expression
, ProblemsHolder holder
, boolean isAnnotationValue
) {
156 if (expression
instanceof PsiConditionalExpression
) {
157 final PsiConditionalExpression expr
= (PsiConditionalExpression
)expression
;
158 PsiExpression e
= expr
.getThenExpression();
160 check(e
, holder
, isAnnotationValue
);
162 e
= expr
.getElseExpression();
164 check(e
, holder
, isAnnotationValue
);
168 final PsiType type
= expression
.getType();
169 // optimiziation: only check expressions of type String
170 if (type
!= null && PsiUtilEx
.isString(type
)) {
171 final PsiModifierListOwner element
;
172 if (isAnnotationValue
) {
173 final PsiAnnotation psiAnnotation
= PsiTreeUtil
.getParentOfType(expression
, PsiAnnotation
.class);
174 if (psiAnnotation
!= null && myConfiguration
.getSubstAnnotationClass().equals(psiAnnotation
.getQualifiedName())) {
175 element
= PsiTreeUtil
.getParentOfType(expression
, PsiModifierListOwner
.class);
182 element
= AnnotationUtilEx
.getAnnotatedElementFor(expression
, AnnotationUtilEx
.LookupType
.PREFER_CONTEXT
);
184 if (element
!= null && PsiUtilEx
.isLanguageAnnotationTarget(element
)) {
185 PsiAnnotation
[] annotations
= AnnotationUtilEx
.getAnnotationFrom(element
, myConfiguration
.getPatternAnnotationPair(), true);
186 checkExpression(expression
, annotations
, holder
);
194 private void checkExpression(PsiExpression expression
, final PsiAnnotation
[] annotations
, ProblemsHolder holder
) {
195 if (annotations
.length
== 0) return;
196 final PsiAnnotation psiAnnotation
= annotations
[0];
198 // cache compiled pattern with annotation
199 CachedValue
<Pattern
> p
= psiAnnotation
.getUserData(COMPLIED_PATTERN
);
201 final CachedValueProvider
<Pattern
> provider
= new CachedValueProvider
<Pattern
>() {
202 public Result
<Pattern
> compute() {
203 final String pattern
= AnnotationUtilEx
.calcAnnotationValue(psiAnnotation
, "value");
205 if (pattern
!= null) {
207 p
= Pattern
.compile(pattern
);
209 catch (PatternSyntaxException e
) {
210 // pattern stays null
213 return Result
.create(p
, (Object
[])annotations
);
216 p
= CachedValuesManager
.getManager(expression
.getProject()).createCachedValue(provider
, false);
217 psiAnnotation
.putUserData(COMPLIED_PATTERN
, p
);
220 final Pattern pattern
= p
.getValue();
221 if (pattern
== null) return;
223 List
<PsiExpression
> nonConstantElements
= new SmartList
<PsiExpression
>();
224 final Object result
= new SubstitutedExpressionEvaluationHelper(expression
.getProject()).computeExpression(
225 expression
, myConfiguration
.isUseDfaIfAvailable(), false, nonConstantElements
);
226 final String o
= result
== null ?
null : String
.valueOf(result
);
228 if (!pattern
.matcher(o
).matches()) {
229 if (annotations
.length
> 1) {
230 // the last element contains the element's actual annotation
231 final String fqn
= annotations
[annotations
.length
- 1].getQualifiedName();
234 final String name
= StringUtil
.getShortName(fqn
);
235 holder
.registerProblem(expression
, MessageFormat
.format("Expression ''{0}'' doesn''t match ''{1}'' pattern: {2}", o
, name
,
239 holder
.registerProblem(expression
,
240 MessageFormat
.format("Expression ''{0}'' doesn''t match pattern: {1}", o
, pattern
.pattern()));
244 else if (CHECK_NON_CONSTANT_VALUES
) {
245 for (PsiExpression expr
: nonConstantElements
) {
247 if (expr
instanceof PsiReferenceExpression
) {
248 e
= ((PsiReferenceExpression
)expr
).resolve();
250 else if (expr
instanceof PsiMethodCallExpression
) {
251 e
= ((PsiMethodCallExpression
)expr
).getMethodExpression().resolve();
256 final PsiModifierListOwner owner
= e
instanceof PsiModifierListOwner?
(PsiModifierListOwner
)e
: null;
257 LocalQuickFix quickFix
;
258 if (owner
!= null && PsiUtilEx
.isLanguageAnnotationTarget(owner
)) {
259 PsiAnnotation
[] resolvedAnnos
= AnnotationUtilEx
.getAnnotationFrom(owner
, myConfiguration
.getPatternAnnotationPair(), true);
260 if (resolvedAnnos
.length
== 2 && annotations
.length
== 2 && Comparing
.strEqual(resolvedAnnos
[1].getQualifiedName(), annotations
[1].getQualifiedName())) {
261 // both target and source annotated indirectly with the same anno
265 final String classname
= Configuration
.getInstance().getSubstAnnotationPair().first
;
266 final AnnotateFix fix
= new AnnotateFix((PsiModifierListOwner
)e
, classname
);
267 quickFix
= fix
.canApply() ? fix
: new IntroduceVariableFix(expr
);
270 quickFix
= new IntroduceVariableFix(expr
);
272 holder
.registerProblem(expr
, "Unsubstituted expression", quickFix
);
277 private static class IntroduceVariableFix
implements LocalQuickFix
{
278 private final PsiExpression myExpr
;
280 public IntroduceVariableFix(PsiExpression expr
) {
285 public String
getName() {
286 return "Introduce Variable";
290 public String
getFamilyName() {
294 public void applyFix(@NotNull Project project
, @NotNull ProblemDescriptor descriptor
) {
295 final RefactoringActionHandler handler
= JavaRefactoringActionHandlerFactory
.getInstance().createIntroduceVariableHandler();
296 handler
.invoke(project
, new PsiElement
[]{myExpr
}, DataManager
.getInstance().getDataContext());
297 // how to automatically annotate the variable after it has been introduced?