blob: 554deb5fb31a22deaaaff77dfbca4bee6613952f [file] [log] [blame]
/*
* Copyright 2009-2011 Bas Leijdekkers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ig.javadoc;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.IncorrectOperationException;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.InspectionGadgetsFix;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public class UnnecessaryJavaDocLinkInspection extends BaseInspection {
private static final int THIS_METHOD = 1;
private static final int THIS_CLASS = 2;
private static final int SUPER_METHOD = 3;
@SuppressWarnings({"PublicField"})
public boolean ignoreInlineLinkToSuper = false;
@Nls
@NotNull
@Override
public String getDisplayName() {
return InspectionGadgetsBundle.message(
"unnecessary.javadoc.link.display.name");
}
@NotNull
@Override
protected String buildErrorString(Object... infos) {
final int n = ((Integer)infos[1]).intValue();
if (n == THIS_METHOD) {
return InspectionGadgetsBundle.message(
"unnecessary.javadoc.link.this.method.problem.descriptor");
}
if (n == THIS_CLASS) {
return InspectionGadgetsBundle.message(
"unnecessary.javadoc.link.this.class.problem.descriptor");
}
return InspectionGadgetsBundle.message(
"unnecessary.javadoc.link.super.method.problem.descriptor");
}
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel(
InspectionGadgetsBundle.message(
"unnecessary.javadoc.link.option"),
this, "ignoreInlineLinkToSuper");
}
@Override
protected InspectionGadgetsFix buildFix(Object... infos) {
return new UnnecessaryJavaDocLinkFix((String)infos[0]);
}
private static class UnnecessaryJavaDocLinkFix
extends InspectionGadgetsFix {
private final String tagName;
public UnnecessaryJavaDocLinkFix(String tagName) {
this.tagName = tagName;
}
@Override
@NotNull
public String getName() {
return InspectionGadgetsBundle.message(
"unnecessary.javadoc.link.quickfix", tagName);
}
@NotNull
@Override
public String getFamilyName() {
return "Remove redundant tag";
}
@Override
protected void doFix(Project project, ProblemDescriptor descriptor)
throws IncorrectOperationException {
final PsiElement element = descriptor.getPsiElement();
final PsiElement parent = element.getParent();
if (!(parent instanceof PsiDocTag)) {
return;
}
final PsiDocTag docTag = (PsiDocTag)parent;
final PsiDocComment docComment = docTag.getContainingComment();
if (docComment != null) {
if (shouldDeleteEntireComment(docComment)) {
docComment.delete();
return;
}
}
docTag.delete();
}
private static boolean shouldDeleteEntireComment(
PsiDocComment docComment) {
final PsiDocToken[] docTokens = PsiTreeUtil.getChildrenOfType(
docComment, PsiDocToken.class);
if (docTokens == null) {
return false;
}
for (PsiDocToken docToken : docTokens) {
final IElementType tokenType = docToken.getTokenType();
if (!JavaDocTokenType.DOC_COMMENT_DATA.equals(tokenType)) {
continue;
}
if (!StringUtil.isEmptyOrSpaces(docToken.getText())) {
return false;
}
}
return true;
}
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new UnnecessaryJavaDocLinkVisitor();
}
private class UnnecessaryJavaDocLinkVisitor
extends BaseInspectionVisitor {
@Override
public void visitDocTag(PsiDocTag tag) {
super.visitDocTag(tag);
@NonNls final String name = tag.getName();
if ("link".equals(name) || "linkplain".equals(name)) {
if (!(tag instanceof PsiInlineDocTag)) {
return;
}
}
else if ("see".equals(name)) {
if (tag instanceof PsiInlineDocTag) {
return;
}
}
final PsiReference reference = extractReference(tag);
if (reference == null) {
return;
}
final PsiElement target = reference.resolve();
if (target == null) {
return;
}
final PsiMethod containingMethod =
PsiTreeUtil.getParentOfType(tag, PsiMethod.class);
if (containingMethod == null) {
return;
}
if (target.equals(containingMethod)) {
registerError(tag.getNameElement(), '@' + name,
Integer.valueOf(THIS_METHOD));
return;
}
final PsiClass containingClass =
PsiTreeUtil.getParentOfType(tag, PsiClass.class);
if (target.equals(containingClass)) {
registerError(tag.getNameElement(), '@' + name,
Integer.valueOf(THIS_CLASS));
return;
}
if (!(target instanceof PsiMethod)) {
return;
}
final PsiMethod method = (PsiMethod)target;
if (!isSuperMethod(method, containingMethod)) {
return;
}
if (ignoreInlineLinkToSuper && tag instanceof PsiInlineDocTag) {
return;
}
registerError(tag.getNameElement(), '@' + name,
Integer.valueOf(SUPER_METHOD));
}
private PsiReference extractReference(PsiDocTag tag) {
final PsiDocTagValue valueElement = tag.getValueElement();
if (valueElement != null) {
return valueElement.getReference();
}
// hack around the fact that a reference to a class is apparently
// not a PsiDocTagValue
final PsiElement[] dataElements = tag.getDataElements();
if (dataElements.length == 0) {
return null;
}
PsiElement salientElement = null;
for (PsiElement dataElement : dataElements) {
if (!(dataElement instanceof PsiWhiteSpace)) {
salientElement = dataElement;
break;
}
}
if (salientElement == null) {
return null;
}
final PsiElement child = salientElement.getFirstChild();
if (!(child instanceof PsiReference)) {
return null;
}
return (PsiReference)child;
}
public boolean isSuperMethod(PsiMethod superMethodCandidate,
PsiMethod derivedMethod) {
final PsiClass superClassCandidate =
superMethodCandidate.getContainingClass();
final PsiClass derivedClass = derivedMethod.getContainingClass();
if (derivedClass == null || superClassCandidate == null) {
return false;
}
if (!derivedClass.isInheritor(superClassCandidate, false)) {
return false;
}
final PsiSubstitutor superSubstitutor =
TypeConversionUtil.getSuperClassSubstitutor(
superClassCandidate, derivedClass,
PsiSubstitutor.EMPTY);
final MethodSignature superSignature =
superMethodCandidate.getSignature(superSubstitutor);
final MethodSignature derivedSignature =
derivedMethod.getSignature(PsiSubstitutor.EMPTY);
return MethodSignatureUtil.isSubsignature(superSignature,
derivedSignature);
}
}
}