[javadoc inspection]: copy javadoc declaration analysis from "JavaDoc" inspection

GitOrigin-RevId: 0d86d36cca1aee87e99acc3070f8bbeb6c3cee83
This commit is contained in:
Alexandr Suhinin
2022-02-07 09:48:41 +00:00
committed by intellij-monorepo-bot
parent dee5d62919
commit b8ce6d3e04
3 changed files with 591 additions and 2 deletions
@@ -1,7 +1,9 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.javaDoc;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.impl.AddJavadocIntention;
import com.intellij.codeInspection.CommonQuickFixBundle;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.codeInspection.ProblemDescriptor;
@@ -9,10 +11,12 @@ import com.intellij.java.JavaBundle;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.pom.Navigatable;
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.javadoc.PsiInlineDocTag;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nls;
@@ -21,6 +25,9 @@ import org.jetbrains.annotations.Nullable;
public class JavaDocFixes {
private JavaDocFixes(){
}
public static class AddJavadocFix extends LocalQuickFixAndIntentionActionOnPsiElement {
private final AddJavadocIntention myIntention;
@@ -174,4 +181,149 @@ public class JavaDocFixes {
return JavaBundle.message("inspection.javadoc.problem.add.param.tag", myName);
}
}
public static class AddUnknownTagToCustoms implements LocalQuickFix {
private final JavadocDeclarationInspection myInspection;
private final String myTag;
AddUnknownTagToCustoms(@NotNull JavadocDeclarationInspection inspection, @NotNull String tag) {
myInspection = inspection;
myTag = tag;
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
myInspection.registerAdditionalTag(myTag);
ProjectInspectionProfileManager.getInstance(project).fireProfileChanged();
}
@Override
@NotNull
public String getName() {
return QuickFixBundle.message("add.docTag.to.custom.tags", myTag);
}
@Override
public boolean startInWriteAction() {
return false;
}
@Override
@NotNull
public String getFamilyName() {
//noinspection DialogTitleCapitalization
return QuickFixBundle.message("fix.javadoc.family");
}
}
public static class RemoveTagFix implements LocalQuickFix {
private final String myTagName;
RemoveTagFix(String tagName) {
myTagName = tagName;
}
@NotNull
@Override
public String getName() {
return JavaBundle.message("quickfix.text.remove.javadoc.0", myTagName);
}
@NotNull
@Override
public String getFamilyName() {
return JavaBundle.message("quickfix.family.remove.javadoc.tag");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiDocTag tag = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiDocTag.class);
if (tag != null) {
tag.delete();
}
}
}
private static abstract class AbstractUnknownTagFix implements LocalQuickFix {
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
if (element == null) return;
final PsiElement enclosingTag = element.getParent();
if (enclosingTag == null) return;
final PsiElement javadoc = enclosingTag.getParent();
if (javadoc == null) return;
final PsiDocComment donorJavadoc = createDonorJavadoc(element);
final PsiElement codeTag = extractElement(donorJavadoc);
if (codeTag == null) return;
for (var e = enclosingTag.getFirstChild(); e != element && e != null; e = e.getNextSibling()) {
javadoc.addBefore(e, enclosingTag);
}
javadoc.addBefore(codeTag, enclosingTag);
for (var e = element.getNextSibling(); e != null; e = e.getNextSibling()) {
javadoc.addBefore(e, enclosingTag);
}
final PsiElement sibling = enclosingTag.getNextSibling();
if (sibling != null && sibling.getNode().getElementType() == TokenType.WHITE_SPACE) {
javadoc.addBefore(sibling, enclosingTag);
}
enclosingTag.delete();
}
protected abstract @NotNull PsiDocComment createDonorJavadoc(@NotNull PsiElement element);
protected abstract @Nullable PsiElement extractElement(@Nullable PsiDocComment donorJavadoc);
}
public static class EncloseWithCodeFix extends AbstractUnknownTagFix {
private final String myName;
public EncloseWithCodeFix(String name) {
myName = name;
}
@Override
public @NotNull String getFamilyName() {
return CommonQuickFixBundle.message("fix.replace.x.with.y", myName, "{@code " + myName + "}");
}
@Override
protected @NotNull PsiDocComment createDonorJavadoc(@NotNull PsiElement element) {
final PsiElementFactory instance = PsiElementFactory.getInstance(element.getProject());
return instance.createDocCommentFromText(String.format("/** {@code %s} */", element.getText()));
}
@Override
protected @Nullable PsiElement extractElement(@Nullable PsiDocComment donorJavadoc) {
return PsiTreeUtil.findChildOfType(donorJavadoc, PsiInlineDocTag.class);
}
}
public static class EscapeAtQuickFix extends AbstractUnknownTagFix {
private final String myName;
public EscapeAtQuickFix(String name) {
myName = name;
}
@Override
public @NotNull String getFamilyName() {
return CommonQuickFixBundle.message("fix.replace.x.with.y", myName, "@" + myName.substring(1));
}
@Override
protected @NotNull PsiDocComment createDonorJavadoc(@NotNull PsiElement element) {
final PsiElementFactory instance = PsiElementFactory.getInstance(element.getProject());
return instance.createDocCommentFromText("/** @" + element.getText().substring(1) + " */");
}
@Override
protected @Nullable PsiElement extractElement(@Nullable PsiDocComment donorJavadoc) {
if (donorJavadoc == null) return null;
return donorJavadoc.getChildren()[2];
}
}
}
@@ -1,11 +1,34 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.javaDoc;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInsight.daemon.impl.analysis.IncreaseLanguageLevelFix;
import com.intellij.codeInspection.*;
import com.intellij.java.JavaBundle;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.javadoc.*;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JavadocDeclarationInspection extends LocalInspectionTool {
public String additionalJavadocTags = "";
@@ -13,9 +36,423 @@ public class JavadocDeclarationInspection extends LocalInspectionTool {
public boolean ignoreJavaDocPeriod = true;
public boolean ignorePointToItself = false;
private static final String[] TAGS_TO_CHECK = {"author", "version", "since"};
private static final Set<String> UNIQUE_TAGS = ContainerUtil.newHashSet("return", "deprecated", "serial", "serialData");
public void registerAdditionalTag(@NotNull String tag) {
if (!additionalJavadocTags.isEmpty()) {
additionalJavadocTags += "," + tag;
}
else {
additionalJavadocTags = tag;
}
}
@Override
public @Nullable JComponent createOptionsPanel() {
return JavadocUIUtil.INSTANCE.javadocDeclarationOptions(this);
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitJavaFile(PsiJavaFile file) {
if (PsiPackage.PACKAGE_INFO_FILE.equals(file.getName())) {
checkFile(file, holder);
}
}
@Override
public void visitModule(PsiJavaModule module) {
checkModule(module, holder);
}
@Override
public void visitClass(PsiClass aClass) {
checkClass(aClass, holder);
}
@Override
public void visitField(PsiField field) {
checkField(field, holder);
}
@Override
public void visitMethod(PsiMethod method) {
checkMethod(method, holder);
}
};
}
private void checkFile(PsiJavaFile file, ProblemsHolder holder) {
PsiPackage pkg = JavaDirectoryService.getInstance().getPackage(file.getContainingDirectory());
if (pkg == null) return;
PsiDocComment docComment = PsiTreeUtil.getChildOfType(file, PsiDocComment.class);
if (docComment != null) {
PsiDocTag[] tags = docComment.getTags();
checkBasics(docComment, tags, pkg, holder);
}
}
private void checkModule(PsiJavaModule module, ProblemsHolder holder) {
PsiDocComment docComment = module.getDocComment();
if (docComment != null) {
checkBasics(docComment, docComment.getTags(), module, holder);
}
}
private void checkClass(PsiClass aClass, ProblemsHolder holder) {
if (aClass instanceof PsiAnonymousClass || aClass instanceof PsiSyntheticClass || aClass instanceof PsiTypeParameter) {
return;
}
PsiDocComment docComment = aClass.getDocComment();
if (docComment != null) {
PsiDocTag[] tags = docComment.getTags();
checkBasics(docComment, tags, aClass, holder);
}
}
private void checkField(PsiField field, ProblemsHolder holder) {
PsiDocComment docComment = field.getDocComment();
if (docComment != null) {
checkBasics(docComment, docComment.getTags(), field, holder);
}
}
private void checkMethod(PsiMethod method, ProblemsHolder holder) {
if (method instanceof SyntheticElement) {
return;
}
PsiDocComment docComment = method.getDocComment();
if (docComment != null) {
if (!MissingJavadocInspection.isInherited(docComment, method)) {
PsiDocTag[] tags = docComment.getTags();
checkEmptyMethodTagsDescription(tags, method, holder);
checkBasics(docComment, tags, method, holder);
}
}
}
private void checkBasics(PsiDocComment docComment, PsiDocTag[] tags, PsiElement context, ProblemsHolder holder) {
checkRequiredTagDescriptions(tags, holder);
checkTagValues(tags, context, holder);
if (!ignoreJavaDocPeriod) {
checkForPeriod(docComment, context, holder);
}
checkInlineTags(docComment.getDescriptionElements(), holder);
checkForBadCharacters(docComment, holder);
checkDuplicateTags(tags, holder);
}
private static void checkEmptyMethodTagsDescription(PsiDocTag @NotNull [] tags,
@NotNull PsiMethod psiMethod,
@NotNull ProblemsHolder holder) {
for (PsiDocTag tag : tags) {
if (ContainerUtil
.exists(tag.getChildren(), e -> e instanceof PsiInlineDocTag && ((PsiInlineDocTag)e).getName().equals("inheritDoc"))) {
continue;
}
if ("return".equals(tag.getName())) {
if (!PsiType.VOID.equals(psiMethod.getReturnType()) && isEmptyTag(tag)) {
String tagText = "<code>@return</code>";
LocalQuickFix fix = new JavaDocFixes.RemoveTagFix("return");
holder.registerProblem(tag.getNameElement(), JavaBundle.message("inspection.javadoc.method.problem.missing.tag.description", tagText), fix);
}
}
else if ("throws".equals(tag.getName()) || "exception".equals(tag.getName())) {
if (isEmptyThrowsTag(tag)) {
String tagText = "<code>@" + tag.getName() + "</code>";
LocalQuickFix fix = new JavaDocFixes.RemoveTagFix(tag.getName());
holder.registerProblem(tag.getNameElement(), JavaBundle.message("inspection.javadoc.method.problem.missing.tag.description", tagText), fix);
}
}
else if ("param".equals(tag.getName())) {
PsiDocTagValue valueElement = tag.getValueElement();
if (valueElement != null && isEmptyParamTag(tag, valueElement)) {
String tagText = "<code>@param " + valueElement.getText() + "</code>";
LocalQuickFix fix = new JavaDocFixes.RemoveTagFix("param " + valueElement.getText());
holder.registerProblem(valueElement, JavaBundle.message("inspection.javadoc.method.problem.missing.tag.description", tagText), fix);
}
}
}
}
private static void checkRequiredTagDescriptions(PsiDocTag @NotNull [] tags, @NotNull ProblemsHolder holder) {
for (PsiDocTag tag : tags) {
String tagName = tag.getName();
if (ArrayUtil.find(TAGS_TO_CHECK, tagName) >= 0 && isEmptyTag(tag)) {
String message = JavaBundle.message("inspection.javadoc.problem.missing.tag.description", StringUtil.capitalize(tagName), tagName);
holder.registerProblem(tag.getNameElement(), message);
}
}
}
private static void checkForBadCharacters(@NotNull PsiDocComment docComment, @NotNull ProblemsHolder holder) {
docComment.accept(new PsiRecursiveElementVisitor() {
@Override
public void visitElement(@NotNull PsiElement element) {
super.visitElement(element);
ASTNode node = element.getNode();
if (node != null && node.getElementType() == JavaDocTokenType.DOC_COMMENT_BAD_CHARACTER) {
holder.registerProblem(element, JavaBundle.message("inspection.illegal.character"));
}
}
});
}
private void checkDuplicateTags(PsiDocTag @NotNull [] tags, @NotNull ProblemsHolder holder) {
Set<String> documentedParamNames = null;
Set<String> documentedExceptions = null;
Set<String> uniqueTags = null;
for (PsiDocTag tag : tags) {
if ("param".equals(tag.getName())) {
PsiDocTagValue value = tag.getValueElement();
if (value instanceof PsiDocParamRef) {
PsiReference reference = value.getReference();
if (reference != null) {
String paramName = reference.getCanonicalText();
if(((PsiDocParamRef)value).isTypeParamRef()){
paramName = "<" + paramName + ">";
}
documentedParamNames = set(documentedParamNames);
if (documentedParamNames.contains(paramName)) {
holder.registerProblem(tag.getNameElement(), JavaBundle.message("inspection.javadoc.problem.duplicate.param", paramName));
}
documentedParamNames.add(paramName);
}
}
}
else if (!ignoreDuplicatedThrows && ("throws".equals(tag.getName()) || "exception".equals(tag.getName()))) {
PsiDocTagValue value = tag.getValueElement();
if (value != null) {
PsiElement firstChild = value.getFirstChild();
if (firstChild != null && firstChild.getFirstChild() instanceof PsiJavaCodeReferenceElement) {
PsiJavaCodeReferenceElement refElement = (PsiJavaCodeReferenceElement)firstChild.getFirstChild();
PsiElement element = refElement.resolve();
if (element instanceof PsiClass) {
String fqName = ((PsiClass)element).getQualifiedName();
documentedExceptions = set(documentedExceptions);
if (documentedExceptions.contains(fqName)) {
holder.registerProblem(tag.getNameElement(), JavaBundle.message("inspection.javadoc.problem.duplicate.throws", fqName));
}
documentedExceptions.add(fqName);
}
}
}
}
else if (UNIQUE_TAGS.contains(tag.getName())) {
uniqueTags = set(uniqueTags);
if (uniqueTags.contains(tag.getName())) {
holder.registerProblem(tag.getNameElement(), JavaBundle.message("inspection.javadoc.problem.duplicate.tag", tag.getName()));
}
uniqueTags.add(tag.getName());
}
}
}
private static void checkForPeriod(@NotNull PsiDocComment docComment, @Nullable PsiElement context, @NotNull ProblemsHolder holder) {
int dotIndex = docComment.getText().indexOf('.'), tagOffset = 0;
if (dotIndex >= 0) { // need to find first valid tag
for (PsiDocTag tag : docComment.getTags()) {
String tagName = tag.getName();
JavadocTagInfo tagInfo = JavadocManager.SERVICE.getInstance(tag.getProject()).getTagInfo(tagName);
if (tagInfo != null && tagInfo.isValidInContext(context) && !tagInfo.isInline()) {
tagOffset = tag.getTextOffset();
break;
}
}
}
if (dotIndex == -1 || tagOffset > 0 && dotIndex + docComment.getTextOffset() > tagOffset) {
holder.registerProblem(docComment.getFirstChild(), JavaBundle.message("inspection.javadoc.problem.descriptor1"));
}
}
private void checkInlineTags(PsiElement @NotNull [] elements, @NotNull ProblemsHolder holder) {
JavadocManager docManager = JavadocManager.SERVICE.getInstance(holder.getProject());
for (PsiElement element : elements) {
if (element instanceof PsiInlineDocTag) {
PsiInlineDocTag tag = (PsiInlineDocTag)element;
String tagName = tag.getName();
if (docManager.getTagInfo(tagName) == null) {
checkTagInfo(tag, tagName, null, holder);
}
checkPointToSelf(holder, tag);
checkSnippetTag(holder, element, tag);
}
}
}
private void checkTagValues(PsiDocTag @NotNull [] tags, @Nullable PsiElement context, @NotNull ProblemsHolder holder) {
JavadocManager docManager = JavadocManager.SERVICE.getInstance(holder.getProject());
for (PsiDocTag tag : tags) {
String tagName = tag.getName();
JavadocTagInfo tagInfo = docManager.getTagInfo(tagName);
if (tagInfo == null || !tagInfo.isValidInContext(context)) {
if (checkTagInfo(tag, tagName, tagInfo, holder)) continue;
}
PsiDocTagValue value = tag.getValueElement();
if (tagInfo != null && !tagInfo.isValidInContext(context)) continue;
String message = tagInfo == null ? null : tagInfo.checkTagValue(value);
PsiReference reference = value != null ? value.getReference() : null;
if (message == null && reference != null) {
PsiElement element = reference.resolve();
if (element == null) {
int textOffset = value.getTextOffset();
if (textOffset == value.getTextRange().getEndOffset()) {
ProblemDescriptor problem = holder.getManager().createProblemDescriptor(
tag, JavaBundle.message("inspection.javadoc.problem.name.expected"), null,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, holder.isOnTheFly(), true);
holder.registerProblem(problem);
}
}
}
if (message != null) {
PsiElement toHighlight = ObjectUtils.notNull(tag.getValueElement(), tag.getNameElement());
holder.registerProblem(toHighlight, message);
}
PsiElement[] dataElements = tag.getDataElements();
if ("see".equals(tagName)) {
if (dataElements.length == 0 || dataElements.length == 1 && isEmpty(dataElements[0])) {
holder.registerProblem(tag.getNameElement(), JavaBundle.message("inspection.javadoc.problem.see.tag.expecting.ref"));
}
else if (!isValidSeeRef(dataElements)) {
holder.registerProblem(dataElements[0], JavaBundle.message("inspection.javadoc.problem.see.tag.expecting.ref"));
}
}
checkInlineTags(dataElements, holder);
}
}
private boolean checkTagInfo(PsiDocTag tag, String tagName, JavadocTagInfo tagInfo, ProblemsHolder holder) {
StringTokenizer tokenizer = new StringTokenizer(additionalJavadocTags, ", ");
while (tokenizer.hasMoreTokens()) {
if (Comparing.strEqual(tagName, tokenizer.nextToken())) return true;
}
PsiElement nameElement = tag.getNameElement();
if (nameElement != null) {
String key = tagInfo == null ? "inspection.javadoc.problem.wrong.tag" : "inspection.javadoc.problem.disallowed.tag";
LocalQuickFix fix = tagInfo == null ? new JavaDocFixes.AddUnknownTagToCustoms(this, tagName) : new JavaDocFixes.RemoveTagFix(tagName);
final LocalQuickFix[] fixes;
if (tagInfo != null) {
fixes = new LocalQuickFix[]{ fix };
}
else {
final String nameElementText = nameElement.getText();
fixes = new LocalQuickFix[]{ fix, new JavaDocFixes.EncloseWithCodeFix(nameElementText), new JavaDocFixes.EscapeAtQuickFix(nameElementText) };
}
holder.registerProblem(nameElement, JavaBundle.message(key, "<code>" + tagName + "</code>"), fixes);
}
return false;
}
private void checkPointToSelf(@NotNull ProblemsHolder holder, PsiInlineDocTag tag) {
if (ignorePointToItself) {
return;
}
PsiDocTagValue value = tag.getValueElement();
if (value == null) {
return;
}
PsiReference reference = value.getReference();
if (reference == null) {
return;
}
PsiElement target = reference.resolve();
if (target == null) {
return;
}
if (PsiTreeUtil.getParentOfType(tag, PsiDocCommentOwner.class) !=
PsiTreeUtil.getParentOfType(target, PsiDocCommentOwner.class, false)) {
return;
}
PsiElement nameElement = tag.getNameElement();
if (nameElement == null) {
return;
}
holder.registerProblem(nameElement, JavaBundle.message("inspection.javadoc.problem.pointing.to.itself"));
}
private static final TokenSet SEE_TAG_REFS = TokenSet.create(JavaDocElementType.DOC_REFERENCE_HOLDER, JavaDocElementType.DOC_METHOD_OR_FIELD_REF);
private static boolean isValidSeeRef(PsiElement... elements) {
if (SEE_TAG_REFS.contains(elements[0].getNode().getElementType())) return true;
String text = Stream.of(elements).map(e -> e.getText().trim()).collect(Collectors.joining(" ")).trim();
if (StringUtil.isQuotedString(text) && text.charAt(0) == '"') return true;
if (StringUtil.toLowerCase(text).matches("^<a\\s+href=.+")) return true;
return false;
}
private static void checkSnippetTag(@NotNull ProblemsHolder holder, PsiElement element, PsiInlineDocTag tag) {
if (element instanceof PsiSnippetDocTag) {
if (!PsiUtil.getLanguageLevel(element).isAtLeast(LanguageLevel.JDK_18)) {
PsiElement nameElement = tag.getNameElement();
if (nameElement != null) {
String message = JavaBundle.message("inspection.javadoc.problem.snippet.tag.is.not.available");
holder.registerProblem(nameElement, message, new IncreaseLanguageLevelFix(LanguageLevel.JDK_18));
}
}
}
}
private static boolean isEmptyTag(PsiDocTag tag) {
return Stream.of(tag.getChildren())
.filter(e -> e instanceof PsiDocToken && ((PsiDocToken)e).getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA ||
e instanceof PsiDocTagValue ||
e instanceof PsiInlineDocTag)
.allMatch(JavadocDeclarationInspection::isEmpty);
}
private static boolean isEmptyThrowsTag(PsiDocTag tag) {
return Stream.of(tag.getChildren())
.filter(e -> e instanceof PsiDocToken && ((PsiDocToken)e).getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA)
.allMatch(JavadocDeclarationInspection::isEmpty);
}
private static boolean isEmptyParamTag(PsiDocTag tag, PsiDocTagValue valueElement) {
PsiElement[] dataElements = tag.getDataElements();
return dataElements.length < 2 || Stream.of(dataElements)
.filter(e -> e != valueElement)
.allMatch(JavadocDeclarationInspection::isEmpty);
}
private static boolean isEmpty(PsiElement e) {
return e.getText().chars().allMatch(c -> c <= ' ');
}
private static <T> Set<T> set(Set<T> set) {
return set != null ? set : new HashSet<>();
}
}
@@ -222,7 +222,7 @@ public class MissingJavadocInspection extends LocalInspectionTool {
return element.isDeprecated() || element.getContainingClass() != null && element.getContainingClass().isDeprecated();
}
private static boolean isInherited(PsiDocComment docComment, PsiMethod psiMethod) {
public static boolean isInherited(PsiDocComment docComment, PsiMethod psiMethod) {
for (PsiElement descriptionElement : docComment.getDescriptionElements()) {
if (descriptionElement instanceof PsiInlineDocTag && "inheritDoc".equals(((PsiInlineDocTag)descriptionElement).getName())) {
return true;