deprecation removed
[fedora-idea.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / pattern / PatternValidator.java
blobf23ffd3a8968acad1c07cae00e5bbc428e339994
1 /*
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;
44 import javax.swing.*;
45 import java.awt.*;
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;
53 /**
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() {
69 return true;
72 @NotNull
73 public String getGroupDisplayName() {
74 return InspectionProvider.PATTERN_VALIDATION;
77 @NotNull
78 public String getDisplayName() {
79 return "Validate Annotated Patterns";
82 @Nullable
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();
93 });
94 jPanel.add(jCheckBox, BorderLayout.NORTH);
95 return jPanel;
98 @NotNull
99 @NonNls
100 public String getShortName() {
101 return "PatternValidation";
104 @NotNull
105 public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
106 return new JavaElementVisitor() {
108 public final void visitReferenceExpression(PsiReferenceExpression expression) {
109 visitExpression(expression);
112 @Override
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);
129 @Override
130 public void visitReturnStatement(PsiReturnStatement statement) {
131 final PsiExpression returnValue = statement.getReturnValue();
132 if (returnValue != null) {
133 check(returnValue, holder, false);
137 @Override
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);
146 @Override
147 public void visitAssignmentExpression(PsiAssignmentExpression expression) {
148 final PsiExpression e = expression.getRExpression();
149 if (e != null) {
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();
159 if (e != null) {
160 check(e, holder, isAnnotationValue);
162 e = expr.getElseExpression();
163 if (e != null) {
164 check(e, holder, isAnnotationValue);
167 else {
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);
177 else {
178 return;
181 else {
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);
200 if (p == null) {
201 final CachedValueProvider<Pattern> provider = new CachedValueProvider<Pattern>() {
202 public Result<Pattern> compute() {
203 final String pattern = AnnotationUtilEx.calcAnnotationValue(psiAnnotation, "value");
204 Pattern p = null;
205 if (pattern != null) {
206 try {
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);
227 if (o != null) {
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();
232 assert fqn != null;
234 final String name = StringUtil.getShortName(fqn);
235 holder.registerProblem(expression, MessageFormat.format("Expression ''{0}'' doesn''t match ''{1}'' pattern: {2}", o, name,
236 pattern.pattern()));
238 else {
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) {
246 final PsiElement e;
247 if (expr instanceof PsiReferenceExpression) {
248 e = ((PsiReferenceExpression)expr).resolve();
250 else if (expr instanceof PsiMethodCallExpression) {
251 e = ((PsiMethodCallExpression)expr).getMethodExpression().resolve();
253 else {
254 e = expr;
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
262 return;
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);
269 else {
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) {
281 myExpr = expr;
284 @NotNull
285 public String getName() {
286 return "Introduce Variable";
289 @NotNull
290 public String getFamilyName() {
291 return getName();
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?