2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.codeInspection
.javaDoc
;
18 import com
.intellij
.codeHighlighting
.HighlightDisplayLevel
;
19 import com
.intellij
.codeInsight
.daemon
.QuickFixBundle
;
20 import com
.intellij
.codeInsight
.daemon
.impl
.quickfix
.ImportClassFix
;
21 import com
.intellij
.codeInspection
.*;
22 import com
.intellij
.codeInspection
.ex
.BaseLocalInspectionTool
;
23 import com
.intellij
.ide
.DataManager
;
24 import com
.intellij
.ide
.util
.FQNameCellRenderer
;
25 import com
.intellij
.openapi
.actionSystem
.PlatformDataKeys
;
26 import com
.intellij
.openapi
.application
.Result
;
27 import com
.intellij
.openapi
.command
.WriteCommandAction
;
28 import com
.intellij
.openapi
.editor
.Editor
;
29 import com
.intellij
.openapi
.project
.Project
;
30 import com
.intellij
.openapi
.ui
.popup
.PopupChooserBuilder
;
31 import com
.intellij
.psi
.*;
32 import com
.intellij
.psi
.javadoc
.*;
33 import com
.intellij
.psi
.util
.proximity
.PsiProximityComparator
;
34 import org
.jetbrains
.annotations
.NonNls
;
35 import org
.jetbrains
.annotations
.NotNull
;
36 import org
.jetbrains
.annotations
.Nullable
;
41 public class JavaDocReferenceInspection
extends BaseLocalInspectionTool
{
42 @NonNls public static final String SHORT_NAME
= "JavadocReference";
43 public static final String DISPLAY_NAME
= InspectionsBundle
.message("inspection.javadoc.ref.display.name");
46 private static ProblemDescriptor
createDescriptor(@NotNull PsiElement element
, String template
, InspectionManager manager
) {
47 return manager
.createProblemDescriptor(element
, template
, (LocalQuickFix
[])null, ProblemHighlightType
.LIKE_UNKNOWN_SYMBOL
);
51 public ProblemDescriptor
[] checkMethod(@NotNull PsiMethod psiMethod
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
52 return checkMember(psiMethod
, manager
, isOnTheFly
);
56 public ProblemDescriptor
[] checkField(@NotNull PsiField field
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
57 return checkMember(field
, manager
, isOnTheFly
);
61 private ProblemDescriptor
[] checkMember(final PsiDocCommentOwner docCommentOwner
, final InspectionManager manager
, final boolean isOnTheFly
) {
62 ArrayList
<ProblemDescriptor
> problems
= new ArrayList
<ProblemDescriptor
>();
63 final PsiDocComment docComment
= docCommentOwner
.getDocComment();
64 if (docComment
== null) return null;
66 final Set
<PsiJavaCodeReferenceElement
> references
= new HashSet
<PsiJavaCodeReferenceElement
>();
67 docComment
.accept(getVisitor(references
, docCommentOwner
, problems
, manager
));
68 for (PsiJavaCodeReferenceElement reference
: references
) {
69 final List
<PsiClass
> classesToImport
= new ImportClassFix(reference
).getClassesToImport();
70 problems
.add(manager
.createProblemDescriptor(reference
, InspectionsBundle
.message("inspection.javadoc.problem.cannot.resolve",
71 "<code>" + reference
.getText() + "</code>"),
72 !isOnTheFly
|| classesToImport
.isEmpty() ?
null : new AddImportFix(classesToImport
), ProblemHighlightType
.LIKE_UNKNOWN_SYMBOL
));
75 return problems
.isEmpty()
77 : problems
.toArray(new ProblemDescriptor
[problems
.size()]);
81 public ProblemDescriptor
[] checkClass(@NotNull PsiClass aClass
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
82 return checkMember(aClass
, manager
, isOnTheFly
);
86 private PsiElementVisitor
getVisitor(final Set
<PsiJavaCodeReferenceElement
> references
,
87 final PsiElement context
,
88 final ArrayList
<ProblemDescriptor
> problems
,
89 final InspectionManager manager
) {
90 return new JavaElementVisitor() {
91 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
92 visitElement(expression
);
95 @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference
) {
96 super.visitReferenceElement(reference
);
97 JavaResolveResult result
= reference
.advancedResolve(false);
98 if (result
.getElement() == null && !result
.isPackagePrefixPackageReference()) {
99 references
.add(reference
);
103 @Override public void visitDocTag(PsiDocTag tag
) {
104 super.visitDocTag(tag
);
105 final JavadocManager javadocManager
= JavaPsiFacade
.getInstance(tag
.getProject()).getJavadocManager();
106 final JavadocTagInfo info
= javadocManager
.getTagInfo(tag
.getName());
107 if (info
== null || !info
.isInline()) {
108 visitRefInDocTag(tag
, javadocManager
, context
, problems
, manager
);
112 @Override public void visitInlineDocTag(PsiInlineDocTag tag
) {
113 super.visitInlineDocTag(tag
);
114 final JavadocManager javadocManager
= JavaPsiFacade
.getInstance(tag
.getProject()).getJavadocManager();
115 visitRefInDocTag(tag
, javadocManager
, context
, problems
, manager
);
118 @Override public void visitElement(PsiElement element
) {
119 PsiElement
[] children
= element
.getChildren();
120 for (PsiElement child
: children
) {
121 //do not visit method javadoc twice
122 if (!(child
instanceof PsiDocCommentOwner
)) {
130 public static void visitRefInDocTag(final PsiDocTag tag
,
131 final JavadocManager manager
,
132 final PsiElement context
,
133 ArrayList
<ProblemDescriptor
> problems
,
134 InspectionManager inspectionManager
) {
135 String tagName
= tag
.getName();
136 PsiDocTagValue value
= tag
.getValueElement();
137 if (value
== null) return;
138 final JavadocTagInfo info
= manager
.getTagInfo(tagName
);
139 if (info
!= null && !info
.isValidInContext(context
)) return;
140 String message
= info
== null || !info
.isInline() ?
null : info
.checkTagValue(value
);
141 if (message
!= null){
142 problems
.add(createDescriptor(value
, message
, inspectionManager
));
144 final PsiReference reference
= value
.getReference();
145 if (reference
!= null) {
146 PsiElement element
= reference
.resolve();
147 if (element
== null) {
148 final int textOffset
= value
.getTextOffset();
150 if (textOffset
!= value
.getTextRange().getEndOffset()) {
151 final PsiDocTagValue valueElement
= tag
.getValueElement();
152 if (valueElement
!= null) {
153 @NonNls String params
= "<code>" + value
.getContainingFile().getViewProvider().getContents().subSequence(textOffset
, value
.getTextRange().getEndOffset()) + "</code>";
154 problems
.add(createDescriptor(valueElement
, InspectionsBundle
.message("inspection.javadoc.problem.cannot.resolve", params
), inspectionManager
));
163 public String
getDisplayName() {
168 public String
getGroupDisplayName() {
173 public String
getShortName() {
178 public HighlightDisplayLevel
getDefaultLevel() {
179 return HighlightDisplayLevel
.ERROR
;
182 private class AddImportFix
implements LocalQuickFix
{
183 private final List
<PsiClass
> myClassesToImport
;
185 public AddImportFix(final List
<PsiClass
> classesToImport
) {
186 myClassesToImport
= classesToImport
;
190 public String
getName() {
191 return QuickFixBundle
.message("import.class.fix");
195 public String
getFamilyName() {
196 return QuickFixBundle
.message("import.class.fix");
199 public void applyFix(@NotNull final Project project
, @NotNull final ProblemDescriptor descriptor
) {
200 final PsiElement element
= descriptor
.getPsiElement();
201 if (element
instanceof PsiJavaCodeReferenceElement
) {
202 final PsiJavaCodeReferenceElement referenceElement
= (PsiJavaCodeReferenceElement
)element
;
203 Collections
.sort(myClassesToImport
, new PsiProximityComparator(referenceElement
.getElement()));
204 final JList list
= new JList(myClassesToImport
.toArray(new PsiClass
[myClassesToImport
.size()]));
205 list
.setCellRenderer(new FQNameCellRenderer());
206 Runnable runnable
= new Runnable() {
208 if (!element
.isValid()) return;
209 final int index
= list
.getSelectedIndex();
210 if (index
< 0) return;
211 new WriteCommandAction(project
, element
.getContainingFile()){
212 protected void run(final Result result
) throws Throwable
{
213 final PsiClass psiClass
= myClassesToImport
.get(index
);
214 if (psiClass
.isValid()) {
215 PsiDocumentManager
.getInstance(project
).commitAllDocuments();
216 referenceElement
.bindToElement(psiClass
);
222 final Editor editor
= PlatformDataKeys
.EDITOR
.getData(DataManager
.getInstance().getDataContext());
223 assert editor
!= null; //available for on the fly mode only
224 new PopupChooserBuilder(list
).
225 setTitle(QuickFixBundle
.message("class.to.import.chooser.title")).
226 setItemChoosenCallback(runnable
).
228 showInBestPositionFor(editor
);