0c8a419e2c5cae5e25153e47b855ef8a4b38ba96
[idea/community.git] / plugins / java-i18n / src / com / intellij / codeInspection / i18n / I18nInspection.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2
3 package com.intellij.codeInspection.i18n;
4
5 import com.intellij.codeInsight.AnnotationUtil;
6 import com.intellij.codeInsight.CodeInsightBundle;
7 import com.intellij.codeInsight.daemon.GroupNames;
8 import com.intellij.codeInsight.externalAnnotation.NonNlsAnnotationProvider;
9 import com.intellij.codeInspection.*;
10 import com.intellij.ide.util.TreeClassChooser;
11 import com.intellij.ide.util.TreeClassChooserFactory;
12 import com.intellij.lang.properties.PropertiesImplUtil;
13 import com.intellij.openapi.editor.Document;
14 import com.intellij.openapi.project.Project;
15 import com.intellij.openapi.project.ProjectManager;
16 import com.intellij.openapi.ui.DialogWrapper;
17 import com.intellij.openapi.util.Comparing;
18 import com.intellij.openapi.util.InvalidDataException;
19 import com.intellij.openapi.util.WriteExternalException;
20 import com.intellij.openapi.util.registry.Registry;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.psi.*;
23 import com.intellij.psi.search.GlobalSearchScope;
24 import com.intellij.psi.util.InheritanceUtil;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.psi.util.PsiUtil;
27 import com.intellij.refactoring.introduceField.IntroduceConstantHandler;
28 import com.intellij.ui.AddDeleteListPanel;
29 import com.intellij.ui.DocumentAdapter;
30 import com.intellij.ui.FieldPanel;
31 import com.intellij.ui.ScrollPaneFactory;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.ObjectUtils;
34 import com.intellij.util.containers.ContainerUtil;
35 import com.siyeh.HardcodedMethodConstants;
36 import com.siyeh.ig.psiutils.MethodUtils;
37 import gnu.trove.THashSet;
38 import org.jdom.Element;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42 import org.jetbrains.annotations.TestOnly;
43 import org.jetbrains.uast.*;
44 import org.jetbrains.uast.util.UastExpressionUtils;
45 import org.jetbrains.uast.visitor.AbstractUastVisitor;
46
47 import javax.swing.*;
48 import javax.swing.event.ChangeEvent;
49 import javax.swing.event.ChangeListener;
50 import javax.swing.event.DocumentEvent;
51 import java.awt.*;
52 import java.awt.event.ActionEvent;
53 import java.awt.event.ActionListener;
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.Objects;
57 import java.util.Set;
58 import java.util.regex.Matcher;
59 import java.util.regex.Pattern;
60
61 import static com.intellij.codeInsight.AnnotationUtil.CHECK_EXTERNAL;
62 import static com.intellij.codeInsight.AnnotationUtil.CHECK_HIERARCHY;
63
64 public class I18nInspection extends AbstractBaseUastLocalInspectionTool implements CustomSuppressableInspectionTool {
65   public boolean ignoreForAssertStatements = true;
66   public boolean ignoreForExceptionConstructors = true;
67   @NonNls
68   public String ignoreForSpecifiedExceptionConstructors = "";
69   public boolean ignoreForJUnitAsserts = true;
70   public boolean ignoreForClassReferences = true;
71   public boolean ignoreForPropertyKeyReferences = true;
72   public boolean ignoreForNonAlpha = true;
73   private boolean ignoreForAllButNls = false;
74   public boolean ignoreAssignedToConstants;
75   public boolean ignoreToString;
76   @NonNls public String nonNlsCommentPattern = "NON-NLS";
77   private boolean ignoreForEnumConstants;
78
79   @Nullable private Pattern myCachedNonNlsPattern;
80   @NonNls private static final String TO_STRING = "toString";
81
82   public I18nInspection() {
83     cacheNonNlsCommentPattern();
84   }
85
86   @Override
87   public SuppressIntentionAction @NotNull [] getSuppressActions(PsiElement element) {
88     SuppressQuickFix[] suppressActions = getBatchSuppressActions(element);
89
90     if (myCachedNonNlsPattern == null) {
91       return ContainerUtil.map2Array(suppressActions,  SuppressIntentionAction.class, SuppressIntentionActionFromFix::convertBatchToSuppressIntentionAction);
92     }
93     else {
94       List<SuppressIntentionAction> suppressors = new ArrayList<>(suppressActions.length + 1);
95       suppressors.add(new SuppressByCommentOutAction(nonNlsCommentPattern));
96       suppressors.addAll(ContainerUtil.map(suppressActions,  SuppressIntentionActionFromFix::convertBatchToSuppressIntentionAction));
97       return suppressors.toArray(SuppressIntentionAction.EMPTY_ARRAY);
98     }
99   }
100
101   private static final String SKIP_FOR_ENUM = "ignoreForEnumConstant";
102   private static final String IGNORE_ALL_BUT_NLS = "ignoreAllButNls";
103   @Override
104   public void writeSettings(@NotNull Element node) throws WriteExternalException {
105     super.writeSettings(node);
106     if (ignoreForEnumConstants) {
107       node.addContent(new Element("option")
108                         .setAttribute("name", SKIP_FOR_ENUM)
109                         .setAttribute("value", Boolean.toString(ignoreForEnumConstants)));
110     }
111     if (ignoreForAllButNls) {
112       node.addContent(new Element("option")
113                         .setAttribute("name", IGNORE_ALL_BUT_NLS)
114                         .setAttribute("value", Boolean.toString(ignoreForAllButNls)));
115     }
116   }
117
118   @Override
119   public void readSettings(@NotNull Element node) throws InvalidDataException {
120     super.readSettings(node);
121     for (Element o : node.getChildren()) {
122       String nameAttr = o.getAttributeValue("name");
123       String valueAttr = o.getAttributeValue("value");
124       if (Comparing.strEqual(nameAttr, SKIP_FOR_ENUM)) {
125         if (valueAttr != null) {
126           ignoreForEnumConstants = Boolean.parseBoolean(valueAttr);
127         }
128       }
129       else if (Comparing.strEqual(nameAttr, IGNORE_ALL_BUT_NLS)) {
130         if (valueAttr != null) {
131           ignoreForAllButNls = Boolean.parseBoolean(valueAttr);
132         }
133       }
134     }
135     cacheNonNlsCommentPattern();
136   }
137
138   @Override
139   @NotNull
140   public String getGroupDisplayName() {
141     return GroupNames.INTERNATIONALIZATION_GROUP_NAME;
142   }
143
144   @Override
145   @NotNull
146   public String getShortName() {
147     return "HardCodedStringLiteral";
148   }
149
150   @TestOnly
151   public boolean setIgnoreForEnumConstants(boolean ignoreForEnumConstants) {
152     boolean old = this.ignoreForEnumConstants;
153     this.ignoreForEnumConstants = ignoreForEnumConstants;
154     return old;
155   }
156
157   @TestOnly
158   public boolean setIgnoreForAllButNls(boolean ignoreForAllButNls) {
159     boolean old = this.ignoreForAllButNls;
160     this.ignoreForAllButNls = ignoreForAllButNls;
161     return old;
162   }
163
164   @Override
165   public JComponent createOptionsPanel() {
166     final GridBagLayout layout = new GridBagLayout();
167     final JPanel panel = new JPanel(layout);
168     final JCheckBox assertStatementsCheckbox = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.assert"), ignoreForAssertStatements);
169     assertStatementsCheckbox.addChangeListener(new ChangeListener() {
170       @Override
171       public void stateChanged(@NotNull ChangeEvent e) {
172         ignoreForAssertStatements = assertStatementsCheckbox.isSelected();
173       }
174     });
175     final JCheckBox exceptionConstructorCheck =
176       new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.for.exception.constructor.arguments"),
177                     ignoreForExceptionConstructors);
178     exceptionConstructorCheck.addChangeListener(new ChangeListener() {
179       @Override
180       public void stateChanged(@NotNull ChangeEvent e) {
181         ignoreForExceptionConstructors = exceptionConstructorCheck.isSelected();
182       }
183     });
184
185     final JTextField specifiedExceptions = new JTextField(ignoreForSpecifiedExceptionConstructors);
186     specifiedExceptions.getDocument().addDocumentListener(new DocumentAdapter(){
187       @Override
188       protected void textChanged(@NotNull DocumentEvent e) {
189         ignoreForSpecifiedExceptionConstructors = specifiedExceptions.getText();
190       }
191     });
192
193     final JCheckBox junitAssertCheckbox = new JCheckBox(
194       CodeInsightBundle.message("inspection.i18n.option.ignore.for.junit.assert.arguments"), ignoreForJUnitAsserts);
195     junitAssertCheckbox.addChangeListener(new ChangeListener() {
196       @Override
197       public void stateChanged(@NotNull ChangeEvent e) {
198         ignoreForJUnitAsserts = junitAssertCheckbox.isSelected();
199       }
200     });
201     final JCheckBox classRef = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.qualified.class.names"), ignoreForClassReferences);
202     classRef.addChangeListener(new ChangeListener() {
203       @Override
204       public void stateChanged(@NotNull ChangeEvent e) {
205         ignoreForClassReferences = classRef.isSelected();
206       }
207     });
208     final JCheckBox propertyRef = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.property.keys"), ignoreForPropertyKeyReferences);
209     propertyRef.addChangeListener(new ChangeListener() {
210       @Override
211       public void stateChanged(@NotNull ChangeEvent e) {
212         ignoreForPropertyKeyReferences = propertyRef.isSelected();
213       }
214     });
215     final JCheckBox nonAlpha = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.nonalphanumerics"), ignoreForNonAlpha);
216     nonAlpha.addChangeListener(new ChangeListener() {
217       @Override
218       public void stateChanged(@NotNull ChangeEvent e) {
219         ignoreForNonAlpha = nonAlpha.isSelected();
220       }
221     });
222     final JCheckBox assignedToConstants = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.assigned.to.constants"), ignoreAssignedToConstants);
223     assignedToConstants.addChangeListener(new ChangeListener() {
224       @Override
225       public void stateChanged(@NotNull ChangeEvent e) {
226         ignoreAssignedToConstants = assignedToConstants.isSelected();
227       }
228     });
229     final JCheckBox chkToString = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.tostring"), ignoreToString);
230     chkToString.addChangeListener(new ChangeListener() {
231       @Override
232       public void stateChanged(@NotNull ChangeEvent e) {
233         ignoreToString = chkToString.isSelected();
234       }
235     });
236
237     final JCheckBox ignoreEnumConstants = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.enum"), ignoreForEnumConstants);
238     ignoreEnumConstants.addChangeListener(new ChangeListener() {
239       @Override
240       public void stateChanged(@NotNull ChangeEvent e) {
241         ignoreForEnumConstants = ignoreEnumConstants.isSelected();
242       }
243     });
244
245     final JCheckBox ignoreAllButNls = new JCheckBox(CodeInsightBundle.message("inspection.i18n.option.ignore.nls"), ignoreForAllButNls);
246     ignoreAllButNls.addChangeListener(new ChangeListener() {
247       @Override
248       public void stateChanged(@NotNull ChangeEvent e) {
249         ignoreForAllButNls = ignoreAllButNls.isSelected();
250       }
251     });
252
253     final GridBagConstraints gc = new GridBagConstraints();
254     gc.fill = GridBagConstraints.HORIZONTAL;
255     gc.insets.bottom = 2;
256
257     gc.gridx = GridBagConstraints.REMAINDER;
258     gc.gridy = 0;
259     gc.weightx = 1;
260     gc.weighty = 0;
261     panel.add(ignoreAllButNls, gc);
262
263     gc.gridy ++;
264     panel.add(assertStatementsCheckbox, gc);
265
266     gc.gridy ++;
267     panel.add(junitAssertCheckbox, gc);
268
269     gc.gridy ++;
270     panel.add(exceptionConstructorCheck, gc);
271
272     gc.gridy ++;
273     final Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
274     panel.add(new FieldPanel(specifiedExceptions,
275                              null,
276                              CodeInsightBundle.message("inspection.i18n.option.ignore.for.specified.exception.constructor.arguments"),
277                              openProjects.length == 0 ? null :
278                              new ActionListener() {
279                                @Override
280                                public void actionPerformed(@NotNull ActionEvent e) {
281                                  createIgnoreExceptionsConfigurationDialog(openProjects[0], specifiedExceptions).show();
282                                }
283                              },
284                              null), gc);
285
286     gc.gridy ++;
287     panel.add(classRef, gc);
288
289     gc.gridy ++;
290     panel.add(propertyRef, gc);
291
292     gc.gridy++;
293     panel.add(assignedToConstants, gc);
294
295     gc.gridy++;
296     panel.add(chkToString, gc);
297
298     gc.gridy ++;
299     panel.add(nonAlpha, gc);
300
301     gc.gridy ++;
302     panel.add(ignoreEnumConstants, gc);
303
304     gc.gridy ++;
305     gc.anchor = GridBagConstraints.NORTHWEST;
306     gc.weighty = 1;
307     final JTextField text = new JTextField(nonNlsCommentPattern);
308     final FieldPanel nonNlsCommentPatternComponent =
309       new FieldPanel(text, CodeInsightBundle.message("inspection.i18n.option.ignore.comment.pattern"),
310                      CodeInsightBundle.message("inspection.i18n.option.ignore.comment.title"), null, () -> {
311                        nonNlsCommentPattern = text.getText();
312                        cacheNonNlsCommentPattern();
313                      });
314     panel.add(nonNlsCommentPatternComponent, gc);
315
316     final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(panel);
317     scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
318     scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
319     scrollPane.setBorder(null);
320     scrollPane.setPreferredSize(new Dimension(panel.getPreferredSize().width + scrollPane.getVerticalScrollBar().getPreferredSize().width,
321                                               panel.getPreferredSize().height +
322                                               scrollPane.getHorizontalScrollBar().getPreferredSize().height));
323     return scrollPane;
324   }
325
326   private DialogWrapper createIgnoreExceptionsConfigurationDialog(final Project project, final JTextField specifiedExceptions) {
327     return new DialogWrapper(true) {
328       private AddDeleteListPanel myPanel;
329       {
330         setTitle(CodeInsightBundle.message(
331           "inspection.i18n.option.ignore.for.specified.exception.constructor.arguments"));
332         init();
333       }
334
335       @Override
336       protected JComponent createCenterPanel() {
337         final String[] ignored = ignoreForSpecifiedExceptionConstructors.split(",");
338         final List<String> initialList = new ArrayList<>();
339         for (String e : ignored) {
340           if (!e.isEmpty()) initialList.add(e);
341         }
342         myPanel = new AddDeleteListPanel<String>(null, initialList) {
343           @Override
344           protected String findItemToAdd() {
345             final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
346             TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project).
347               createInheritanceClassChooser(
348                 CodeInsightBundle.message("inspection.i18n.option.ignore.for.specified.exception.constructor.arguments"), scope,
349                 JavaPsiFacade.getInstance(project).findClass("java.lang.Throwable", scope), true, true, null);
350             chooser.showDialog();
351             PsiClass selectedClass = chooser.getSelected();
352             return selectedClass != null ? selectedClass.getQualifiedName() : null;
353           }
354         };
355         return myPanel;
356       }
357
358       @Override
359       protected void doOKAction() {
360         StringBuilder buf = new StringBuilder();
361         final Object[] exceptions = myPanel.getListItems();
362         for (Object exception : exceptions) {
363           buf.append(",").append(exception);
364         }
365         specifiedExceptions.setText(buf.length() > 0 ? buf.substring(1) : buf.toString());
366         super.doOKAction();
367       }
368     };
369   }
370
371   @Override
372   public ProblemDescriptor @Nullable [] checkMethod(@NotNull UMethod method, @NotNull InspectionManager manager, boolean isOnTheFly) {
373     if (isClassNonNls(method)) {
374       return null;
375     }
376     List<ProblemDescriptor> results = new ArrayList<>();
377     final UExpression body = method.getUastBody();
378     if (body != null) {
379       ProblemDescriptor[] descriptors = checkElement(body, manager, isOnTheFly);
380       if (descriptors != null) {
381         ContainerUtil.addAll(results, descriptors);
382       }
383     }
384     checkAnnotations(method, manager, isOnTheFly, results);
385     for (UParameter parameter : method.getUastParameters()) {
386       checkAnnotations(parameter, manager, isOnTheFly, results);
387     }
388     return results.isEmpty() ? null : results.toArray(ProblemDescriptor.EMPTY_ARRAY);
389   }
390
391   @Override
392   public ProblemDescriptor @Nullable [] checkClass(@NotNull UClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
393     if (isClassNonNls(aClass)) {
394       return null;
395     }
396     final UClassInitializer[] initializers = aClass.getInitializers();
397     List<ProblemDescriptor> result = new ArrayList<>();
398     for (UClassInitializer initializer : initializers) {
399       final ProblemDescriptor[] descriptors = checkElement(initializer.getUastBody(), manager, isOnTheFly);
400       if (descriptors != null) {
401         ContainerUtil.addAll(result, descriptors);
402       }
403     }
404     checkAnnotations(aClass, manager, isOnTheFly, result);
405
406
407     return result.isEmpty() ? null : result.toArray(ProblemDescriptor.EMPTY_ARRAY);
408   }
409
410   private void checkAnnotations(UDeclaration member,
411                                 @NotNull InspectionManager manager,
412                                 boolean isOnTheFly, List<? super ProblemDescriptor> result) {
413     for (UAnnotation annotation : member.getUAnnotations()) {
414       final ProblemDescriptor[] descriptors = checkElement(annotation, manager, isOnTheFly);
415       if (descriptors != null) {
416         ContainerUtil.addAll(result, descriptors);
417       }
418     }
419   }
420
421   @Override
422   public ProblemDescriptor @Nullable [] checkField(@NotNull UField field, @NotNull InspectionManager manager, boolean isOnTheFly) {
423     if (isClassNonNls(field)) {
424       return null;
425     }
426     if (AnnotationUtil.isAnnotated((PsiModifierListOwner)field.getJavaPsi(), AnnotationUtil.NON_NLS, CHECK_EXTERNAL)) {
427       return null;
428     }
429     List<ProblemDescriptor> result = new ArrayList<>();
430     final UExpression initializer = field.getUastInitializer();
431     if (initializer != null) {
432       ProblemDescriptor[] descriptors = checkElement(initializer, manager, isOnTheFly);
433       if (descriptors != null) {
434         ContainerUtil.addAll(result, descriptors);
435       }
436     } else if (field instanceof UEnumConstant) {
437       List<UExpression> arguments = ((UEnumConstant)field).getValueArguments();
438       for (UExpression argument : arguments) {
439         ProblemDescriptor[] descriptors = checkElement(argument, manager, isOnTheFly);
440         if (descriptors != null) {
441           ContainerUtil.addAll(result, descriptors);
442         }
443       }
444     }
445     checkAnnotations(field, manager, isOnTheFly, result);
446     return result.isEmpty() ? null : result.toArray(ProblemDescriptor.EMPTY_ARRAY);
447   }
448
449   @Nullable
450   @Override
451   public String getAlternativeID() {
452     return "nls";
453   }
454
455   private ProblemDescriptor[] checkElement(@NotNull UElement element, @NotNull InspectionManager manager, boolean isOnTheFly) {
456     StringI18nVisitor visitor = new StringI18nVisitor(manager, isOnTheFly);
457     element.accept(visitor);
458     List<ProblemDescriptor> problems = visitor.getProblems();
459     return problems.isEmpty() ? null : problems.toArray(ProblemDescriptor.EMPTY_ARRAY);
460   }
461
462   @NotNull
463   private static LocalQuickFix createIntroduceConstantFix() {
464     return new LocalQuickFix() {
465       @Override
466       @NotNull
467       public String getFamilyName() {
468         return IntroduceConstantHandler.getRefactoringNameText();
469       }
470
471       @Override
472       public boolean startInWriteAction() {
473         return false;
474       }
475
476       @Override
477       public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
478         PsiElement element = descriptor.getPsiElement();
479         if (!(element instanceof PsiExpression)) return;
480
481         PsiExpression[] expressions = {(PsiExpression)element};
482         new IntroduceConstantHandler().invoke(project, expressions);
483       }
484     };
485   }
486
487   private class StringI18nVisitor extends AbstractUastVisitor {
488     private final List<ProblemDescriptor> myProblems = new ArrayList<>();
489     private final InspectionManager myManager;
490     private final boolean myOnTheFly;
491
492     private StringI18nVisitor(@NotNull InspectionManager manager, boolean onTheFly) {
493       myManager = manager;
494       myOnTheFly = onTheFly;
495     }
496
497     @Override
498     public boolean visitObjectLiteralExpression(@NotNull UObjectLiteralExpression objectLiteralExpression) {
499       for (UExpression argument : objectLiteralExpression.getValueArguments()) {
500         argument.accept(this);
501       }
502
503       return true;
504     }
505     
506     @Override
507     public boolean visitClass(@NotNull UClass node) {
508       return false;
509     }
510
511     @Override
512     public boolean visitField(@NotNull UField node) {
513       return false;
514     }
515
516     @Override
517     public boolean visitMethod(@NotNull UMethod node) {
518       return false;
519     }
520
521     @Override
522     public boolean visitInitializer(@NotNull UClassInitializer node) {
523       return false;
524     }
525
526     @Override
527     public boolean visitLiteralExpression(@NotNull ULiteralExpression expression) {
528       Object value = expression.getValue();
529       if (!(value instanceof String)) return false;
530       String stringValue = (String)value;
531       if (stringValue.trim().isEmpty()) {
532         return false;
533       }
534
535       Set<PsiModifierListOwner> nonNlsTargets = new THashSet<>();
536       if (canBeI18ned(myManager.getProject(), expression, stringValue, nonNlsTargets)) {
537         UField parentField = UastUtils.getParentOfType(expression, UField.class); // PsiTreeUtil.getParentOfType(expression, PsiField.class);
538         if (parentField != null) {
539           nonNlsTargets.add(parentField);
540         }
541
542         final String description = CodeInsightBundle.message("inspection.i18n.message.general.with.value", "#ref");
543
544         PsiElement sourcePsi = expression.getSourcePsi();
545         
546         List<LocalQuickFix> fixes = new ArrayList<>();
547
548         if (sourcePsi instanceof PsiLiteralExpression) {
549           if (myOnTheFly) {
550             if (I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(sourcePsi) != null) {
551               fixes.add(new I18nizeConcatenationQuickFix());
552             }
553             fixes.add(new I18nizeQuickFix());
554
555             if (!isNotConstantFieldInitializer((PsiExpression)sourcePsi)) {
556               fixes.add(createIntroduceConstantFix());
557             }
558
559             if (PsiUtil.isLanguageLevel5OrHigher(sourcePsi)) {
560               final JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject());
561               for (PsiModifierListOwner element : nonNlsTargets) {
562                 if (!AnnotationUtil.isAnnotated(element, AnnotationUtil.NLS, CHECK_HIERARCHY | CHECK_EXTERNAL)) {
563                   if (!element.getManager().isInProject(element) ||
564                       facade.findClass(AnnotationUtil.NON_NLS, element.getResolveScope()) != null) {
565                     fixes.add(new NonNlsAnnotationProvider().createFix(element));
566                   }
567                 }
568               }
569             }
570           }
571           else if (Registry.is("i18n.for.idea.project") &&
572                    I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(sourcePsi) == null) {
573             fixes.add(new I18nizeBatchQuickFix());
574           }
575         }
576
577         LocalQuickFix[] farr = fixes.toArray(LocalQuickFix.EMPTY_ARRAY);
578         final ProblemDescriptor problem = myManager.createProblemDescriptor(sourcePsi,
579                                                                             description, myOnTheFly, farr,
580                                                                             ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
581         myProblems.add(problem);
582       }
583       return false;
584     }
585
586     private boolean isNotConstantFieldInitializer(final PsiExpression expression) {
587       PsiField parentField = expression.getParent() instanceof PsiField ? (PsiField)expression.getParent() : null;
588       return parentField != null && expression == parentField.getInitializer() &&
589              parentField.hasModifierProperty(PsiModifier.FINAL) &&
590              parentField.hasModifierProperty(PsiModifier.STATIC);
591     }
592
593     @Override
594     public boolean visitAnnotation(UAnnotation annotation) {
595       //prevent from @SuppressWarnings
596       if (BatchSuppressManager.SUPPRESS_INSPECTIONS_ANNOTATION_NAME.equals(annotation.getQualifiedName())) {
597         return true;
598       }
599       return super.visitAnnotation(annotation);
600     }
601
602     private List<ProblemDescriptor> getProblems() {
603       return myProblems;
604     }
605   }
606
607   private boolean canBeI18ned(@NotNull Project project,
608                               @NotNull ULiteralExpression expression,
609                               @NotNull String value,
610                               @NotNull Set<? super PsiModifierListOwner> nonNlsTargets) {
611     if (ignoreForNonAlpha && !StringUtil.containsAlphaCharacters(value)) {
612       return false;
613     }
614
615     if (ignoreForAllButNls) {
616       return JavaI18nUtil.isPassedToAnnotatedParam(expression, AnnotationUtil.NLS, null);
617     }
618
619     if (JavaI18nUtil.isPassedToAnnotatedParam(expression, AnnotationUtil.NON_NLS, nonNlsTargets)) {
620       return false;
621     }
622
623     if (isInNonNlsCall(expression, nonNlsTargets)) {
624       return false;
625     }
626
627     if (isInNonNlsEquals(expression, nonNlsTargets)) {
628       return false;
629     }
630
631     if (isPassedToNonNlsVariable(expression, nonNlsTargets)) {
632       return false;
633     }
634
635     if (JavaI18nUtil.mustBePropertyKey(expression, null)) {
636       return false;
637     }
638
639     if (isReturnedFromNonNlsMethod(expression, nonNlsTargets)) {
640       return false;
641     }
642     if (ignoreForAssertStatements && isArgOfAssertStatement(expression)) {
643       return false;
644     }
645     if (ignoreForExceptionConstructors && isExceptionArgument(expression)) {
646       return false;
647     }
648     if (ignoreForEnumConstants && isArgOfEnumConstant(expression)) {
649       return false;
650     }
651     if (!ignoreForExceptionConstructors && isArgOfSpecifiedExceptionConstructor(expression, ignoreForSpecifiedExceptionConstructors.split(","))) {
652       return false;
653     }
654     if (ignoreForJUnitAsserts && isArgOfJUnitAssertion(expression)) {
655       return false;
656     }
657     if (ignoreForClassReferences && isClassRef(expression, value)) {
658       return false;
659     }
660     if (ignoreForPropertyKeyReferences && !PropertiesImplUtil.findPropertiesByKey(project, value).isEmpty()) {
661       return false;
662     }
663     if (ignoreToString && isToString(expression)) {
664       return false;
665     }
666
667     Pattern pattern = myCachedNonNlsPattern;
668     if (pattern != null) {
669       PsiFile file = expression.getSourcePsi().getContainingFile();
670       Document document = PsiDocumentManager.getInstance(project).getDocument(file);
671       int line = document.getLineNumber(expression.getSourcePsi().getTextRange().getStartOffset());
672       int lineStartOffset = document.getLineStartOffset(line);
673       CharSequence lineText = document.getCharsSequence().subSequence(lineStartOffset, document.getLineEndOffset(line));
674
675       Matcher matcher = pattern.matcher(lineText);
676       int start = 0;
677       while (matcher.find(start)) {
678         start = matcher.start();
679         PsiElement element = file.findElementAt(lineStartOffset + start);
680         if (PsiTreeUtil.getParentOfType(element, PsiComment.class, false) != null) return false;
681         if (start == lineText.length() - 1) break;
682         start++;
683       }
684     }
685
686     return true;
687   }
688
689   private static boolean isArgOfEnumConstant(ULiteralExpression expression) {
690     return expression.getUastParent() instanceof UEnumConstant;
691   }
692
693   public void cacheNonNlsCommentPattern() {
694     myCachedNonNlsPattern = nonNlsCommentPattern.trim().isEmpty() ? null : Pattern.compile(nonNlsCommentPattern);
695   }
696
697   private static boolean isClassRef(final ULiteralExpression expression, String value) {
698     if (StringUtil.startsWithChar(value,'#')) {
699       value = value.substring(1); // A favor for JetBrains team to catch common Logger usage practice.
700     }
701
702     Project project = Objects.requireNonNull(expression.getSourcePsi()).getProject();
703     return JavaPsiFacade.getInstance(project).findClass(value, GlobalSearchScope.allScope(project)) != null;
704   }
705
706   private static boolean isClassNonNls(@NotNull UDeclaration clazz) {
707     UFile uFile = UastUtils.getContainingUFile(clazz);
708     if (uFile == null) return false;
709     final PsiDirectory directory = uFile.getSourcePsi().getContainingDirectory();
710     return directory != null && isPackageNonNls(JavaDirectoryService.getInstance().getPackage(directory));
711   }
712
713   public static boolean isPackageNonNls(final PsiPackage psiPackage) {
714     if (psiPackage == null || psiPackage.getName() == null) {
715       return false;
716     }
717     final PsiModifierList pkgModifierList = psiPackage.getAnnotationList();
718     return pkgModifierList != null && pkgModifierList.hasAnnotation(AnnotationUtil.NON_NLS)
719            || isPackageNonNls(psiPackage.getParentPackage());
720   }
721
722   private boolean isPassedToNonNlsVariable(@NotNull ULiteralExpression expression,
723                                            final Set<? super PsiModifierListOwner> nonNlsTargets) {
724     UExpression toplevel = JavaI18nUtil.getTopLevelExpression(expression);
725     PsiModifierListOwner var = null;
726     if (UastExpressionUtils.isAssignment(toplevel)) {
727       UExpression lExpression = ((UBinaryExpression)toplevel).getLeftOperand();
728       while (lExpression instanceof UArrayAccessExpression) {
729         lExpression = ((UArrayAccessExpression)lExpression).getReceiver();
730       }
731       if (lExpression instanceof UResolvable) {
732         final PsiElement resolved = ((UResolvable)lExpression).resolve();
733         if (resolved instanceof PsiVariable) var = (PsiVariable)resolved;
734       }
735     }
736
737     if (var == null) {
738       UElement parent = toplevel.getUastParent();
739       if (parent instanceof UVariable && toplevel.equals(((UVariable)parent).getUastInitializer())) {
740         if (((UVariable)parent).findAnnotation(AnnotationUtil.NON_NLS) != null) {
741           return true;
742         }
743
744         PsiElement psi = parent.getSourcePsi();
745         if (psi instanceof PsiModifierListOwner) {
746           var = (PsiModifierListOwner)psi;
747         }
748       }
749       else if (toplevel instanceof USwitchExpression) {
750         UExpression switchExpression = ((USwitchExpression)toplevel).getExpression();
751         if (switchExpression instanceof UResolvable) {
752           PsiElement resolved = ((UResolvable)switchExpression).resolve();
753           if (resolved instanceof PsiVariable) {
754             UElement caseParent = expression.getUastParent();
755             if (caseParent instanceof USwitchClauseExpression && ((USwitchClauseExpression)caseParent).getCaseValues().contains(expression)) {
756               var = (PsiVariable)resolved;
757             }
758           }
759         }
760       }
761     }
762
763     if (var != null) {
764       if (annotatedAsNonNls(var)) {
765         return true;
766       }
767       if (ignoreAssignedToConstants &&
768           var.hasModifierProperty(PsiModifier.STATIC) &&
769           var.hasModifierProperty(PsiModifier.FINAL)) {
770         return true;
771       }
772       nonNlsTargets.add(var);
773     }
774     return false;
775   }
776
777   private static boolean annotatedAsNonNls(final PsiModifierListOwner parent) {
778     if (parent instanceof PsiParameter) {
779       final PsiParameter parameter = (PsiParameter)parent;
780       final PsiElement declarationScope = parameter.getDeclarationScope();
781       if (declarationScope instanceof PsiMethod) {
782         final PsiMethod method = (PsiMethod)declarationScope;
783         final int index = method.getParameterList().getParameterIndex(parameter);
784         return JavaI18nUtil.isMethodParameterAnnotatedWith(method, index, null, AnnotationUtil.NON_NLS, null, null);
785       }
786     }
787     return AnnotationUtil.isAnnotated(parent, AnnotationUtil.NON_NLS, CHECK_EXTERNAL);
788   }
789
790   private static boolean isInNonNlsEquals(ULiteralExpression expression, final Set<? super PsiModifierListOwner> nonNlsTargets) {
791     UElement parent = UastUtils.skipParenthesizedExprUp(expression.getUastParent());
792     if (!(parent instanceof UQualifiedReferenceExpression)) return false;
793     UExpression selector = ((UQualifiedReferenceExpression)parent).getSelector();
794     if (!(selector instanceof UCallExpression)) return false;
795     UCallExpression call = (UCallExpression)selector;
796     if (!HardcodedMethodConstants.EQUALS.equals(call.getMethodName()) ||
797         !MethodUtils.isEquals(call.resolve())) return false;
798     final List<UExpression> expressions = call.getValueArguments();
799     if (expressions.size() != 1) return false;
800     final UExpression arg = UastUtils.skipParenthesizedExprDown(expressions.get(0));
801     UResolvable ref = ObjectUtils.tryCast(arg, UResolvable.class);
802     if (ref != null) {
803       final PsiElement resolvedEntity = ref.resolve();
804       if (resolvedEntity instanceof PsiModifierListOwner) {
805         PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)resolvedEntity;
806         if (annotatedAsNonNls(modifierListOwner)) {
807           return true;
808         }
809         nonNlsTargets.add(modifierListOwner);
810       }
811     }
812     return false;
813   }
814
815   private static boolean isInNonNlsCall(@NotNull UExpression expression,
816                                         final Set<? super PsiModifierListOwner> nonNlsTargets) {
817     UExpression parent = UastUtils.skipParenthesizedExprDown(JavaI18nUtil.getTopLevelExpression(expression));
818     if (parent instanceof UQualifiedReferenceExpression) {
819       return isNonNlsCall((UQualifiedReferenceExpression)parent, nonNlsTargets);
820     }
821     else if (parent != null && UastExpressionUtils.isAssignment(parent)) {
822       UExpression operand = ((UBinaryExpression)parent).getLeftOperand();
823       if (operand instanceof UReferenceExpression &&
824           isNonNlsCall((UReferenceExpression)operand, nonNlsTargets)) return true;
825     }
826     else if (parent instanceof UCallExpression) {
827       UElement parentOfNew = UastUtils.skipParenthesizedExprUp(parent.getUastParent());
828       if (parentOfNew instanceof ULocalVariable) {
829         final ULocalVariable newVariable = (ULocalVariable)parentOfNew;
830         if (annotatedAsNonNls(newVariable.getPsi())) {
831           return true;
832         }
833         nonNlsTargets.add(newVariable);
834         return false;
835       }
836     }
837
838     return false;
839   }
840
841   private static boolean isNonNlsCall(UReferenceExpression qualifier, Set<? super PsiModifierListOwner> nonNlsTargets) {
842     final PsiElement resolved = qualifier.resolve();
843     if (resolved instanceof PsiModifierListOwner) {
844       final PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)resolved;
845       if (annotatedAsNonNls(modifierListOwner)) {
846         return true;
847       }
848       nonNlsTargets.add(modifierListOwner);
849     }
850     if (qualifier instanceof UQualifiedReferenceExpression) {
851       UExpression receiver = UastUtils.skipParenthesizedExprDown(((UQualifiedReferenceExpression)qualifier).getReceiver());
852       if (receiver instanceof UReferenceExpression) {
853         return isNonNlsCall((UReferenceExpression)receiver, nonNlsTargets);
854       }
855     }
856     return false;
857   }
858
859   private static boolean isReturnedFromNonNlsMethod(final ULiteralExpression expression, final Set<? super PsiModifierListOwner> nonNlsTargets) {
860     PsiMethod method;
861     UNamedExpression nameValuePair = UastUtils.getParentOfType(expression, UNamedExpression.class);
862     if (nameValuePair != null) {
863       method = UastUtils.getAnnotationMethod(nameValuePair);
864     }
865     else {
866       //todo return from lambda
867       UElement parent = expression.getUastParent();
868       while (parent instanceof UCallExpression && 
869              ((UCallExpression)parent).getKind() == UastCallKind.NEW_ARRAY_WITH_INITIALIZER) {
870         parent = parent.getUastParent();
871       }
872       final UElement returnStmt = UastUtils.getParentOfType(parent, UReturnExpression.class, false, UCallExpression.class, ULambdaExpression.class);
873       if (!(returnStmt instanceof UReturnExpression)) {
874         return false;
875       }
876       UMethod uMethod = UastUtils.getParentOfType(expression, UMethod.class);
877       method = uMethod != null ? uMethod.getJavaPsi() : null;
878     }
879     if (method == null) return false;
880
881     if (AnnotationUtil.isAnnotated(method, AnnotationUtil.NON_NLS, CHECK_HIERARCHY | CHECK_EXTERNAL)) {
882       return true;
883     }
884     nonNlsTargets.add(method);
885     return false;
886   }
887
888   private static boolean isToString(final ULiteralExpression expression) {
889     final UMethod method = UastUtils.getParentOfType(expression, UMethod.class);
890     if (method == null) return false;
891     final PsiType returnType = method.getReturnType();
892     return TO_STRING.equals(method.getName())
893            && method.getUastParameters().isEmpty()
894            && returnType != null
895            && "java.lang.String".equals(returnType.getCanonicalText());
896   }
897
898   private static boolean isArgOfJUnitAssertion(ULiteralExpression expression) {
899     final UElement parent = UastUtils.skipParenthesizedExprUp(expression.getUastParent());
900     if (parent == null || !UastExpressionUtils.isMethodCall(parent)) {
901       return false;
902     }
903     @NonNls final String methodName = ((UCallExpression)parent).getMethodName();
904     if (methodName == null) {
905       return false;
906     }
907
908     if (!methodName.startsWith("assert") && !methodName.equals("fail")) {
909       return false;
910     }
911     final PsiMethod method = ((UCallExpression)parent).resolve();
912     if (method == null) {
913       return false;
914     }
915     final PsiClass containingClass = method.getContainingClass();
916     if (containingClass == null) {
917       return false;
918     }
919     return InheritanceUtil.isInheritor(containingClass,"org.junit.Assert") ||
920            InheritanceUtil.isInheritor(containingClass,"org.junit.jupiter.api.Assertions") || 
921            InheritanceUtil.isInheritor(containingClass, "junit.framework.Assert");
922   }
923
924   private static boolean isArgOfSpecifiedExceptionConstructor(ULiteralExpression expression, 
925                                                               String[] specifiedExceptions) {
926     if (specifiedExceptions.length == 0) return false;
927
928     UCallExpression parent = UastUtils.getParentOfType(expression, UCallExpression.class, true, UClass.class);
929     if (parent == null || !UastExpressionUtils.isConstructorCall(parent)) {
930       return false;
931     }
932     final PsiMethod resolved = parent.resolve();
933     final PsiClass aClass = resolved != null ? resolved.getContainingClass() : null;
934     if (aClass == null) {
935       return false;
936     }
937
938     return ArrayUtil.contains(aClass.getQualifiedName(), specifiedExceptions);
939   }
940
941   private static boolean isArgOfAssertStatement(UExpression expression) {
942     UCallExpression parent = UastUtils.getParentOfType(expression, UCallExpression.class);
943     return parent != null && "assert".equals(parent.getMethodName());
944   }
945   
946    public static boolean isExceptionArgument(@NotNull UExpression expression) {
947     final UCallExpression newExpression =
948       UastUtils.getParentOfType(expression, UCallExpression.class, true, UBlockExpression.class, UClass.class);
949     if (newExpression != null) {
950       if (UastExpressionUtils.isConstructorCall(newExpression)) {
951         final PsiType newExpressionType = newExpression.getExpressionType();
952         return InheritanceUtil.isInheritor(newExpressionType, CommonClassNames.JAVA_LANG_THROWABLE);
953       }
954       else if (UastExpressionUtils.isMethodCall(newExpression)) {
955         String methodName = newExpression.getMethodName();
956         if (PsiKeyword.SUPER.equals(methodName) || PsiKeyword.THIS.equals(methodName)) {
957           PsiMethod ctor = newExpression.resolve();
958           return ctor != null &&
959                  InheritanceUtil.isInheritor(ctor.getContainingClass(), CommonClassNames.JAVA_LANG_THROWABLE);
960         }
961       }
962     }
963     return false;
964   }
965
966 }