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
.codeInsight
.lookup
.LookupElement
;
22 import com
.intellij
.codeInsight
.lookup
.LookupElementBuilder
;
23 import com
.intellij
.codeInsight
.lookup
.LookupManager
;
24 import com
.intellij
.codeInspection
.*;
25 import com
.intellij
.codeInspection
.ex
.BaseLocalInspectionTool
;
26 import com
.intellij
.codeInspection
.ex
.ProblemDescriptorImpl
;
27 import com
.intellij
.ide
.DataManager
;
28 import com
.intellij
.ide
.util
.FQNameCellRenderer
;
29 import com
.intellij
.openapi
.actionSystem
.PlatformDataKeys
;
30 import com
.intellij
.openapi
.application
.Result
;
31 import com
.intellij
.openapi
.command
.WriteCommandAction
;
32 import com
.intellij
.openapi
.editor
.Editor
;
33 import com
.intellij
.openapi
.project
.Project
;
34 import com
.intellij
.openapi
.ui
.popup
.PopupChooserBuilder
;
35 import com
.intellij
.openapi
.util
.TextRange
;
36 import com
.intellij
.openapi
.util
.text
.StringUtil
;
37 import com
.intellij
.psi
.*;
38 import com
.intellij
.psi
.javadoc
.*;
39 import com
.intellij
.psi
.util
.PsiTreeUtil
;
40 import com
.intellij
.psi
.util
.proximity
.PsiProximityComparator
;
41 import org
.jetbrains
.annotations
.NonNls
;
42 import org
.jetbrains
.annotations
.NotNull
;
43 import org
.jetbrains
.annotations
.Nullable
;
48 public class JavaDocReferenceInspection
extends BaseLocalInspectionTool
{
49 @NonNls public static final String SHORT_NAME
= "JavadocReference";
50 public static final String DISPLAY_NAME
= InspectionsBundle
.message("inspection.javadoc.ref.display.name");
53 private static ProblemDescriptor
createDescriptor(@NotNull PsiElement element
, String template
, InspectionManager manager
,
55 return manager
.createProblemDescriptor(element
, template
, onTheFly
, (LocalQuickFix
[])null, ProblemHighlightType
.LIKE_UNKNOWN_SYMBOL
);
59 public ProblemDescriptor
[] checkMethod(@NotNull PsiMethod psiMethod
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
60 return checkMember(psiMethod
, manager
, isOnTheFly
);
64 public ProblemDescriptor
[] checkField(@NotNull PsiField field
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
65 return checkMember(field
, manager
, isOnTheFly
);
69 private ProblemDescriptor
[] checkMember(final PsiDocCommentOwner docCommentOwner
, final InspectionManager manager
, final boolean isOnTheFly
) {
70 ArrayList
<ProblemDescriptor
> problems
= new ArrayList
<ProblemDescriptor
>();
71 final PsiDocComment docComment
= docCommentOwner
.getDocComment();
72 if (docComment
== null) return null;
74 final Set
<PsiJavaCodeReferenceElement
> references
= new HashSet
<PsiJavaCodeReferenceElement
>();
75 docComment
.accept(getVisitor(references
, docCommentOwner
, problems
, manager
, isOnTheFly
));
76 for (PsiJavaCodeReferenceElement reference
: references
) {
77 final List
<PsiClass
> classesToImport
= new ImportClassFix(reference
).getClassesToImport();
78 problems
.add(manager
.createProblemDescriptor(reference
, cannotResolveSymbolMessage("<code>" + reference
.getText() + "</code>"),
79 !isOnTheFly
|| classesToImport
.isEmpty() ?
null : new AddImportFix(classesToImport
), ProblemHighlightType
.LIKE_UNKNOWN_SYMBOL
,
83 return problems
.isEmpty()
85 : problems
.toArray(new ProblemDescriptor
[problems
.size()]);
89 public ProblemDescriptor
[] checkClass(@NotNull PsiClass aClass
, @NotNull InspectionManager manager
, boolean isOnTheFly
) {
90 return checkMember(aClass
, manager
, isOnTheFly
);
94 private PsiElementVisitor
getVisitor(final Set
<PsiJavaCodeReferenceElement
> references
,
95 final PsiElement context
,
96 final ArrayList
<ProblemDescriptor
> problems
,
97 final InspectionManager manager
, final boolean onTheFly
) {
98 return new JavaElementVisitor() {
99 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
100 visitElement(expression
);
103 @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference
) {
104 super.visitReferenceElement(reference
);
105 JavaResolveResult result
= reference
.advancedResolve(false);
106 if (result
.getElement() == null && !result
.isPackagePrefixPackageReference()) {
107 references
.add(reference
);
111 @Override public void visitDocTag(PsiDocTag tag
) {
112 super.visitDocTag(tag
);
113 final JavadocManager javadocManager
= JavaPsiFacade
.getInstance(tag
.getProject()).getJavadocManager();
114 final JavadocTagInfo info
= javadocManager
.getTagInfo(tag
.getName());
115 if (info
== null || !info
.isInline()) {
116 visitRefInDocTag(tag
, javadocManager
, context
, problems
, manager
, onTheFly
);
120 @Override public void visitInlineDocTag(PsiInlineDocTag tag
) {
121 super.visitInlineDocTag(tag
);
122 final JavadocManager javadocManager
= JavaPsiFacade
.getInstance(tag
.getProject()).getJavadocManager();
123 visitRefInDocTag(tag
, javadocManager
, context
, problems
, manager
, onTheFly
);
126 @Override public void visitElement(PsiElement element
) {
127 PsiElement
[] children
= element
.getChildren();
128 for (PsiElement child
: children
) {
129 //do not visit method javadoc twice
130 if (!(child
instanceof PsiDocCommentOwner
)) {
138 public static void visitRefInDocTag(final PsiDocTag tag
, final JavadocManager manager
, final PsiElement context
, ArrayList
<ProblemDescriptor
> problems
,
139 InspectionManager inspectionManager
,
141 final String tagName
= tag
.getName();
142 PsiDocTagValue value
= tag
.getValueElement();
143 if (value
== null) return;
144 final JavadocTagInfo info
= manager
.getTagInfo(tagName
);
145 if (info
!= null && !info
.isValidInContext(context
)) return;
146 String message
= info
== null || !info
.isInline() ?
null : info
.checkTagValue(value
);
147 if (message
!= null){
148 problems
.add(createDescriptor(value
, message
, inspectionManager
, onTheFly
));
150 final PsiReference reference
= value
.getReference();
151 if (reference
!= null) {
152 PsiElement element
= reference
.resolve();
153 if (element
== null) {
154 final int textOffset
= value
.getTextOffset();
156 if (textOffset
!= value
.getTextRange().getEndOffset()) {
157 final PsiDocTagValue valueElement
= tag
.getValueElement();
158 if (valueElement
!= null) {
159 final CharSequence paramName
=
160 value
.getContainingFile().getViewProvider().getContents().subSequence(textOffset
, value
.getTextRange().getEndOffset());
161 @NonNls String params
= "<code>" + paramName
+ "</code>";
163 final List
<LocalQuickFix
> fixes
= new ArrayList
<LocalQuickFix
>();
164 if (onTheFly
&& "param".equals(tagName
)) {
165 final PsiDocCommentOwner commentOwner
= PsiTreeUtil
.getParentOfType(tag
, PsiDocCommentOwner
.class);
166 if (commentOwner
instanceof PsiMethod
) {
167 final PsiMethod method
= (PsiMethod
)commentOwner
;
168 final PsiParameter
[] parameters
= method
.getParameterList().getParameters();
169 final PsiDocTag
[] tags
= tag
.getContainingComment().getTags();
170 final Set
<String
> unboundParams
= new HashSet
<String
>();
171 for (PsiParameter parameter
: parameters
) {
172 if (!JavaDocLocalInspection
.isFound(tags
, parameter
)) {
173 unboundParams
.add(parameter
.getName());
176 if (!unboundParams
.isEmpty()) {
177 fixes
.add(new RenameReferenceQuickFix(unboundParams
));
181 fixes
.add(new RemoveTagFix(tagName
, paramName
, tag
));
183 problems
.add(inspectionManager
.createProblemDescriptor(valueElement
, cannotResolveSymbolMessage(params
), onTheFly
,
184 fixes
.toArray(new LocalQuickFix
[fixes
.size()]),
185 ProblemHighlightType
.LIKE_UNKNOWN_SYMBOL
));
192 private static String
cannotResolveSymbolMessage(String params
) {
193 return InspectionsBundle
.message("inspection.javadoc.problem.cannot.resolve", params
);
197 public String
getDisplayName() {
202 public String
getGroupDisplayName() {
207 public String
getShortName() {
212 public HighlightDisplayLevel
getDefaultLevel() {
213 return HighlightDisplayLevel
.ERROR
;
216 private class AddImportFix
implements LocalQuickFix
{
217 private final List
<PsiClass
> myClassesToImport
;
219 public AddImportFix(final List
<PsiClass
> classesToImport
) {
220 myClassesToImport
= classesToImport
;
224 public String
getName() {
225 return QuickFixBundle
.message("import.class.fix");
229 public String
getFamilyName() {
230 return QuickFixBundle
.message("import.class.fix");
233 public void applyFix(@NotNull final Project project
, @NotNull final ProblemDescriptor descriptor
) {
234 final PsiElement element
= descriptor
.getPsiElement();
235 if (element
instanceof PsiJavaCodeReferenceElement
) {
236 final PsiJavaCodeReferenceElement referenceElement
= (PsiJavaCodeReferenceElement
)element
;
237 Collections
.sort(myClassesToImport
, new PsiProximityComparator(referenceElement
.getElement()));
238 final JList list
= new JList(myClassesToImport
.toArray(new PsiClass
[myClassesToImport
.size()]));
239 list
.setCellRenderer(new FQNameCellRenderer());
240 Runnable runnable
= new Runnable() {
242 if (!element
.isValid()) return;
243 final int index
= list
.getSelectedIndex();
244 if (index
< 0) return;
245 new WriteCommandAction(project
, element
.getContainingFile()){
246 protected void run(final Result result
) throws Throwable
{
247 final PsiClass psiClass
= myClassesToImport
.get(index
);
248 if (psiClass
.isValid()) {
249 PsiDocumentManager
.getInstance(project
).commitAllDocuments();
250 referenceElement
.bindToElement(psiClass
);
256 final Editor editor
= PlatformDataKeys
.EDITOR
.getData(DataManager
.getInstance().getDataContext());
257 assert editor
!= null; //available for on the fly mode only
258 new PopupChooserBuilder(list
).
259 setTitle(QuickFixBundle
.message("class.to.import.chooser.title")).
260 setItemChoosenCallback(runnable
).
262 showInBestPositionFor(editor
);
267 private static class RenameReferenceQuickFix
implements LocalQuickFix
{
268 private final Set
<String
> myUnboundParams
;
270 public RenameReferenceQuickFix(Set
<String
> unboundParams
) {
271 myUnboundParams
= unboundParams
;
275 public String
getName() {
276 return "Change to ...";
280 public String
getFamilyName() {
284 public void applyFix(@NotNull Project project
, @NotNull ProblemDescriptor descriptor
) {
285 final Editor editor
= PlatformDataKeys
.EDITOR
.getData(DataManager
.getInstance().getDataContext());
286 assert editor
!= null;
287 final TextRange textRange
= ((ProblemDescriptorImpl
)descriptor
).getTextRange();
288 editor
.getSelectionModel().setSelection(textRange
.getStartOffset(), textRange
.getEndOffset());
290 final String word
= editor
.getSelectionModel().getSelectedText();
292 if (word
== null || StringUtil
.isEmptyOrSpaces(word
)) {
295 final List
<LookupElement
> items
= new ArrayList
<LookupElement
>();
296 for (String variant
: myUnboundParams
) {
297 items
.add(LookupElementBuilder
.create(variant
));
299 LookupManager
.getInstance(project
).showLookup(editor
, items
.toArray(new LookupElement
[items
.size()]));
303 private static class RemoveTagFix
implements LocalQuickFix
{
304 private final String myTagName
;
305 private final CharSequence myParamName
;
306 private final PsiDocTag myTag
;
308 public RemoveTagFix(String tagName
, CharSequence paramName
, PsiDocTag tag
) {
310 myParamName
= paramName
;
315 public String
getName() {
316 return "Remove @" + myTagName
+ " " + myParamName
;
320 public String
getFamilyName() {
324 public void applyFix(@NotNull Project project
, @NotNull ProblemDescriptor descriptor
) {