generify
[fedora-idea.git] / inspections / impl / com / intellij / codeInspection / javaDoc / JavaDocLocalInspection.java
blobaf9a6f5beccfbfb1971cad3576336511ffd2e969
1 /*
2 * Copyright (c) 2005 Jet Brains. All Rights Reserved.
3 */
4 package com.intellij.codeInspection.javaDoc;
6 import com.intellij.ExtensionPoints;
7 import com.intellij.codeInsight.CodeInsightUtil;
8 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
9 import com.intellij.codeInsight.daemon.QuickFixBundle;
10 import com.intellij.codeInspection.*;
11 import com.intellij.codeInspection.ex.BaseLocalInspectionTool;
12 import com.intellij.codeInspection.reference.RefJavaUtil;
13 import com.intellij.openapi.diagnostic.Logger;
14 import com.intellij.openapi.editor.Editor;
15 import com.intellij.openapi.editor.ScrollType;
16 import com.intellij.openapi.extensions.Extensions;
17 import com.intellij.openapi.extensions.ExtensionPoint;
18 import com.intellij.openapi.fileEditor.FileEditorManager;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.*;
21 import com.intellij.profile.codeInspection.InspectionProfileManager;
22 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
23 import com.intellij.psi.*;
24 import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
25 import com.intellij.psi.impl.source.jsp.jspJava.JspClass;
26 import com.intellij.psi.impl.source.jsp.jspJava.JspHolderMethod;
27 import com.intellij.psi.javadoc.*;
28 import com.intellij.psi.util.InheritanceUtil;
29 import com.intellij.psi.util.PsiTreeUtil;
30 import com.intellij.ui.DocumentAdapter;
31 import com.intellij.ui.FieldPanel;
32 import com.intellij.ui.IdeBorderFactory;
33 import com.intellij.util.IJSwingUtilities;
34 import com.intellij.util.IncorrectOperationException;
35 import com.intellij.util.ui.UIUtil;
36 import org.jdom.Element;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
41 import javax.swing.*;
42 import javax.swing.event.ChangeEvent;
43 import javax.swing.event.ChangeListener;
44 import javax.swing.event.DocumentEvent;
45 import javax.swing.text.BadLocationException;
46 import javax.swing.text.Document;
47 import java.awt.*;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.ActionListener;
50 import java.util.*;
52 public class JavaDocLocalInspection extends BaseLocalInspectionTool {
53 private static final String REQUIRED_JAVADOC_IS_ABSENT = InspectionsBundle.message("inspection.javadoc.problem.descriptor");
55 @NonNls private static final String NONE = "none";
56 @NonNls private static final String PUBLIC = "public";
57 @NonNls private static final String PROTECTED = "protected";
58 @NonNls private static final String PACKAGE_LOCAL = "package";
59 @NonNls private static final String PRIVATE = "private";
60 @NonNls private static final Set<String> ourUniqueTags = new HashSet<String>();
61 @NonNls public static final String SHORT_NAME = "JavaDoc";
63 static {
64 ourUniqueTags.add("return");
65 ourUniqueTags.add("deprecated");
66 ourUniqueTags.add("serial");
67 ourUniqueTags.add("serialData");
71 public static class Options implements JDOMExternalizable {
72 @NonNls public String ACCESS_JAVADOC_REQUIRED_FOR = NONE;
73 @NonNls public String REQUIRED_TAGS = "";
75 public Options() {
78 public Options(String ACCESS_JAVADOC_REQUIRED_FOR, String REQUIRED_TAGS) {
79 this.ACCESS_JAVADOC_REQUIRED_FOR = ACCESS_JAVADOC_REQUIRED_FOR;
80 this.REQUIRED_TAGS = REQUIRED_TAGS;
83 public void readExternal(Element element) throws InvalidDataException {
84 DefaultJDOMExternalizer.readExternal(this, element);
87 public void writeExternal(Element element) throws WriteExternalException {
88 DefaultJDOMExternalizer.writeExternal(this, element);
92 @NonNls public Options TOP_LEVEL_CLASS_OPTIONS = new Options("none", "");
93 @NonNls public Options INNER_CLASS_OPTIONS = new Options("none", "");
94 @NonNls public Options METHOD_OPTIONS = new Options("none", "@return@param@throws or @exception");
95 @NonNls public Options FIELD_OPTIONS = new Options("none", "");
96 public boolean IGNORE_DEPRECATED = false;
97 public boolean IGNORE_JAVADOC_PERIOD = true;
98 public String myAdditionalJavadocTags = "";
100 private static final Logger LOG = Logger.getInstance("com.intellij.codeInspection.javaDoc.JavaDocLocalInspection");
102 private class OptionsPanel extends JPanel {
103 private JPanel createOptionsPanel(String[] modifiers, String[] tags, Options options) {
104 JPanel pane = new JPanel(new GridLayout(1, tags == null ? 1 : 2));
106 pane.add(createScopePanel(modifiers, options));
107 if (tags != null) {
108 pane.add(createTagsPanel(tags, options));
111 pane.validate();
113 return pane;
116 private JPanel createTagsPanel(String[] tags, Options options) {
117 JPanel panel = new JPanel(new GridBagLayout());
118 panel.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createTitledBorder(InspectionsBundle.message("inspection.javadoc.required.tags.option.title")),
119 BorderFactory.createEmptyBorder(0, 3, 3, 3)));
121 GridBagConstraints gc = new GridBagConstraints();
122 gc.weightx = 1;
123 gc.weighty = 0;
124 gc.fill = GridBagConstraints.HORIZONTAL;
125 gc.anchor = GridBagConstraints.NORTHWEST;
128 for (int i = 0; i < tags.length; i++) {
129 JCheckBox box = new JCheckBox(tags[i]);
130 gc.gridy = i;
131 if (i == tags.length - 1) gc.weighty = 1;
132 panel.add(box, gc);
133 box.setSelected(isTagRequired(options, tags[i]));
134 box.addChangeListener(new MyChangeListener(box, options, tags[i]));
137 return panel;
140 private class MyChangeListener implements ChangeListener {
141 private final JCheckBox myCheckBox;
142 private final Options myOptions;
143 private final String myTagName;
145 public MyChangeListener(JCheckBox checkBox, Options options, String tagName) {
146 myCheckBox = checkBox;
147 myOptions = options;
148 myTagName = tagName;
151 public void stateChanged(ChangeEvent e) {
152 if (myCheckBox.isSelected()) {
153 if (!isTagRequired(myOptions,myTagName)) {
154 myOptions.REQUIRED_TAGS += myTagName;
157 else {
158 myOptions.REQUIRED_TAGS = myOptions.REQUIRED_TAGS.replaceAll(myTagName, "");
163 private JPanel createScopePanel(final String[] modifiers, final Options options) {
164 JPanel panel = new JPanel(new BorderLayout());
165 panel.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createTitledBorder(InspectionsBundle.message("inspection.scope.for.title")),
166 BorderFactory.createEmptyBorder(0, 3, 3, 3)));
168 final Hashtable<Integer, JLabel> sliderLabels = new Hashtable<Integer, JLabel>();
169 for (int i = 0; i < modifiers.length; i++) {
170 sliderLabels.put(i + 1, new JLabel(modifiers[i]));
173 final JSlider slider = new JSlider(SwingConstants.VERTICAL, 1, modifiers.length, 1);
175 slider.setLabelTable(sliderLabels);
176 slider.putClientProperty(UIUtil.JSLIDER_ISFILLED, Boolean.TRUE);
177 slider.setPreferredSize(new Dimension(80, 50));
178 slider.setPaintLabels(true);
179 slider.setSnapToTicks(true);
180 slider.addChangeListener(new ChangeListener() {
181 public void stateChanged(ChangeEvent e) {
182 int value = slider.getValue();
183 options.ACCESS_JAVADOC_REQUIRED_FOR = modifiers[value - 1];
184 for (Integer key : sliderLabels.keySet()) {
185 sliderLabels.get(key).setForeground(key.intValue() <= value ? Color.black : new Color(100, 100, 100));
190 Color fore = Color.black;
191 for (int i = 0; i < modifiers.length; i++) {
192 sliderLabels.get(i + 1).setForeground(fore);
194 if (modifiers[i].equals(options.ACCESS_JAVADOC_REQUIRED_FOR)) {
195 slider.setValue(i + 1);
196 fore = new Color(100, 100, 100);
200 panel.add(slider, BorderLayout.WEST);
202 return panel;
205 public OptionsPanel() {
206 super(new GridBagLayout());
207 GridBagConstraints gc = new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1, 1, GridBagConstraints.NORTH, GridBagConstraints.BOTH, new Insets(0,0,0,0),0,0 );
208 gc.weighty = 0;
209 add(createAdditionalJavadocTagsPanel(), gc);
210 JTabbedPane tabs = new JTabbedPane(SwingConstants.BOTTOM);
211 @NonNls String[] tags = new String[]{"@author", "@version", "@since", "@param"};
212 tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title"), createOptionsPanel(new String[]{NONE, PUBLIC, PACKAGE_LOCAL},
213 tags,
214 TOP_LEVEL_CLASS_OPTIONS));
215 tags = new String[]{"@return", "@param", InspectionsBundle.message("inspection.javadoc.throws.or.exception.option")};
216 tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.method"), createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE},
217 tags,
218 METHOD_OPTIONS));
219 tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.field"), createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE},
220 null,
221 FIELD_OPTIONS));
222 tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.inner.class"), createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE},
223 null,
224 INNER_CLASS_OPTIONS));
225 add(tabs, gc);
227 final JCheckBox checkBox = new JCheckBox(InspectionsBundle.message("inspection.javadoc.option.ignore.deprecated"),
228 IGNORE_DEPRECATED);
229 checkBox.addActionListener(new ActionListener() {
230 public void actionPerformed(ActionEvent e) {
231 IGNORE_DEPRECATED = checkBox.isSelected();
234 gc.gridwidth = 1;
235 add(checkBox, gc);
236 final JCheckBox periodCheckBox = new JCheckBox(InspectionsBundle.message("inspection.javadoc.option.ignore.period"),
237 IGNORE_JAVADOC_PERIOD);
238 periodCheckBox.addActionListener(new ActionListener() {
239 public void actionPerformed(ActionEvent e) {
240 IGNORE_JAVADOC_PERIOD = periodCheckBox.isSelected();
243 add(periodCheckBox, gc);
246 public FieldPanel createAdditionalJavadocTagsPanel(){
247 FieldPanel additionalTagsPanel = new FieldPanel(InspectionsBundle.message("inspection.javadoc.label.text"), InspectionsBundle.message("inspection.javadoc.dialog.title"), null, null);
248 additionalTagsPanel.setPreferredSize(new Dimension(150, additionalTagsPanel.getPreferredSize().height));
249 additionalTagsPanel.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
250 protected void textChanged(DocumentEvent e) {
251 final Document document = e.getDocument();
252 try {
253 final String text = document.getText(0, document.getLength());
254 if (text != null) {
255 myAdditionalJavadocTags = text.trim();
258 catch (BadLocationException e1) {
259 LOG.error(e1);
263 additionalTagsPanel.setText(myAdditionalJavadocTags);
264 return additionalTagsPanel;
268 public JComponent createOptionsPanel() {
269 return new OptionsPanel();
272 private static ProblemDescriptor createDescriptor(@NotNull PsiElement element, String template, InspectionManager manager) {
273 return manager.createProblemDescriptor(element, template, (LocalQuickFix [])null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
276 private static ProblemDescriptor createDescriptor(@NotNull PsiElement element, String template, @NotNull LocalQuickFix fix, InspectionManager manager) {
277 return manager.createProblemDescriptor(element, template, fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
280 private static class AddMissingTagFix implements LocalQuickFix {
281 private final String myTag;
282 private final String myValue;
284 public AddMissingTagFix(@NonNls String tag, String value) {
285 myTag = tag;
286 myValue = value;
288 public AddMissingTagFix(String tag) {
289 this(tag, "");
292 @NotNull
293 public String getName() {
294 return InspectionsBundle.message("inspection.javadoc.problem.add.tag", myTag, myValue);
297 public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
298 final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
299 try {
300 final PsiDocCommentOwner owner = PsiTreeUtil.getParentOfType(descriptor.getEndElement(), PsiDocCommentOwner.class);
301 if (owner != null) {
302 if (!CodeInsightUtil.preparePsiElementsForWrite(owner)) return;
303 final PsiDocComment docComment = owner.getDocComment();
304 final PsiDocTag tag = factory.createDocTagFromText("@" + myTag+" "+myValue, docComment);
305 if (docComment != null) {
306 PsiElement addedTag;
307 final PsiElement anchor = getAnchor();
308 if (anchor != null) {
309 addedTag = docComment.addBefore(tag, anchor);
311 else {
312 addedTag = docComment.add(tag);
314 moveCaretTo(addedTag);
318 catch (IncorrectOperationException e) {
319 LOG.error(e);
323 @Nullable
324 protected PsiElement getAnchor() {
325 return null;
328 private static void moveCaretTo(final PsiElement newCaretPosition) {
329 Project project = newCaretPosition.getProject();
330 final PsiFile psiFile = newCaretPosition.getContainingFile();
331 final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
332 if (editor != null && IJSwingUtilities.hasFocus(editor.getComponent())) {
333 final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
334 if (file == psiFile) {
335 editor.getCaretModel().moveToOffset(newCaretPosition.getTextRange().getEndOffset());
336 editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
341 @NotNull
342 public String getFamilyName() {
343 return InspectionsBundle.message("inspection.javadoc.problem.add.tag.family");
346 @Nullable
347 public ProblemDescriptor[] checkClass(@NotNull PsiClass psiClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
348 if (psiClass instanceof PsiAnonymousClass) return null;
349 if (psiClass instanceof JspClass) return null;
350 if (psiClass instanceof PsiTypeParameter) return null;
351 if (IGNORE_DEPRECATED && psiClass.isDeprecated()) {
352 return null;
354 PsiDocComment docComment = psiClass.getDocComment();
355 final PsiIdentifier nameIdentifier = psiClass.getNameIdentifier();
356 final PsiElement elementToHighlight = nameIdentifier != null ? nameIdentifier : psiClass;
357 if (docComment == null) {
358 return isJavaDocRequired(psiClass)
359 ? new ProblemDescriptor[]{createDescriptor(elementToHighlight, REQUIRED_JAVADOC_IS_ABSENT, manager)}
360 : null;
363 PsiDocTag[] tags = docComment.getTags();
364 @NonNls String[] tagsToCheck = {"author", "version", "since"};
365 @NonNls String[] absentDescriptionKeys = {
366 "inspection.javadoc.problem.missing.author.description",
367 "inspection.javadoc.problem.missing.version.description",
368 "inspection.javadoc.problem.missing.since.description"};
370 boolean[] isTagRequired = new boolean[tagsToCheck.length];
371 boolean[] isTagPresent = new boolean[tagsToCheck.length];
373 boolean someTagsAreRequired = false;
374 for (int i = 0; i < tagsToCheck.length; i++) {
375 final String tag = tagsToCheck[i];
376 someTagsAreRequired |= isTagRequired[i] = isTagRequired(psiClass, tag);
379 if (someTagsAreRequired) {
380 for (PsiDocTag tag : tags) {
381 String tagName = tag.getName();
382 for (int i = 0; i < tagsToCheck.length; i++) {
383 final String tagToCheck = tagsToCheck[i];
384 if (tagToCheck.equals(tagName)) {
385 isTagPresent[i] = true;
391 final ArrayList<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>(2);
393 for (int i = 0; i < tagsToCheck.length; i++) {
394 final String tagToCheck = tagsToCheck[i];
395 if (isTagRequired[i] && !isTagPresent[i]) {
396 problems.add(createMissingTagDescriptor(elementToHighlight, tagToCheck, manager));
399 ArrayList<ProblemDescriptor> tagProblems = getTagValuesProblems(psiClass, tags, manager);
400 if (tagProblems != null) {
401 problems.addAll(tagProblems);
403 checkForPeriodInDoc(docComment, problems, manager);
404 checkInlineTags(manager, problems, docComment.getDescriptionElements(),
405 JavaPsiFacade.getInstance(docComment.getProject()).getJavadocManager());
407 for (PsiDocTag tag : tags) {
408 for (int i = 0; i < tagsToCheck.length; i++) {
409 final String tagToCheck = tagsToCheck[i];
410 if (tagToCheck.equals(tag.getName()) && extractTagDescription(tag).length() == 0) {
411 problems.add(createDescriptor(elementToHighlight, InspectionsBundle.message(absentDescriptionKeys[i]), manager));
416 checkDuplicateTags(tags, problems, manager);
418 if (isTagRequired(psiClass, "param") && psiClass.hasTypeParameters() && nameIdentifier != null) {
419 ArrayList<PsiTypeParameter> absentParameters = null;
420 final PsiTypeParameter[] typeParameters = psiClass.getTypeParameters();
421 for (PsiTypeParameter typeParameter : typeParameters) {
422 if (!isFound(tags, typeParameter)) {
423 if (absentParameters == null) absentParameters = new ArrayList<PsiTypeParameter>(1);
424 absentParameters.add(typeParameter);
427 if (absentParameters != null) {
428 for (PsiTypeParameter psiTypeParameter : absentParameters) {
429 problems.add(createMissingParamTagDescriptor(nameIdentifier, psiTypeParameter, manager));
434 return problems.isEmpty()
435 ? null
436 : problems.toArray(new ProblemDescriptor[problems.size()]);
439 private static ProblemDescriptor createMissingParamTagDescriptor(final PsiIdentifier nameIdentifier,
440 final PsiTypeParameter psiTypeParameter,
441 final InspectionManager manager) {
442 String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "<code>@param</code>");
443 return createDescriptor(nameIdentifier, message, new AddMissingTagFix("param", "<" + psiTypeParameter.getName() + ">"), manager);
446 @Nullable
447 public ProblemDescriptor[] checkField(@NotNull PsiField psiField, @NotNull InspectionManager manager, boolean isOnTheFly) {
448 if (IGNORE_DEPRECATED && (psiField.isDeprecated() || psiField.getContainingClass().isDeprecated())) {
449 return null;
452 PsiDocComment docComment = psiField.getDocComment();
453 if (docComment == null) {
454 return isJavaDocRequired(psiField)
455 ? new ProblemDescriptor[]{createDescriptor(psiField.getNameIdentifier(), REQUIRED_JAVADOC_IS_ABSENT, manager)}
456 : null;
459 final ArrayList<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>(2);
460 ArrayList<ProblemDescriptor> tagProblems = getTagValuesProblems(psiField, docComment.getTags(), manager);
461 if (tagProblems != null) {
462 problems.addAll(tagProblems);
464 checkInlineTags(manager, problems, docComment.getDescriptionElements(),
465 JavaPsiFacade.getInstance(docComment.getProject()).getJavadocManager());
466 checkForPeriodInDoc(docComment, problems, manager);
467 checkDuplicateTags(docComment.getTags(), problems, manager);
468 return problems.isEmpty()
469 ? null
470 : problems.toArray(new ProblemDescriptor[problems.size()]);
473 @Nullable
474 public ProblemDescriptor[] checkMethod(@NotNull PsiMethod psiMethod, @NotNull InspectionManager manager, boolean isOnTheFly) {
475 if (psiMethod instanceof JspHolderMethod) return null;
476 if (IGNORE_DEPRECATED && (psiMethod.isDeprecated() || psiMethod.getContainingClass().isDeprecated())) {
477 return null;
479 PsiDocComment docComment = psiMethod.getDocComment();
480 final PsiMethod[] superMethods = psiMethod.findSuperMethods();
481 if (docComment == null) {
482 if (isJavaDocRequired(psiMethod)) {
483 if (superMethods.length > 0) return null;
484 ExtensionPoint<Condition<PsiMember>> point = Extensions.getRootArea().getExtensionPoint(ExtensionPoints.JAVADOC_LOCAL);
485 final Condition<PsiMember>[] addins = point.getExtensions();
486 for (Condition<PsiMember> addin : addins) {
487 if (addin.value(psiMethod)) return null;
489 if (superMethods.length == 0) {
490 final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier();
491 return nameIdentifier != null ? new ProblemDescriptor[] { createDescriptor(nameIdentifier, REQUIRED_JAVADOC_IS_ABSENT, manager)} : null;
493 else {
494 return null;
497 else {
498 return null;
502 final PsiElement[] descriptionElements = docComment.getDescriptionElements();
503 for (PsiElement descriptionElement : descriptionElements) {
504 if (descriptionElement instanceof PsiInlineDocTag) {
505 if ("inheritDoc".equals(((PsiInlineDocTag)descriptionElement).getName())) return null;
509 final ArrayList<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>(2);
511 checkInlineTags(manager, problems, descriptionElements,
512 JavaPsiFacade.getInstance(docComment.getProject()).getJavadocManager());
514 final PsiDocTag tagByName = docComment.findTagByName("inheritDoc");
515 if (tagByName != null) {
516 final String tagName = tagByName.getName();
517 final JavadocTagInfo tagInfo = JavaPsiFacade.getInstance(tagByName.getProject()).getJavadocManager().getTagInfo(tagName);
518 if (tagInfo != null && tagInfo.isValidInContext(psiMethod)){
519 return null;
523 PsiDocTag[] tags = docComment.getTags();
525 boolean isReturnRequired = false;
526 boolean isReturnAbsent = true;
527 if (superMethods.length == 0 && !psiMethod.isConstructor() && PsiType.VOID != psiMethod.getReturnType() && isTagRequired(psiMethod, "return")) {
528 isReturnRequired = true;
529 for (PsiDocTag tag : tags) {
530 if ("return".equals(tag.getName())) {
531 isReturnAbsent = false;
532 break;
537 ArrayList<PsiParameter> absentParameters = null;
538 if (superMethods.length == 0 && isTagRequired(psiMethod, "param") ) {
539 PsiParameter[] params = psiMethod.getParameterList().getParameters();
540 for (PsiParameter param : params) {
541 if (!isFound(tags, param)) {
542 if (absentParameters == null) absentParameters = new ArrayList<PsiParameter>(2);
543 absentParameters.add(param);
550 if (isReturnRequired && isReturnAbsent) {
551 final PsiIdentifier psiIdentifier = psiMethod.getNameIdentifier();
552 if (psiIdentifier != null) {
553 problems.add(createMissingTagDescriptor(psiIdentifier, "return", manager));
557 if (absentParameters != null) {
558 for (PsiParameter psiParameter : absentParameters) {
559 final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier();
560 if (nameIdentifier != null) {
561 problems.add(createMissingParamTagDescriptor(nameIdentifier, psiParameter, manager));
566 for (PsiDocTag tag : tags) {
567 if ("param".equals(tag.getName())) {
568 final PsiElement[] dataElements = tag.getDataElements();
569 final PsiDocTagValue valueElement = tag.getValueElement();
570 boolean hasProblemsWithTag = dataElements.length < 2;
571 if (!hasProblemsWithTag) {
572 final StringBuilder buf = new StringBuilder();
573 for (PsiElement element : dataElements) {
574 if (element != valueElement){
575 buf.append(element.getText());
578 hasProblemsWithTag = buf.toString().trim().length() == 0;
580 if (hasProblemsWithTag) {
581 if (valueElement != null) {
582 problems.add(createDescriptor(valueElement,
583 InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "<code>@param " + valueElement.getText() + "</code>"),
584 manager));
591 if (superMethods.length == 0 && isTagRequired(psiMethod, "@throws") && psiMethod.getThrowsList().getReferencedTypes().length > 0) {
592 final Map<PsiClassType, PsiClass> declaredExceptions = new HashMap<PsiClassType, PsiClass>();
593 final PsiClassType[] classTypes = psiMethod.getThrowsList().getReferencedTypes();
594 for (PsiClassType classType : classTypes) {
595 final PsiClass psiClass = classType.resolve();
596 if (psiClass != null){
597 declaredExceptions.put(classType, psiClass);
600 processThrowsTags(tags, declaredExceptions, manager, problems);
601 if (!declaredExceptions.isEmpty()) {
602 for (PsiClassType declaredException : declaredExceptions.keySet()) {
603 problems.add(createMissingThrowsTagDescriptor(psiMethod, manager, declaredException));
608 ArrayList<ProblemDescriptor> tagProblems = getTagValuesProblems(psiMethod, tags, manager);
609 if (tagProblems != null) {
610 problems.addAll(tagProblems);
613 checkForPeriodInDoc(docComment, problems, manager);
615 for (PsiDocTag tag : tags) {
616 if ("param".equals(tag.getName())) {
617 if (extractTagDescription(tag).length() == 0) {
618 PsiDocTagValue value = tag.getValueElement();
619 if (value instanceof PsiDocParamRef) {
620 PsiDocParamRef paramRef = (PsiDocParamRef)value;
621 PsiParameter[] params = psiMethod.getParameterList().getParameters();
622 for (PsiParameter param : params) {
623 if (paramRef.getReference().isReferenceTo(param)) {
624 problems.add(createDescriptor(value,
625 InspectionsBundle.message("inspection.javadoc.method.problem.descriptor", "<code>@param</code>", "<code>" + param.getName() + "</code>"),
626 manager));
632 else
633 if ("return".equals(tag.getName())) {
634 if (extractTagDescription(tag).length() == 0) {
635 String message = InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "<code>@return</code>");
636 ProblemDescriptor descriptor = manager.createProblemDescriptor(tag.getNameElement(), message, null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, true);
637 problems.add(descriptor);
642 checkDuplicateTags(tags, problems, manager);
644 return problems.isEmpty()
645 ? null
646 : problems.toArray(new ProblemDescriptor[problems.size()]);
649 private static boolean isFound(final PsiDocTag[] tags, final PsiElement param) {
650 for (PsiDocTag tag : tags) {
651 if ("param".equals(tag.getName())) {
652 PsiDocTagValue value = tag.getValueElement();
653 if (value instanceof PsiDocParamRef) {
654 PsiDocParamRef paramRef = (PsiDocParamRef)value;
655 final PsiReference psiReference = paramRef.getReference();
656 if (psiReference != null && psiReference.isReferenceTo(param)) {
657 return true;
662 return false;
665 private static void processThrowsTags(final PsiDocTag[] tags,
666 final Map<PsiClassType, PsiClass> declaredExceptions,
667 final InspectionManager mananger,
668 @NotNull final ArrayList<ProblemDescriptor> problems) {
669 for (PsiDocTag tag : tags) {
670 if ("throws".equals(tag.getName()) || "exception".equals(tag.getName())) {
671 final PsiDocTagValue value = tag.getValueElement();
672 if (value == null) continue;
673 final PsiElement firstChild = value.getFirstChild();
674 if (firstChild == null) continue;
675 final PsiElement psiElement = firstChild.getFirstChild();
676 if (!(psiElement instanceof PsiJavaCodeReferenceElement)) continue;
677 final PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)psiElement;
678 final PsiElement element = ref.resolve();
679 if (element instanceof PsiClass){
680 final PsiClass exceptionClass = (PsiClass)element;
681 for (Iterator<PsiClassType> it = declaredExceptions.keySet().iterator(); it.hasNext();) {
682 PsiClassType classType = it.next();
683 final PsiClass psiClass = declaredExceptions.get(classType);
684 if (InheritanceUtil.isInheritorOrSelf(exceptionClass, psiClass, true)) {
685 if (extractThrowsTagDescription(tag).length() == 0) {
686 problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "<code>" + tag.getName() + "</code>"), mananger));
688 it.remove();
696 @Nullable
697 private static ProblemDescriptor createMissingThrowsTagDescriptor(final PsiMethod method,
698 final InspectionManager manager,
699 final PsiClassType exceptionClassType) {
700 @NonNls String tag = "throws";
701 String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "<code>@" + tag + "</code> " + exceptionClassType.getCanonicalText());
702 final String firstDeclaredException = exceptionClassType.getCanonicalText();
703 final PsiIdentifier nameIdentifier = method.getNameIdentifier();
704 return nameIdentifier != null ? createDescriptor(nameIdentifier, message,new AddMissingTagFix(tag, firstDeclaredException), manager) : null;
707 private static ProblemDescriptor createMissingTagDescriptor(PsiElement elementToHighlight,
708 @NonNls String tag,
709 final InspectionManager manager) {
710 String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "<code>@" + tag + "</code>");
711 return createDescriptor(elementToHighlight, message,new AddMissingTagFix(tag), manager);
713 private static ProblemDescriptor createMissingParamTagDescriptor(PsiElement elementToHighlight,
714 PsiParameter param,
715 final InspectionManager manager) {
716 String message = InspectionsBundle.message("inspection.javadoc.method.problem.missing.param.tag", "<code>@param</code>", "<code>" + param.getName() + "</code>");
717 return createDescriptor(elementToHighlight, message, new AddMissingParamTagFix(param), manager);
720 private static class AddMissingParamTagFix extends AddMissingTagFix {
721 private final PsiParameter myParam;
723 public AddMissingParamTagFix(final PsiParameter param) {
724 super("param", param.getName());
725 myParam = param;
728 @NotNull
729 public String getName() {
730 return InspectionsBundle.message("inspection.javadoc.problem.add.param.tag", myParam.getName());
733 @Nullable
734 protected PsiElement getAnchor() {
735 final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(myParam, PsiMethod.class);
736 LOG.assertTrue(psiMethod != null);
737 final PsiDocComment docComment = psiMethod.getDocComment();
738 LOG.assertTrue(docComment != null);
739 PsiDocTag[] tags = docComment.findTagsByName("param");
740 if (tags.length == 0) { //insert as first tag or append to description
741 tags = docComment.getTags();
742 if (tags.length == 0) return null;
743 return tags[0];
746 PsiParameter nextParam = PsiTreeUtil.getNextSiblingOfType(myParam, PsiParameter.class);
747 while (nextParam != null) {
748 for (PsiDocTag tag : tags) {
749 if (matches(nextParam, tag)) {
750 return tag;
753 nextParam = PsiTreeUtil.getNextSiblingOfType(nextParam, PsiParameter.class);
756 PsiParameter prevParam = PsiTreeUtil.getPrevSiblingOfType(myParam, PsiParameter.class);
757 while (prevParam != null) {
758 for (PsiDocTag tag : tags) {
759 if (matches(prevParam, tag)) {
760 return PsiTreeUtil.getNextSiblingOfType(tag, PsiDocTag.class);
763 prevParam = PsiTreeUtil.getPrevSiblingOfType(prevParam, PsiParameter.class);
766 return null;
769 private static boolean matches(final PsiParameter param, final PsiDocTag tag) {
770 return tag.getValueElement().getText().trim().startsWith(param.getName());
774 private static String extractTagDescription(PsiDocTag tag) {
775 StringBuilder buf = new StringBuilder();
776 PsiElement[] children = tag.getChildren();
777 for (PsiElement child : children) {
778 if (child instanceof PsiDocToken) {
779 PsiDocToken token = (PsiDocToken)child;
780 if (token.getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA) {
781 buf.append(token.getText());
784 else if (child instanceof PsiDocTagValue) {
785 buf.append(child.getText());
786 } else if (child instanceof PsiInlineDocTag) {
787 buf.append(child.getText());
791 String s = buf.toString();
792 return s.trim();
795 private static String extractThrowsTagDescription(PsiDocTag tag) {
796 StringBuilder buf = new StringBuilder();
797 PsiElement[] children = tag.getChildren();
798 for (PsiElement child : children) {
799 if (child instanceof PsiDocToken) {
800 PsiDocToken token = (PsiDocToken)child;
801 if (token.getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA) {
802 buf.append(token.getText());
807 return buf.toString().trim();
810 private void checkForPeriodInDoc(PsiDocComment docComment,
811 ArrayList<ProblemDescriptor> problems,
812 InspectionManager manager) {
813 if (IGNORE_JAVADOC_PERIOD) return;
814 PsiDocTag[] tags = docComment.getTags();
815 int dotIndex = docComment.getText().indexOf('.');
816 int tagOffset = 0;
817 if (dotIndex >= 0) { //need to find first valid tag
818 final PsiDocCommentOwner owner = PsiTreeUtil.getParentOfType(docComment, PsiDocCommentOwner.class);
819 for (PsiDocTag tag : tags) {
820 final String tagName = tag.getName();
821 final JavadocTagInfo tagInfo = JavaPsiFacade.getInstance(tag.getProject()).getJavadocManager().getTagInfo(tagName);
822 if (tagInfo != null && tagInfo.isValidInContext(owner) && !tagInfo.isInline()) {
823 tagOffset = tag.getTextOffset();
824 break;
829 if (dotIndex == -1 || tagOffset > 0 && dotIndex + docComment.getTextOffset() > tagOffset) {
830 problems.add(manager.createProblemDescriptor(docComment.getFirstChild(),
831 InspectionsBundle.message("inspection.javadoc.problem.descriptor1"),
832 null,
833 ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
834 false));
838 @Nullable
839 private ArrayList<ProblemDescriptor> getTagValuesProblems(PsiDocCommentOwner context, PsiDocTag[] tags, InspectionManager inspectionManager) {
840 final ArrayList<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>(2);
841 nextTag:
842 for (PsiDocTag tag : tags) {
843 final JavadocManager manager = JavaPsiFacade.getInstance(tag.getProject()).getJavadocManager();
844 String tagName = tag.getName();
845 JavadocTagInfo tagInfo = manager.getTagInfo(tagName);
847 if (tagInfo == null || !tagInfo.isValidInContext(context)) {
848 final StringTokenizer tokenizer = new StringTokenizer(myAdditionalJavadocTags, ", ");
849 while (tokenizer.hasMoreTokens()) {
850 if (Comparing.strEqual(tagName, tokenizer.nextToken())) continue nextTag;
853 if (tagInfo == null){
854 problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.wrong.tag", "<code>" + tagName + "</code>"), new AddUnknownTagToCustoms(tag), inspectionManager));
855 } else {
856 problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.disallowed.tag", "<code>" + tagName + "</code>"), new AddUnknownTagToCustoms(tag), inspectionManager));
861 PsiDocTagValue value = tag.getValueElement();
862 final JavadocTagInfo info = manager.getTagInfo(tagName);
863 if (info != null && !info.isValidInContext(context)) continue;
864 String message = info == null ? null : info.checkTagValue(value);
866 final PsiReference reference = value != null ? value.getReference() : null;
867 if (message == null && reference != null) {
868 PsiElement element = reference.resolve();
869 if (element == null) {
870 final int textOffset = value.getTextOffset();
872 if (textOffset == value.getTextRange().getEndOffset()) {
873 problems.add(inspectionManager.createProblemDescriptor(tag, InspectionsBundle.message("inspection.javadoc.problem.name.expected"), null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, true));
878 if (message != null) {
879 final PsiDocTagValue valueElement = tag.getValueElement();
880 if (valueElement == null){
881 problems.add(inspectionManager.createProblemDescriptor(tag, InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "<code>" + tag.getName() + "</code>"), null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, true));
882 } else {
883 problems.add(createDescriptor(valueElement, message, inspectionManager));
886 checkInlineTags(inspectionManager, problems, tag.getDataElements(), manager);
889 return problems.isEmpty() ? null : problems;
892 private void checkInlineTags(final InspectionManager inspectionManager,
893 final ArrayList<ProblemDescriptor> problems,
894 final PsiElement[] dataElements,
895 final JavadocManager manager) {
896 for (PsiElement dataElement : dataElements) {
897 if (dataElement instanceof PsiInlineDocTag) {
898 final PsiInlineDocTag inlineDocTag = (PsiInlineDocTag)dataElement;
899 final PsiElement nameElement = inlineDocTag.getNameElement();
900 if (manager.getTagInfo(inlineDocTag.getName()) == null) {
901 if (nameElement != null) {
902 problems.add(createDescriptor(nameElement, InspectionsBundle.message("inspection.javadoc.problem.wrong.tag", "<code>" + inlineDocTag.getName() + "</code>"), new AddUnknownTagToCustoms(inlineDocTag), inspectionManager));
905 final PsiDocTagValue value = inlineDocTag.getValueElement();
906 if (value != null) {
907 final PsiReference reference = value.getReference();
908 if (reference != null) {
909 final PsiElement ref = reference.resolve();
910 if (ref != null){
911 if (PsiTreeUtil.getParentOfType(inlineDocTag, PsiDocCommentOwner.class) == PsiTreeUtil.getParentOfType(ref, PsiDocCommentOwner.class, false)) {
912 if (nameElement != null) {
913 problems.add(createDescriptor(nameElement, InspectionsBundle.message("inspection.javadoc.problem.pointing.to.itself"), inspectionManager));
923 @SuppressWarnings({"SimplifiableIfStatement"})
924 private boolean isTagRequired(PsiElement context, @NonNls String tag) {
925 if (context instanceof PsiClass) {
926 if (PsiTreeUtil.getParentOfType(context, PsiClass.class) != null) {
927 return isTagRequired(INNER_CLASS_OPTIONS, tag);
930 return isTagRequired(TOP_LEVEL_CLASS_OPTIONS, tag);
933 if (context instanceof PsiMethod) {
934 return isTagRequired(METHOD_OPTIONS, tag);
937 if (context instanceof PsiField) {
938 return isTagRequired(FIELD_OPTIONS, tag);
941 return false;
944 private static boolean isTagRequired(Options options, String tag) {
945 return options.REQUIRED_TAGS.contains(tag);
948 private boolean isJavaDocRequired(PsiModifierListOwner psiElement) {
949 final RefJavaUtil refUtil = RefJavaUtil.getInstance();
950 int actualAccess = getAccessNumber(refUtil.getAccessModifier(psiElement));
951 if (psiElement instanceof PsiClass) {
952 PsiClass psiClass = (PsiClass)psiElement;
953 if (PsiTreeUtil.getParentOfType(psiClass, PsiClass.class) != null) {
954 return actualAccess <= getAccessNumber(INNER_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR);
957 return actualAccess <= getAccessNumber(TOP_LEVEL_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR);
960 if (psiElement instanceof PsiMethod) {
961 psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class);
962 while (psiElement != null) {
963 actualAccess = Math.max(actualAccess, getAccessNumber(refUtil.getAccessModifier(psiElement)));
964 psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class);
967 return actualAccess <= getAccessNumber(METHOD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR);
970 if (psiElement instanceof PsiField) {
971 psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class);
972 while (psiElement != null) {
973 actualAccess = Math.max(actualAccess, getAccessNumber(refUtil.getAccessModifier(psiElement)));
974 psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class);
977 return actualAccess <= getAccessNumber(FIELD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR);
980 return false;
983 private static void checkDuplicateTags(final PsiDocTag[] tags,
984 ArrayList<ProblemDescriptor> problems,
985 final InspectionManager manager) {
986 Set<String> documentedParamNames = null;
987 Set<String> documentedExceptions = null;
988 Set<String> uniqueTags = null;
989 for(PsiDocTag tag: tags) {
990 if ("param".equals(tag.getName())) {
991 PsiDocTagValue value = tag.getValueElement();
992 if (value instanceof PsiDocParamRef) {
993 PsiDocParamRef paramRef = (PsiDocParamRef)value;
994 final PsiReference reference = paramRef.getReference();
995 if (reference != null) {
996 final String paramName = reference.getCanonicalText();
997 if (documentedParamNames == null) {
998 documentedParamNames = new HashSet<String>();
1000 if (documentedParamNames.contains(paramName)) {
1001 problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.duplicate.param", paramName), manager));
1003 documentedParamNames.add(paramName);
1007 else if ("throws".equals(tag.getName()) || "exception".equals(tag.getName())) {
1008 PsiDocTagValue value = tag.getValueElement();
1009 if (value != null) {
1010 final PsiElement firstChild = value.getFirstChild();
1011 if (firstChild != null && firstChild.getFirstChild() instanceof PsiJavaCodeReferenceElement) {
1012 PsiJavaCodeReferenceElement refElement = (PsiJavaCodeReferenceElement) firstChild.getFirstChild();
1013 if (refElement != null) {
1014 PsiElement element = refElement.resolve();
1015 if (element instanceof PsiClass) {
1016 String fqName = ((PsiClass)element).getQualifiedName();
1017 if (documentedExceptions == null) {
1018 documentedExceptions = new HashSet<String>();
1020 if (documentedExceptions.contains(fqName)) {
1021 problems.add(createDescriptor(tag.getNameElement(),
1022 InspectionsBundle.message("inspection.javadoc.problem.duplicate.throws", fqName),
1023 manager));
1025 documentedExceptions.add(fqName);
1031 else if (JavaDocLocalInspection.ourUniqueTags.contains(tag.getName())) {
1032 if (uniqueTags == null) {
1033 uniqueTags = new HashSet<String>();
1035 if (uniqueTags.contains(tag.getName())) {
1036 problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.duplicate.tag", tag.getName()), manager));
1038 uniqueTags.add(tag.getName());
1043 private static int getAccessNumber(@NonNls String accessModifier) {
1044 if (accessModifier.startsWith("none")) return 0;
1045 if (accessModifier.startsWith("public")) return 1;
1046 if (accessModifier.startsWith("protected")) return 2;
1047 if (accessModifier.startsWith("package")) return 3;
1048 if (accessModifier.startsWith("private")) return 4;
1050 return 5;
1053 @NotNull
1054 public String getDisplayName() {
1055 return InspectionsBundle.message("inspection.javadoc.display.name");
1058 @NotNull
1059 public String getGroupDisplayName() {
1060 return "";
1063 @NotNull
1064 public String getShortName() {
1065 return SHORT_NAME;
1068 private class AddUnknownTagToCustoms implements LocalQuickFix {
1069 PsiDocTag myTag;
1071 public AddUnknownTagToCustoms(PsiDocTag tag) {
1072 myTag = tag;
1075 @NotNull
1076 public String getName() {
1077 return QuickFixBundle.message("add.doctag.to.custom.tags", myTag.getName());
1080 @NotNull
1081 public String getFamilyName() {
1082 return QuickFixBundle.message("fix.javadoc.family");
1085 public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
1086 if (myTag == null || !myTag.isValid()) return;
1087 if (myAdditionalJavadocTags.length() > 0) {
1088 myAdditionalJavadocTags += "," + myTag.getName();
1090 else {
1091 myAdditionalJavadocTags = myTag.getName();
1093 final InspectionProfile inspectionProfile =
1094 InspectionProjectProfileManager.getInstance(project).getInspectionProfile(myTag);
1095 //correct save settings
1096 ((ModifiableModel)inspectionProfile).isProperSetting(HighlightDisplayKey.find(SHORT_NAME));
1097 InspectionProfileManager.getInstance().fireProfileChanged(inspectionProfile);
1098 //TODO lesya
1102 try {
1103 inspectionProfile.save();
1105 catch (IOException e) {
1106 Messages.showErrorDialog(project, e.getMessage(), CommonBundle.getErrorTitle());