javadoc unresolved reference: suggest to remove/rename tag (IDEA-52118)
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInspection / javaDoc / JavaDocReferenceInspection.java
blob9bf82b401d381dee8281977b4d5ec69284e9b877
1 /*
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;
45 import javax.swing.*;
46 import java.util.*;
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,
54 boolean onTheFly) {
55 return manager.createProblemDescriptor(element, template, onTheFly, (LocalQuickFix [])null, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
58 @Nullable
59 public ProblemDescriptor[] checkMethod(@NotNull PsiMethod psiMethod, @NotNull InspectionManager manager, boolean isOnTheFly) {
60 return checkMember(psiMethod, manager, isOnTheFly);
63 @Nullable
64 public ProblemDescriptor[] checkField(@NotNull PsiField field, @NotNull InspectionManager manager, boolean isOnTheFly) {
65 return checkMember(field, manager, isOnTheFly);
68 @Nullable
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,
80 isOnTheFly));
83 return problems.isEmpty()
84 ? null
85 : problems.toArray(new ProblemDescriptor[problems.size()]);
88 @Nullable
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)) {
131 child.accept(this);
138 public static void visitRefInDocTag(final PsiDocTag tag, final JavadocManager manager, final PsiElement context, ArrayList<ProblemDescriptor> problems,
139 InspectionManager inspectionManager,
140 boolean onTheFly) {
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);
196 @NotNull
197 public String getDisplayName() {
198 return DISPLAY_NAME;
201 @NotNull
202 public String getGroupDisplayName() {
203 return "";
206 @NotNull
207 public String getShortName() {
208 return SHORT_NAME;
211 @NotNull
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;
223 @NotNull
224 public String getName() {
225 return QuickFixBundle.message("import.class.fix");
228 @NotNull
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() {
241 public void run() {
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);
253 }.execute();
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).
261 createPopup().
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;
274 @NotNull
275 public String getName() {
276 return "Change to ...";
279 @NotNull
280 public String getFamilyName() {
281 return getName();
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)) {
293 return;
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) {
309 myTagName = tagName;
310 myParamName = paramName;
311 myTag = tag;
314 @NotNull
315 public String getName() {
316 return "Remove @" + myTagName + " " + myParamName;
319 @NotNull
320 public String getFamilyName() {
321 return getName();
324 public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
325 myTag.delete();