2 * Copyright (c) 2005 Jet Brains. All Rights Reserved.
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
;
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
;
48 import java
.awt
.event
.ActionEvent
;
49 import java
.awt
.event
.ActionListener
;
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";
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
= "";
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
));
108 pane
.add(createTagsPanel(tags
, options
));
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();
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
]);
131 if (i
== tags
.length
- 1) gc
.weighty
= 1;
133 box
.setSelected(isTagRequired(options
, tags
[i
]));
134 box
.addChangeListener(new MyChangeListener(box
, options
, tags
[i
]));
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
;
151 public void stateChanged(ChangeEvent e
) {
152 if (myCheckBox
.isSelected()) {
153 if (!isTagRequired(myOptions
,myTagName
)) {
154 myOptions
.REQUIRED_TAGS
+= myTagName
;
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
);
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 );
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
},
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
},
219 tabs
.add(InspectionsBundle
.message("inspection.javadoc.option.tab.title.field"), createOptionsPanel(new String
[]{NONE
, PUBLIC
, PROTECTED
, PACKAGE_LOCAL
, PRIVATE
},
222 tabs
.add(InspectionsBundle
.message("inspection.javadoc.option.tab.title.inner.class"), createOptionsPanel(new String
[]{NONE
, PUBLIC
, PROTECTED
, PACKAGE_LOCAL
, PRIVATE
},
224 INNER_CLASS_OPTIONS
));
227 final JCheckBox checkBox
= new JCheckBox(InspectionsBundle
.message("inspection.javadoc.option.ignore.deprecated"),
229 checkBox
.addActionListener(new ActionListener() {
230 public void actionPerformed(ActionEvent e
) {
231 IGNORE_DEPRECATED
= checkBox
.isSelected();
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();
253 final String text
= document
.getText(0, document
.getLength());
255 myAdditionalJavadocTags
= text
.trim();
258 catch (BadLocationException 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
) {
288 public AddMissingTagFix(String tag
) {
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();
300 final PsiDocCommentOwner owner
= PsiTreeUtil
.getParentOfType(descriptor
.getEndElement(), PsiDocCommentOwner
.class);
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) {
307 final PsiElement anchor
= getAnchor();
308 if (anchor
!= null) {
309 addedTag
= docComment
.addBefore(tag
, anchor
);
312 addedTag
= docComment
.add(tag
);
314 moveCaretTo(addedTag
);
318 catch (IncorrectOperationException e
) {
324 protected PsiElement
getAnchor() {
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
);
342 public String
getFamilyName() {
343 return InspectionsBundle
.message("inspection.javadoc.problem.add.tag.family");
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()) {
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
)}
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()
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
);
447 public ProblemDescriptor
[] checkField(@NotNull PsiField psiField
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
448 if (IGNORE_DEPRECATED
&& (psiField
.isDeprecated() || psiField
.getContainingClass().isDeprecated())) {
452 PsiDocComment docComment
= psiField
.getDocComment();
453 if (docComment
== null) {
454 return isJavaDocRequired(psiField
)
455 ?
new ProblemDescriptor
[]{createDescriptor(psiField
.getNameIdentifier(), REQUIRED_JAVADOC_IS_ABSENT
, manager
)}
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()
470 : problems
.toArray(new ProblemDescriptor
[problems
.size()]);
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())) {
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;
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
)){
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;
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>"),
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>"),
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()
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
)) {
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
));
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
,
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
,
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());
729 public String
getName() {
730 return InspectionsBundle
.message("inspection.javadoc.problem.add.param.tag", myParam
.getName());
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;
746 PsiParameter nextParam
= PsiTreeUtil
.getNextSiblingOfType(myParam
, PsiParameter
.class);
747 while (nextParam
!= null) {
748 for (PsiDocTag tag
: tags
) {
749 if (matches(nextParam
, 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);
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();
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('.');
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();
829 if (dotIndex
== -1 || tagOffset
> 0 && dotIndex
+ docComment
.getTextOffset() > tagOffset
) {
830 problems
.add(manager
.createProblemDescriptor(docComment
.getFirstChild(),
831 InspectionsBundle
.message("inspection.javadoc.problem.descriptor1"),
833 ProblemHighlightType
.GENERIC_ERROR_OR_WARNING
,
839 private ArrayList
<ProblemDescriptor
> getTagValuesProblems(PsiDocCommentOwner context
, PsiDocTag
[] tags
, InspectionManager inspectionManager
) {
840 final ArrayList
<ProblemDescriptor
> problems
= new ArrayList
<ProblemDescriptor
>(2);
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
));
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));
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();
907 final PsiReference reference
= value
.getReference();
908 if (reference
!= null) {
909 final PsiElement ref
= reference
.resolve();
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
);
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
);
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
),
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;
1054 public String
getDisplayName() {
1055 return InspectionsBundle
.message("inspection.javadoc.display.name");
1059 public String
getGroupDisplayName() {
1064 public String
getShortName() {
1068 private class AddUnknownTagToCustoms
implements LocalQuickFix
{
1071 public AddUnknownTagToCustoms(PsiDocTag tag
) {
1076 public String
getName() {
1077 return QuickFixBundle
.message("add.doctag.to.custom.tags", myTag
.getName());
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();
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
);
1103 inspectionProfile.save();
1105 catch (IOException e) {
1106 Messages.showErrorDialog(project, e.getMessage(), CommonBundle.getErrorTitle());