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
.PsiTreeUtil
;
31 import com
.intellij
.refactoring
.JavaRefactoringActionHandlerFactory
;
32 import com
.intellij
.refactoring
.RefactoringActionHandler
;
33 import com
.intellij
.util
.SmartList
;
34 import org
.intellij
.plugins
.intelliLang
.Configuration
;
35 import org
.intellij
.plugins
.intelliLang
.util
.AnnotateFix
;
36 import org
.intellij
.plugins
.intelliLang
.util
.AnnotationUtilEx
;
37 import org
.intellij
.plugins
.intelliLang
.util
.PsiUtilEx
;
38 import org
.intellij
.plugins
.intelliLang
.util
.SubstitutedExpressionEvaluationHelper
;
39 import org
.jetbrains
.annotations
.NonNls
;
40 import org
.jetbrains
.annotations
.NotNull
;
41 import org
.jetbrains
.annotations
.Nullable
;
45 import java
.awt
.event
.ItemEvent
;
46 import java
.awt
.event
.ItemListener
;
47 import java
.text
.MessageFormat
;
48 import java
.util
.List
;
49 import java
.util
.regex
.Pattern
;
50 import java
.util
.regex
.PatternSyntaxException
;
53 * Inspection that validates if string literals, compile-time constants or
54 * substituted expressions match the pattern of the context they're used in.
56 public class PatternValidator
extends LocalInspectionTool
{
57 private static final Key
<CachedValue
<Pattern
>> COMPLIED_PATTERN
= Key
.create("COMPILED_PATTERN");
59 public boolean CHECK_NON_CONSTANT_VALUES
= true;
61 private final Configuration myConfiguration
;
63 public PatternValidator() {
64 myConfiguration
= Configuration
.getInstance();
67 public boolean isEnabledByDefault() {
72 public String
getGroupDisplayName() {
73 return InspectionProvider
.PATTERN_VALIDATION
;
77 public String
getDisplayName() {
78 return "Validate Annotated Patterns";
82 public JComponent
createOptionsPanel() {
83 final JPanel jPanel
= new JPanel(new BorderLayout());
84 final JCheckBox jCheckBox
= new JCheckBox("Flag non compile-time constant expressions");
85 jCheckBox
.setToolTipText(
86 "If checked, the inspection will flag expressions with unknown values " + "and offer to add a substitution (@Subst) annotation");
87 jCheckBox
.setSelected(CHECK_NON_CONSTANT_VALUES
);
88 jCheckBox
.addItemListener(new ItemListener() {
89 public void itemStateChanged(ItemEvent e
) {
90 CHECK_NON_CONSTANT_VALUES
= jCheckBox
.isSelected();
93 jPanel
.add(jCheckBox
, BorderLayout
.NORTH
);
99 public String
getShortName() {
100 return "PatternValidation";
104 public PsiElementVisitor
buildVisitor(@NotNull final ProblemsHolder holder
, boolean isOnTheFly
) {
105 return new JavaElementVisitor() {
107 public final void visitReferenceExpression(PsiReferenceExpression expression
) {
108 visitExpression(expression
);
112 public void visitExpression(PsiExpression expression
) {
113 final PsiElement element
= expression
.getParent();
114 if (element
instanceof PsiExpressionList
) {
115 // this checks method arguments
116 check(expression
, holder
, false);
118 else if (element
instanceof PsiNameValuePair
) {
119 final PsiNameValuePair valuePair
= (PsiNameValuePair
)element
;
120 final String name
= valuePair
.getName();
121 if (name
== null || name
.equals(PsiAnnotation
.DEFAULT_REFERENCED_METHOD_NAME
)) {
122 // check whether @Subst complies with pattern
123 check(expression
, holder
, true);
129 public void visitReturnStatement(PsiReturnStatement statement
) {
130 final PsiExpression returnValue
= statement
.getReturnValue();
131 if (returnValue
!= null) {
132 check(returnValue
, holder
, false);
137 public void visitVariable(PsiVariable var
) {
138 final PsiExpression initializer
= var
.getInitializer();
139 if (initializer
!= null) {
140 // variable/field initializer
141 check(initializer
, holder
, false);
146 public void visitAssignmentExpression(PsiAssignmentExpression expression
) {
147 final PsiExpression e
= expression
.getRExpression();
149 check(e
, holder
, false);
151 visitExpression(expression
);
154 private void check(@NotNull PsiExpression expression
, ProblemsHolder holder
, boolean isAnnotationValue
) {
155 if (expression
instanceof PsiConditionalExpression
) {
156 final PsiConditionalExpression expr
= (PsiConditionalExpression
)expression
;
157 PsiExpression e
= expr
.getThenExpression();
159 check(e
, holder
, isAnnotationValue
);
161 e
= expr
.getElseExpression();
163 check(e
, holder
, isAnnotationValue
);
167 final PsiType type
= expression
.getType();
168 // optimiziation: only check expressions of type String
169 if (type
!= null && PsiUtilEx
.isString(type
)) {
170 final PsiModifierListOwner element
;
171 if (isAnnotationValue
) {
172 final PsiAnnotation psiAnnotation
= PsiTreeUtil
.getParentOfType(expression
, PsiAnnotation
.class);
173 if (psiAnnotation
!= null && myConfiguration
.getSubstAnnotationClass().equals(psiAnnotation
.getQualifiedName())) {
174 element
= PsiTreeUtil
.getParentOfType(expression
, PsiModifierListOwner
.class);
181 element
= AnnotationUtilEx
.getAnnotatedElementFor(expression
, AnnotationUtilEx
.LookupType
.PREFER_CONTEXT
);
183 if (element
!= null && PsiUtilEx
.isLanguageAnnotationTarget(element
)) {
184 PsiAnnotation
[] annotations
= AnnotationUtilEx
.getAnnotationFrom(element
, myConfiguration
.getPatternAnnotationPair(), true);
185 checkExpression(expression
, annotations
, holder
);
193 private void checkExpression(PsiExpression expression
, final PsiAnnotation
[] annotations
, ProblemsHolder holder
) {
194 if (annotations
.length
== 0) return;
195 final PsiAnnotation psiAnnotation
= annotations
[0];
197 // cache compiled pattern with annotation
198 CachedValue
<Pattern
> p
= psiAnnotation
.getUserData(COMPLIED_PATTERN
);
200 final CachedValueProvider
<Pattern
> provider
= new CachedValueProvider
<Pattern
>() {
201 public Result
<Pattern
> compute() {
202 final String pattern
= AnnotationUtilEx
.calcAnnotationValue(psiAnnotation
, "value");
204 if (pattern
!= null) {
206 p
= Pattern
.compile(pattern
);
208 catch (PatternSyntaxException e
) {
209 // pattern stays null
212 return Result
.create(p
, (Object
[])annotations
);
215 p
= expression
.getManager().getCachedValuesManager().createCachedValue(provider
, false);
216 psiAnnotation
.putUserData(COMPLIED_PATTERN
, p
);
219 final Pattern pattern
= p
.getValue();
220 if (pattern
== null) return;
222 List
<PsiExpression
> nonConstantElements
= new SmartList
<PsiExpression
>();
223 final Object result
= new SubstitutedExpressionEvaluationHelper(expression
.getProject()).computeExpression(
224 expression
, myConfiguration
.isUseDfaIfAvailable(), false, nonConstantElements
);
225 final String o
= result
== null ?
null : String
.valueOf(result
);
227 if (!pattern
.matcher(o
).matches()) {
228 if (annotations
.length
> 1) {
229 // the last element contains the element's actual annotation
230 final String fqn
= annotations
[annotations
.length
- 1].getQualifiedName();
233 final String name
= StringUtil
.getShortName(fqn
);
234 holder
.registerProblem(expression
, MessageFormat
.format("Expression ''{0}'' doesn''t match ''{1}'' pattern: {2}", o
, name
,
238 holder
.registerProblem(expression
,
239 MessageFormat
.format("Expression ''{0}'' doesn''t match pattern: {1}", o
, pattern
.pattern()));
243 else if (CHECK_NON_CONSTANT_VALUES
) {
244 for (PsiExpression expr
: nonConstantElements
) {
246 if (expr
instanceof PsiReferenceExpression
) {
247 e
= ((PsiReferenceExpression
)expr
).resolve();
249 else if (expr
instanceof PsiMethodCallExpression
) {
250 e
= ((PsiMethodCallExpression
)expr
).getMethodExpression().resolve();
255 final PsiModifierListOwner owner
= e
instanceof PsiModifierListOwner?
(PsiModifierListOwner
)e
: null;
256 LocalQuickFix quickFix
;
257 if (owner
!= null && PsiUtilEx
.isLanguageAnnotationTarget(owner
)) {
258 PsiAnnotation
[] resolvedAnnos
= AnnotationUtilEx
.getAnnotationFrom(owner
, myConfiguration
.getPatternAnnotationPair(), true);
259 if (resolvedAnnos
.length
== 2 && annotations
.length
== 2 && Comparing
.strEqual(resolvedAnnos
[1].getQualifiedName(), annotations
[1].getQualifiedName())) {
260 // both target and source annotated indirectly with the same anno
264 final String classname
= Configuration
.getInstance().getSubstAnnotationPair().first
;
265 final AnnotateFix fix
= new AnnotateFix((PsiModifierListOwner
)e
, classname
);
266 quickFix
= fix
.canApply() ? fix
: new IntroduceVariableFix(expr
);
269 quickFix
= new IntroduceVariableFix(expr
);
271 holder
.registerProblem(expr
, "Unsubstituted expression", quickFix
);
276 private static class IntroduceVariableFix
implements LocalQuickFix
{
277 private final PsiExpression myExpr
;
279 public IntroduceVariableFix(PsiExpression expr
) {
284 public String
getName() {
285 return "Introduce Variable";
289 public String
getFamilyName() {
293 public void applyFix(@NotNull Project project
, @NotNull ProblemDescriptor descriptor
) {
294 final RefactoringActionHandler handler
= JavaRefactoringActionHandlerFactory
.getInstance().createIntroduceVariableHandler();
295 handler
.invoke(project
, new PsiElement
[]{myExpr
}, DataManager
.getInstance().getDataContext());
296 // how to automatically annotate the variable after it has been introduced?