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
,
48 return manager
.createProblemDescriptor(element
, template
, onTheFly
, (LocalQuickFix
[])null, ProblemHighlightType
.LIKE_UNKNOWN_SYMBOL
);
52 public ProblemDescriptor
[] checkMethod(@NotNull PsiMethod psiMethod
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
53 return checkMember(psiMethod
, manager
, isOnTheFly
);
57 public ProblemDescriptor
[] checkField(@NotNull PsiField field
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
58 return checkMember(field
, manager
, isOnTheFly
);
62 private ProblemDescriptor
[] checkMember(final PsiDocCommentOwner docCommentOwner
, final InspectionManager manager
, final boolean isOnTheFly
) {
63 ArrayList
<ProblemDescriptor
> problems
= new ArrayList
<ProblemDescriptor
>();
64 final PsiDocComment docComment
= docCommentOwner
.getDocComment();
65 if (docComment
== null) return null;
67 final Set
<PsiJavaCodeReferenceElement
> references
= new HashSet
<PsiJavaCodeReferenceElement
>();
68 docComment
.accept(getVisitor(references
, docCommentOwner
, problems
, manager
, isOnTheFly
));
69 for (PsiJavaCodeReferenceElement reference
: references
) {
70 final List
<PsiClass
> classesToImport
= new ImportClassFix(reference
).getClassesToImport();
71 problems
.add(manager
.createProblemDescriptor(reference
, InspectionsBundle
.message("inspection.javadoc.problem.cannot.resolve",
72 "<code>" + reference
.getText() + "</code>"),
73 !isOnTheFly
|| classesToImport
.isEmpty() ?
null : new AddImportFix(classesToImport
), ProblemHighlightType
.LIKE_UNKNOWN_SYMBOL
,
77 return problems
.isEmpty()
79 : problems
.toArray(new ProblemDescriptor
[problems
.size()]);
83 public ProblemDescriptor
[] checkClass(@NotNull PsiClass aClass
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
84 return checkMember(aClass
, manager
, isOnTheFly
);
88 private PsiElementVisitor
getVisitor(final Set
<PsiJavaCodeReferenceElement
> references
,
89 final PsiElement context
,
90 final ArrayList
<ProblemDescriptor
> problems
,
91 final InspectionManager manager
, final boolean onTheFly
) {
92 return new JavaElementVisitor() {
93 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
94 visitElement(expression
);
97 @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference
) {
98 super.visitReferenceElement(reference
);
99 JavaResolveResult result
= reference
.advancedResolve(false);
100 if (result
.getElement() == null && !result
.isPackagePrefixPackageReference()) {
101 references
.add(reference
);
105 @Override public void visitDocTag(PsiDocTag tag
) {
106 super.visitDocTag(tag
);
107 final JavadocManager javadocManager
= JavaPsiFacade
.getInstance(tag
.getProject()).getJavadocManager();
108 final JavadocTagInfo info
= javadocManager
.getTagInfo(tag
.getName());
109 if (info
== null || !info
.isInline()) {
110 visitRefInDocTag(tag
, javadocManager
, context
, problems
, manager
, onTheFly
);
114 @Override public void visitInlineDocTag(PsiInlineDocTag tag
) {
115 super.visitInlineDocTag(tag
);
116 final JavadocManager javadocManager
= JavaPsiFacade
.getInstance(tag
.getProject()).getJavadocManager();
117 visitRefInDocTag(tag
, javadocManager
, context
, problems
, manager
, onTheFly
);
120 @Override public void visitElement(PsiElement element
) {
121 PsiElement
[] children
= element
.getChildren();
122 for (PsiElement child
: children
) {
123 //do not visit method javadoc twice
124 if (!(child
instanceof PsiDocCommentOwner
)) {
132 public static void visitRefInDocTag(final PsiDocTag tag
, final JavadocManager manager
, final PsiElement context
, ArrayList
<ProblemDescriptor
> problems
,
133 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
, onTheFly
));
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
,
164 public String
getDisplayName() {
169 public String
getGroupDisplayName() {
174 public String
getShortName() {
179 public HighlightDisplayLevel
getDefaultLevel() {
180 return HighlightDisplayLevel
.ERROR
;
183 private class AddImportFix
implements LocalQuickFix
{
184 private final List
<PsiClass
> myClassesToImport
;
186 public AddImportFix(final List
<PsiClass
> classesToImport
) {
187 myClassesToImport
= classesToImport
;
191 public String
getName() {
192 return QuickFixBundle
.message("import.class.fix");
196 public String
getFamilyName() {
197 return QuickFixBundle
.message("import.class.fix");
200 public void applyFix(@NotNull final Project project
, @NotNull final ProblemDescriptor descriptor
) {
201 final PsiElement element
= descriptor
.getPsiElement();
202 if (element
instanceof PsiJavaCodeReferenceElement
) {
203 final PsiJavaCodeReferenceElement referenceElement
= (PsiJavaCodeReferenceElement
)element
;
204 Collections
.sort(myClassesToImport
, new PsiProximityComparator(referenceElement
.getElement()));
205 final JList list
= new JList(myClassesToImport
.toArray(new PsiClass
[myClassesToImport
.size()]));
206 list
.setCellRenderer(new FQNameCellRenderer());
207 Runnable runnable
= new Runnable() {
209 if (!element
.isValid()) return;
210 final int index
= list
.getSelectedIndex();
211 if (index
< 0) return;
212 new WriteCommandAction(project
, element
.getContainingFile()){
213 protected void run(final Result result
) throws Throwable
{
214 final PsiClass psiClass
= myClassesToImport
.get(index
);
215 if (psiClass
.isValid()) {
216 PsiDocumentManager
.getInstance(project
).commitAllDocuments();
217 referenceElement
.bindToElement(psiClass
);
223 final Editor editor
= PlatformDataKeys
.EDITOR
.getData(DataManager
.getInstance().getDataContext());
224 assert editor
!= null; //available for on the fly mode only
225 new PopupChooserBuilder(list
).
226 setTitle(QuickFixBundle
.message("class.to.import.chooser.title")).
227 setItemChoosenCallback(runnable
).
229 showInBestPositionFor(editor
);