diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavaDocLocalInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavaDocLocalInspectionBase.java index a0a6875c2135..0c8a33278d3c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavaDocLocalInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavaDocLocalInspectionBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,66 +16,48 @@ package com.intellij.codeInspection.javaDoc; import com.intellij.ToolExtensionPoints; -import com.intellij.codeInsight.FileModificationService; -import com.intellij.codeInsight.daemon.QuickFixBundle; import com.intellij.codeInspection.*; -import com.intellij.codeInspection.reference.RefJavaUtil; -import com.intellij.lang.ASTNode; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.ExtensionPoint; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; -import com.intellij.pom.Navigatable; -import com.intellij.profile.codeInspection.InspectionProfileManager; -import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.*; -import com.intellij.psi.impl.source.javadoc.PsiDocParamRef; import com.intellij.psi.javadoc.*; -import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PropertyUtil; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import org.jdom.Element; -import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; public class JavaDocLocalInspectionBase extends BaseJavaBatchLocalInspectionTool { - private static final Logger LOG = Logger.getInstance("com.intellij.codeInspection.javaDoc.JavaDocLocalInspectionBase"); - @NonNls protected static final String NONE = "none"; - @NonNls protected static final String PUBLIC = "public"; - @NonNls protected static final String PROTECTED = "protected"; - @NonNls protected static final String PACKAGE_LOCAL = "package"; - @NonNls protected static final String PRIVATE = "private"; + public static final String SHORT_NAME = "JavaDoc"; - private static final String REQUIRED_JAVADOC_IS_ABSENT = InspectionsBundle.message("inspection.javadoc.problem.descriptor"); + protected static final String NONE = "none"; + protected static final String PACKAGE_LOCAL = "package"; + protected static final String PUBLIC = PsiModifier.PUBLIC; + protected static final String PROTECTED = PsiModifier.PROTECTED; + protected static final String PRIVATE = PsiModifier.PRIVATE; - @NonNls private static final Set ourUniqueTags = new HashSet(); - @NonNls public static final String SHORT_NAME = "JavaDoc"; - - static { - ourUniqueTags.add("return"); - ourUniqueTags.add("deprecated"); - ourUniqueTags.add("serial"); - ourUniqueTags.add("serialData"); - } - - @NonNls private static final String IGNORE_ACCESSORS_ATTR_NAME = "IGNORE_ACCESSORS"; - @NonNls private static final String IGNORE_DUPLICATED_THROWS_TAGS_ATTR_NAME = "IGNORE_DUPLICATED_THROWS_TAGS"; + private static final String IGNORE_ACCESSORS_ATTR_NAME = "IGNORE_ACCESSORS"; + private static final String IGNORE_DUPLICATED_THROWS_TAGS_ATTR_NAME = "IGNORE_DUPLICATED_THROWS_TAGS"; + @SuppressWarnings("deprecation") public static class Options implements JDOMExternalizable { - @NonNls public String ACCESS_JAVADOC_REQUIRED_FOR = NONE; - @NonNls public String REQUIRED_TAGS = ""; + public String ACCESS_JAVADOC_REQUIRED_FOR = NONE; + public String REQUIRED_TAGS = ""; public Options() {} - public Options(String ACCESS_JAVADOC_REQUIRED_FOR, String REQUIRED_TAGS) { - this.ACCESS_JAVADOC_REQUIRED_FOR = ACCESS_JAVADOC_REQUIRED_FOR; - this.REQUIRED_TAGS = REQUIRED_TAGS; + public Options(String accessJavadocRequiredFor, String requiredTags) { + ACCESS_JAVADOC_REQUIRED_FOR = accessJavadocRequiredFor; + REQUIRED_TAGS = requiredTags; } @Override @@ -89,19 +71,39 @@ public class JavaDocLocalInspectionBase extends BaseJavaBatchLocalInspectionTool } } - @NonNls protected final Options PACKAGE_OPTIONS = new Options("none", ""); - @NonNls public Options TOP_LEVEL_CLASS_OPTIONS = new Options("none", ""); - @NonNls public Options INNER_CLASS_OPTIONS = new Options("none", ""); - @NonNls public Options METHOD_OPTIONS = new Options("none", "@return@param@throws or @exception"); - @NonNls public Options FIELD_OPTIONS = new Options("none", ""); + protected final Options PACKAGE_OPTIONS = new Options("none", ""); + + public Options TOP_LEVEL_CLASS_OPTIONS = new Options("none", ""); + public Options INNER_CLASS_OPTIONS = new Options("none", ""); + public Options METHOD_OPTIONS = new Options("none", "@return@param@throws or @exception"); + public Options FIELD_OPTIONS = new Options("none", ""); public boolean IGNORE_DEPRECATED = false; public boolean IGNORE_JAVADOC_PERIOD = true; @SuppressWarnings("unused") @Deprecated public boolean IGNORE_DUPLICATED_THROWS = false; + public boolean IGNORE_POINT_TO_ITSELF = false; + + public String myAdditionalJavadocTags = ""; private boolean myIgnoreDuplicatedThrows = true; + private boolean myIgnoreEmptyDescriptions = false; + private boolean myIgnoreSimpleAccessors = false; - public boolean getIgnoreDuplicatedThrows() { + public void setPackageOption(String modifier, String tags) { + PACKAGE_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = modifier; + PACKAGE_OPTIONS.REQUIRED_TAGS = tags; + } + + public void registerAdditionalTag(@NotNull String tag) { + if (!myAdditionalJavadocTags.isEmpty()) { + myAdditionalJavadocTags += "," + tag; + } + else { + myAdditionalJavadocTags = tag; + } + } + + public boolean isIgnoreDuplicatedThrows() { return myIgnoreDuplicatedThrows; } @@ -109,33 +111,28 @@ public class JavaDocLocalInspectionBase extends BaseJavaBatchLocalInspectionTool myIgnoreDuplicatedThrows = ignoreDuplicatedThrows; } - public boolean IGNORE_POINT_TO_ITSELF = false; - public String myAdditionalJavadocTags = ""; + public void setIgnoreEmptyDescriptions(boolean ignoreEmptyDescriptions) { + myIgnoreEmptyDescriptions = ignoreEmptyDescriptions; + } - private boolean myIgnoreEmptyDescriptions = false; - protected boolean myIgnoreSimpleAccessors = false; - - @Override - public boolean isEnabledByDefault() { - return true; + public boolean isIgnoreSimpleAccessors() { + return myIgnoreSimpleAccessors; } public void setIgnoreSimpleAccessors(boolean ignoreSimpleAccessors) { myIgnoreSimpleAccessors = ignoreSimpleAccessors; } - public void setPackageOption(@NonNls String modifier, @NonNls String tags) { - PACKAGE_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = modifier; - PACKAGE_OPTIONS.REQUIRED_TAGS = tags; + @Override + public boolean isEnabledByDefault() { + return true; } @Override public void writeSettings(@NotNull Element node) throws WriteExternalException { super.writeSettings(node); if (myIgnoreSimpleAccessors) { - final Element option = new Element(IGNORE_ACCESSORS_ATTR_NAME); - option.setAttribute("value", String.valueOf(true)); - node.addContent(option); + node.addContent(new Element(IGNORE_ACCESSORS_ATTR_NAME).setAttribute("value", String.valueOf(true))); } if (!myIgnoreDuplicatedThrows) { node.addContent(new Element(IGNORE_DUPLICATED_THROWS_TAGS_ATTR_NAME).setAttribute("value", String.valueOf(false))); @@ -148,7 +145,7 @@ public class JavaDocLocalInspectionBase extends BaseJavaBatchLocalInspectionTool @Override public void readSettings(@NotNull Element node) throws InvalidDataException { super.readSettings(node); - final Element ignoreAccessorsTag = node.getChild(IGNORE_ACCESSORS_ATTR_NAME); + Element ignoreAccessorsTag = node.getChild(IGNORE_ACCESSORS_ATTR_NAME); if (ignoreAccessorsTag != null) { myIgnoreSimpleAccessors = Boolean.parseBoolean(ignoreAccessorsTag.getAttributeValue("value")); } @@ -159,793 +156,225 @@ public class JavaDocLocalInspectionBase extends BaseJavaBatchLocalInspectionTool PACKAGE_OPTIONS.readExternal(node); } - private static ProblemDescriptor createDescriptor(@NotNull PsiElement element, String template, InspectionManager manager, - boolean onTheFly) { - return manager.createProblemDescriptor(element, template, onTheFly, null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - } - - private static ProblemDescriptor createDescriptor(@NotNull PsiElement element, String template, @NotNull LocalQuickFix fix, - InspectionManager manager, boolean onTheFly) { - return manager.createProblemDescriptor(element, template, fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, onTheFly); - } - - private static class AddMissingTagFix implements LocalQuickFix { - private final String myTag; - private final String myValue; - - public AddMissingTagFix(@NonNls @NotNull String tag, @NotNull String value) { - myTag = tag; - myValue = value; - } - public AddMissingTagFix(@NotNull String tag) { - this(tag, ""); - } - - @Override - @NotNull - public String getName() { - return InspectionsBundle.message("inspection.javadoc.problem.add.tag", myTag, myValue); - } - - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); - try { - final PsiDocComment docComment = PsiTreeUtil.getParentOfType(descriptor.getEndElement(), PsiDocComment.class); - if (docComment != null) { - if (!FileModificationService.getInstance().preparePsiElementsForWrite(docComment)) return; - final PsiDocTag tag = factory.createDocTagFromText("@" + myTag + " " + myValue); - PsiElement addedTag; - final PsiElement anchor = getAnchor(descriptor); - if (anchor != null) { - addedTag = docComment.addBefore(tag, anchor); - } - else { - addedTag = docComment.add(tag); - } - moveCaretAfter(addedTag); - } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - @Nullable - protected PsiElement getAnchor(ProblemDescriptor descriptor) { - return null; - } - - private static void moveCaretAfter(final PsiElement newCaretPosition) { - PsiElement sibling = newCaretPosition.getNextSibling(); - if (sibling != null) { - ((Navigatable)sibling).navigate(true); - } - } - - @Override - @NotNull - public String getFamilyName() { - return InspectionsBundle.message("inspection.javadoc.problem.add.tag.family"); - } - } - @Nullable @Override public ProblemDescriptor[] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { if (!PsiPackage.PACKAGE_INFO_FILE.equals(file.getName()) || !(file instanceof PsiJavaFile)) { return null; } - final PsiDocComment docComment = PsiTreeUtil.getChildOfType(file, PsiDocComment.class); - final JavaDirectoryService directoryService = JavaDirectoryService.getInstance(); - final PsiDirectory directory = file.getContainingDirectory(); - final PsiPackage aPackage = directoryService.getPackage(directory); - if (IGNORE_DEPRECATED && aPackage != null) { - final PsiModifierList modifierList = aPackage.getModifierList(); - if (modifierList != null && modifierList.findAnnotation("java.lang.Deprecated") != null) { + + PsiDocComment docComment = PsiTreeUtil.getChildOfType(file, PsiDocComment.class); + PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(file.getContainingDirectory()); + boolean required = aPackage != null && JavadocHighlightUtil.isJavaDocRequired(this, aPackage); + ProblemHolderImpl holder = new ProblemHolderImpl(manager, isOnTheFly); + + if (IGNORE_DEPRECATED) { + if (aPackage != null) { + PsiModifierList modifierList = aPackage.getModifierList(); + if (modifierList != null && modifierList.findAnnotation("java.lang.Deprecated") != null) { + return null; + } + } + if (docComment != null && docComment.findTagByName("deprecated") != null) { return null; } } - final PsiJavaFile javaFile = (PsiJavaFile)file; - final PsiPackageStatement packageStatement = javaFile.getPackageStatement(); - final PsiElement elementToHighlight = packageStatement != null ? packageStatement : file; - final boolean required = aPackage != null && isJavaDocRequired(aPackage); - if (docComment != null) { - if (IGNORE_DEPRECATED && docComment.findTagByName("deprecated") != null) { - return null; + if (docComment == null) { + if (required) { + PsiElement toHighlight = ObjectUtils.notNull(((PsiJavaFile)file).getPackageStatement(), file); + JavadocHighlightUtil.reportMissingTag(toHighlight, holder); } } else { - return required - ? new ProblemDescriptor[]{createRequiredJavadocAbsentDescription(elementToHighlight, manager, isOnTheFly)} - : null; + PsiDocTag[] tags = docComment.getTags(); + + if (required) { + Predicate tagChecker = tag -> isTagRequired(aPackage, tag); + JavadocHighlightUtil.checkRequiredTags(tags, tagChecker, docComment.getFirstChild(), holder); + } + + JavadocHighlightUtil.checkRequiredTagDescriptions(tags, holder); + + JavadocHighlightUtil.checkTagValues(tags, aPackage, holder); + + JavadocHighlightUtil.checkInlineTags(docComment.getDescriptionElements(), holder); + + if (!IGNORE_JAVADOC_PERIOD) { + JavadocHighlightUtil.checkForPeriod(docComment, aPackage, holder); + } + + JavadocHighlightUtil.checkForBadCharacters(docComment, holder); } - final PsiDocTag[] tags = docComment.getTags(); - final List problems = - getRequiredTagProblems(aPackage, docComment.getFirstChild(), tags, manager, isOnTheFly, required); - final List tagProblems = getTagValuesProblems(aPackage, tags, manager, isOnTheFly); - if (tagProblems != null) { - problems.addAll(tagProblems); - } - checkInlineTags(manager, problems, docComment.getDescriptionElements(), - JavadocManager.SERVICE.getInstance(docComment.getProject()), isOnTheFly); - checkForPeriodInDoc(aPackage, docComment, problems, manager, isOnTheFly); - checkForBadCharacters(docComment, problems, manager, isOnTheFly); - return problems.isEmpty() - ? null - : problems.toArray(new ProblemDescriptor[problems.size()]); + return holder.problems(); } @Override @Nullable public ProblemDescriptor[] checkClass(@NotNull PsiClass psiClass, @NotNull InspectionManager manager, boolean isOnTheFly) { - if (psiClass instanceof PsiAnonymousClass) return null; - if (psiClass instanceof PsiSyntheticClass) return null; - if (psiClass instanceof PsiTypeParameter) return null; + if (psiClass instanceof PsiAnonymousClass || psiClass instanceof PsiSyntheticClass || psiClass instanceof PsiTypeParameter) { + return null; + } if (IGNORE_DEPRECATED && psiClass.isDeprecated()) { return null; } + PsiDocComment docComment = psiClass.getDocComment(); - final PsiIdentifier nameIdentifier = psiClass.getNameIdentifier(); - final PsiElement elementToHighlight = nameIdentifier != null ? nameIdentifier : psiClass; - final boolean required = isJavaDocRequired(psiClass); + boolean required = JavadocHighlightUtil.isJavaDocRequired(this, psiClass); + ProblemHolderImpl holder = new ProblemHolderImpl(manager, isOnTheFly); + if (docComment == null) { - return required - ? new ProblemDescriptor[]{createRequiredJavadocAbsentDescription(elementToHighlight, manager, isOnTheFly)} - : null; - } - - PsiDocTag[] tags = docComment.getTags(); - final List problems = getRequiredTagProblems(psiClass, docComment.getFirstChild(), tags, manager, isOnTheFly, required); - - List tagProblems = getTagValuesProblems(psiClass, tags, manager, isOnTheFly); - if (tagProblems != null) { - problems.addAll(tagProblems); - } - checkForPeriodInDoc(psiClass, docComment, problems, manager, isOnTheFly); - checkInlineTags(manager, problems, docComment.getDescriptionElements(), - JavadocManager.SERVICE.getInstance(docComment.getProject()), isOnTheFly); - checkForBadCharacters(docComment, problems, manager, isOnTheFly); - checkDuplicateTags(tags, problems, manager, isOnTheFly); - - if (required && isTagRequired(psiClass, "param") && psiClass.hasTypeParameters() && nameIdentifier != null) { - ArrayList absentParameters = null; - final PsiTypeParameter[] typeParameters = psiClass.getTypeParameters(); - for (PsiTypeParameter typeParameter : typeParameters) { - if (!isFound(tags, typeParameter)) { - if (absentParameters == null) absentParameters = new ArrayList(1); - absentParameters.add(typeParameter); - } + if (required) { + PsiElement toHighlight = ObjectUtils.notNull(psiClass.getNameIdentifier(), psiClass); + JavadocHighlightUtil.reportMissingTag(toHighlight, holder); } - if (absentParameters != null) { - for (PsiTypeParameter psiTypeParameter : absentParameters) { - problems.add(createMissingParamTagDescriptor(docComment.getFirstChild(), psiTypeParameter, manager, isOnTheFly)); - } + } + else { + PsiDocTag[] tags = docComment.getTags(); + + if (required) { + Predicate tagChecker = tag -> isTagRequired(psiClass, tag); + JavadocHighlightUtil.checkRequiredTags(tags, tagChecker, docComment.getFirstChild(), holder); + } + + JavadocHighlightUtil.checkRequiredTagDescriptions(tags, holder); + + JavadocHighlightUtil.checkTagValues(tags, psiClass, holder); + + if (!IGNORE_JAVADOC_PERIOD) { + JavadocHighlightUtil.checkForPeriod(docComment, psiClass, holder); + } + + JavadocHighlightUtil.checkInlineTags(docComment.getDescriptionElements(), holder); + + JavadocHighlightUtil.checkForBadCharacters(docComment, holder); + + JavadocHighlightUtil.checkDuplicateTags(tags, holder); + + if (required && isTagRequired(psiClass, "param")) { + JavadocHighlightUtil.checkMissingTypeParamTags(psiClass, tags, docComment.getFirstChild(), holder); } } - return problems.isEmpty() - ? null - : problems.toArray(new ProblemDescriptor[problems.size()]); - } - - private List getRequiredTagProblems(PsiElement context, - PsiElement elementToHighlight, - PsiDocTag[] tags, - InspectionManager manager, boolean isOnTheFly, boolean required) { - @NonNls String[] tagsToCheck = {"author", "version", "since"}; - @NonNls String[] absentDescriptionKeys = { - "inspection.javadoc.problem.missing.author.description", - "inspection.javadoc.problem.missing.version.description", - "inspection.javadoc.problem.missing.since.description"}; - final ArrayList problems = new ArrayList(2); - if (required) { - boolean[] isTagRequired = new boolean[tagsToCheck.length]; - boolean[] isTagPresent = new boolean[tagsToCheck.length]; - - boolean someTagsAreRequired = false; - for (int i = 0; i < tagsToCheck.length; i++) { - final String tag = tagsToCheck[i]; - someTagsAreRequired |= isTagRequired[i] = isTagRequired(context, tag); - } - - if (someTagsAreRequired) { - for (PsiDocTag tag : tags) { - String tagName = tag.getName(); - for (int i = 0; i < tagsToCheck.length; i++) { - final String tagToCheck = tagsToCheck[i]; - if (tagToCheck.equals(tagName)) { - isTagPresent[i] = true; - } - } - } - } - - for (int i = 0; i < tagsToCheck.length; i++) { - final String tagToCheck = tagsToCheck[i]; - if (isTagRequired[i] && !isTagPresent[i]) { - problems.add(createMissingTagDescriptor(elementToHighlight, tagToCheck, manager, isOnTheFly)); - } - } - } - for (PsiDocTag tag : tags) { - for (int i = 0; i < tagsToCheck.length; i++) { - final String tagToCheck = tagsToCheck[i]; - if (tagToCheck.equals(tag.getName()) && extractTagDescription(tag).isEmpty()) { - problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message(absentDescriptionKeys[i]), manager, isOnTheFly)); - } - } - } - return problems; - } - - private static ProblemDescriptor createMissingParamTagDescriptor(final PsiElement elementToHighlight, - final PsiTypeParameter psiTypeParameter, - final InspectionManager manager, boolean isOnTheFly) { - String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "@param"); - return createDescriptor(elementToHighlight, message, new AddMissingTagFix("param", "<" + psiTypeParameter.getName() + ">"), manager, - isOnTheFly); + return holder.problems(); } @Override @Nullable public ProblemDescriptor[] checkField(@NotNull PsiField psiField, @NotNull InspectionManager manager, boolean isOnTheFly) { - if (IGNORE_DEPRECATED && (psiField.isDeprecated() || psiField.getContainingClass().isDeprecated())) { + if (IGNORE_DEPRECATED && isDeprecated(psiField)) { return null; } PsiDocComment docComment = psiField.getDocComment(); + boolean required = JavadocHighlightUtil.isJavaDocRequired(this, psiField); + ProblemHolderImpl holder = new ProblemHolderImpl(manager, isOnTheFly); + if (docComment == null) { - final PsiIdentifier nameIdentifier = psiField.getNameIdentifier(); - return isJavaDocRequired(psiField) - ? new ProblemDescriptor[]{createRequiredJavadocAbsentDescription(nameIdentifier, manager, isOnTheFly)} - : null; + if (required) { + JavadocHighlightUtil.reportMissingTag(psiField.getNameIdentifier(), holder); + } + } + else { + JavadocHighlightUtil.checkTagValues(docComment.getTags(), psiField, holder); + + JavadocHighlightUtil.checkInlineTags(docComment.getDescriptionElements(), holder); + + if (!IGNORE_JAVADOC_PERIOD) { + JavadocHighlightUtil.checkForPeriod(docComment, psiField, holder); + } + + JavadocHighlightUtil.checkDuplicateTags(docComment.getTags(), holder); + + JavadocHighlightUtil.checkForBadCharacters(docComment, holder); } - final ArrayList problems = new ArrayList(2); - ArrayList tagProblems = getTagValuesProblems(psiField, docComment.getTags(), manager, isOnTheFly); - if (tagProblems != null) { - problems.addAll(tagProblems); - } - checkInlineTags(manager, problems, docComment.getDescriptionElements(), - JavadocManager.SERVICE.getInstance(docComment.getProject()), isOnTheFly); - checkForPeriodInDoc(psiField, docComment, problems, manager, isOnTheFly); - checkDuplicateTags(docComment.getTags(), problems, manager, isOnTheFly); - checkForBadCharacters(docComment, problems, manager, isOnTheFly); - return problems.isEmpty() - ? null - : problems.toArray(new ProblemDescriptor[problems.size()]); - } - - private ProblemDescriptor createRequiredJavadocAbsentDescription(@NotNull PsiElement nameIdentifier, - @NotNull InspectionManager manager, - boolean isOnTheFly) { - LocalQuickFix fix = createAddJavadocFix(nameIdentifier, isOnTheFly); - return fix != null ? - createDescriptor(nameIdentifier, REQUIRED_JAVADOC_IS_ABSENT, fix, manager, isOnTheFly): - createDescriptor(nameIdentifier, REQUIRED_JAVADOC_IS_ABSENT, manager, isOnTheFly); - } - - protected LocalQuickFix createAddJavadocFix(@NotNull PsiElement nameIdentifier, boolean isOnTheFly) { - return null; + return holder.problems(); } @Override @Nullable public ProblemDescriptor[] checkMethod(@NotNull PsiMethod psiMethod, @NotNull InspectionManager manager, boolean isOnTheFly) { - if (psiMethod instanceof SyntheticElement) return null; - if (IGNORE_DEPRECATED && (psiMethod.isDeprecated() || psiMethod.getContainingClass().isDeprecated())) { + if (psiMethod instanceof SyntheticElement) { + return null; + } + if (IGNORE_DEPRECATED && isDeprecated(psiMethod)) { return null; } if (myIgnoreSimpleAccessors && PropertyUtil.isSimplePropertyAccessor(psiMethod)) { return null; } + PsiDocComment docComment = psiMethod.getDocComment(); - final PsiMethod[] superMethods = psiMethod.findSuperMethods(); - final boolean required = isJavaDocRequired(psiMethod); + boolean hasSupers = psiMethod.findSuperMethods().length > 0; + boolean required = JavadocHighlightUtil.isJavaDocRequired(this, psiMethod); + ProblemHolderImpl holder = new ProblemHolderImpl(manager, isOnTheFly); + if (docComment == null) { - if (required) { - if (superMethods.length > 0) return null; - ExtensionPoint> point = Extensions.getRootArea().getExtensionPoint(ToolExtensionPoints.JAVADOC_LOCAL); - for (Condition addIn : point.getExtensions()) { - if (addIn.value(psiMethod)) return null; - } - if (superMethods.length == 0) { - final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier(); - return nameIdentifier != null ? new ProblemDescriptor[] { - createRequiredJavadocAbsentDescription(nameIdentifier, manager, isOnTheFly)} : null; - } - else { - return null; - } - } - else { + if (!required || hasSupers) { return null; } - } - final PsiElement[] descriptionElements = docComment.getDescriptionElements(); - for (PsiElement descriptionElement : descriptionElements) { - if (descriptionElement instanceof PsiInlineDocTag) { - if ("inheritDoc".equals(((PsiInlineDocTag)descriptionElement).getName())) return null; - } - } - - List problems = new ArrayList(2); - - checkInlineTags(manager, problems, descriptionElements, JavadocManager.SERVICE.getInstance(docComment.getProject()), isOnTheFly); - - final PsiDocTag tagByName = docComment.findTagByName("inheritDoc"); - if (tagByName != null) { - final String tagName = tagByName.getName(); - final JavadocTagInfo tagInfo = JavadocManager.SERVICE.getInstance(tagByName.getProject()).getTagInfo(tagName); - if (tagInfo != null && tagInfo.isValidInContext(psiMethod)){ + PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier(); + if (nameIdentifier == null) { return null; } + + ExtensionPoint> ep = Extensions.getRootArea().getExtensionPoint(ToolExtensionPoints.JAVADOC_LOCAL); + if (Stream.of(ep.getExtensions()).anyMatch(condition -> condition.value(psiMethod))) { + return null; + } + + JavadocHighlightUtil.reportMissingTag(nameIdentifier, holder); } + else { + PsiElement[] descriptionElements = docComment.getDescriptionElements(); + if (isInherited(docComment, descriptionElements, psiMethod)) { + return null; + } - PsiDocTag[] tags = docComment.getTags(); + JavadocHighlightUtil.checkInlineTags(descriptionElements, holder); - boolean isReturnRequired = false; - boolean isReturnAbsent = true; - if (superMethods.length == 0 && !psiMethod.isConstructor() && - !PsiType.VOID.equals(psiMethod.getReturnType()) && isTagRequired(psiMethod, "return")) { - isReturnRequired = true; - for (PsiDocTag tag : tags) { - if ("return".equals(tag.getName())) { - isReturnAbsent = false; - break; + PsiDocTag[] tags = docComment.getTags(); + if (required && !hasSupers) { + if (isTagRequired(psiMethod, "return")) { + JavadocHighlightUtil.checkMissingReturnTag(tags, psiMethod, docComment.getFirstChild(), holder); + } + if (isTagRequired(psiMethod, "param")) { + JavadocHighlightUtil.checkMissingParamTags(tags, psiMethod, docComment.getFirstChild(), holder); + } + if (isTagRequired(psiMethod, "throws")) { + JavadocHighlightUtil.checkMissingThrowsTags(tags, psiMethod, docComment.getFirstChild(), holder); } } - } - ArrayList absentParameters = null; - if (required && superMethods.length == 0 && isTagRequired(psiMethod, "param") ) { - PsiParameter[] params = psiMethod.getParameterList().getParameters(); - for (PsiParameter param : params) { - if (!isFound(tags, param)) { - if (absentParameters == null) absentParameters = new ArrayList(2); - absentParameters.add(param); - } + if (!myIgnoreEmptyDescriptions) { + JavadocHighlightUtil.checkEmptyMethodTagsDescription(tags, holder); } - } + JavadocHighlightUtil.checkTagValues(tags, psiMethod, holder); - - if (required && isReturnRequired && isReturnAbsent) { - problems.add(createMissingTagDescriptor(docComment.getFirstChild(), "return", manager, isOnTheFly)); - } - - if (absentParameters != null) { - for (PsiParameter psiParameter : absentParameters) { - problems.add(createMissingParamTagDescriptor(docComment.getFirstChild(), psiParameter, manager, isOnTheFly)); + if (!IGNORE_JAVADOC_PERIOD) { + JavadocHighlightUtil.checkForPeriod(docComment, psiMethod, holder); } + + JavadocHighlightUtil.checkForBadCharacters(docComment, holder); + + JavadocHighlightUtil.checkDuplicateTags(tags, holder); } - if (!myIgnoreEmptyDescriptions) { - for (PsiDocTag tag : tags) { - if ("param".equals(tag.getName())) { - final PsiElement[] dataElements = tag.getDataElements(); - final PsiDocTagValue valueElement = tag.getValueElement(); - boolean hasProblemsWithTag = dataElements.length < 2; - if (!hasProblemsWithTag) { - final StringBuilder buf = new StringBuilder(); - for (PsiElement element : dataElements) { - if (element != valueElement){ - buf.append(element.getText()); - } - } - hasProblemsWithTag = buf.toString().trim().isEmpty(); - } - if (hasProblemsWithTag) { - if (valueElement != null) { - problems.add(createDescriptor(valueElement, - InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "@param " + valueElement.getText() + ""), - manager, isOnTheFly)); - } - - } - } - } - } - - if (required && superMethods.length == 0 && isTagRequired(psiMethod, "@throws") && psiMethod.getThrowsList().getReferencedTypes().length > 0) { - final Map declaredExceptions = new LinkedHashMap(); - final PsiClassType[] classTypes = psiMethod.getThrowsList().getReferencedTypes(); - for (PsiClassType classType : classTypes) { - final PsiClass psiClass = classType.resolve(); - if (psiClass != null){ - declaredExceptions.put(classType, psiClass); - } - } - processThrowsTags(tags, declaredExceptions, manager, problems, isOnTheFly); - if (!declaredExceptions.isEmpty()) { - for (PsiClassType declaredException : declaredExceptions.keySet()) { - problems.add(createMissingThrowsTagDescriptor(docComment.getFirstChild(), manager, declaredException, isOnTheFly)); - } - } - } - - ArrayList tagProblems = getTagValuesProblems(psiMethod, tags, manager, isOnTheFly); - if (tagProblems != null) { - problems.addAll(tagProblems); - } - - checkForPeriodInDoc(psiMethod, docComment, problems, manager, isOnTheFly); - checkForBadCharacters(docComment, problems, manager, isOnTheFly); - for (PsiDocTag tag : tags) { - if ("param".equals(tag.getName())) { - if (extractTagDescription(tag).isEmpty()) { - PsiDocTagValue value = tag.getValueElement(); - if (value instanceof PsiDocParamRef) { - PsiDocParamRef paramRef = (PsiDocParamRef)value; - PsiParameter[] params = psiMethod.getParameterList().getParameters(); - for (PsiParameter param : params) { - if (paramRef.getReference().isReferenceTo(param)) { - problems.add(createDescriptor(value, - InspectionsBundle.message("inspection.javadoc.method.problem.descriptor", "@param", "" + param.getName() + ""), - manager, isOnTheFly)); - } - } - } - } - } - else - if ("return".equals(tag.getName()) && !myIgnoreEmptyDescriptions) { - if (extractTagDescription(tag).isEmpty()) { - String message = InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "@return"); - ProblemDescriptor descriptor = manager.createProblemDescriptor(tag.getNameElement(), message, (LocalQuickFix)null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - isOnTheFly); - problems.add(descriptor); - } - } - } - - checkDuplicateTags(tags, problems, manager, isOnTheFly); - - return problems.isEmpty() - ? null - : problems.toArray(new ProblemDescriptor[problems.size()]); + return holder.problems(); } - public static boolean isFound(final PsiDocTag[] tags, final PsiElement param) { - for (PsiDocTag tag : tags) { - if ("param".equals(tag.getName())) { - PsiDocTagValue value = tag.getValueElement(); - if (value instanceof PsiDocParamRef) { - PsiDocParamRef paramRef = (PsiDocParamRef)value; - final PsiReference psiReference = paramRef.getReference(); - if (psiReference != null && psiReference.isReferenceTo(param)) { - return true; - } - } - } - } - return false; - } - - private void processThrowsTags(@NotNull PsiDocTag[] tags, - @NotNull Map declaredExceptions, - @NotNull InspectionManager manager, - @NotNull final List problems, - boolean isOnTheFly) { - for (PsiDocTag tag : tags) { - if ("throws".equals(tag.getName()) || "exception".equals(tag.getName())) { - final PsiDocTagValue value = tag.getValueElement(); - if (value == null) continue; - final PsiElement firstChild = value.getFirstChild(); - if (firstChild == null) continue; - final PsiElement psiElement = firstChild.getFirstChild(); - if (!(psiElement instanceof PsiJavaCodeReferenceElement)) continue; - final PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)psiElement; - final PsiElement element = ref.resolve(); - if (element instanceof PsiClass){ - final PsiClass exceptionClass = (PsiClass)element; - for (Iterator it = declaredExceptions.keySet().iterator(); it.hasNext();) { - PsiClassType classType = it.next(); - final PsiClass psiClass = declaredExceptions.get(classType); - if (InheritanceUtil.isInheritorOrSelf(exceptionClass, psiClass, true)) { - if (!myIgnoreEmptyDescriptions && extractThrowsTagDescription(tag).isEmpty()) { - problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", "" + tag.getName() + ""), manager, - isOnTheFly)); - } - it.remove(); - } - } - } - } - } - } - - @Nullable - private static ProblemDescriptor createMissingThrowsTagDescriptor(final PsiElement elementToHighlight, - final InspectionManager manager, - final PsiClassType exceptionClassType, boolean isOnTheFly) { - @NonNls String tag = "throws"; - String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "@" + tag + " " + exceptionClassType.getCanonicalText()); - final String firstDeclaredException = exceptionClassType.getCanonicalText(); - return createDescriptor(elementToHighlight, message, new AddMissingTagFix(tag, firstDeclaredException), manager, isOnTheFly); - } - - private static ProblemDescriptor createMissingTagDescriptor(PsiElement elementToHighlight, - @NonNls String tag, - final InspectionManager manager, boolean isOnTheFly) { - String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "@" + tag + ""); - return createDescriptor(elementToHighlight, message, new AddMissingTagFix(tag), manager, isOnTheFly); - } - - private static ProblemDescriptor createMissingParamTagDescriptor(PsiElement elementToHighlight, - PsiParameter param, - final InspectionManager manager, boolean isOnTheFly) { - String message = InspectionsBundle.message("inspection.javadoc.method.problem.missing.param.tag", "@param", "" + param.getName() + ""); - return createDescriptor(elementToHighlight, message, new AddMissingParamTagFix(param.getName()), manager, isOnTheFly); - } - - private static class AddMissingParamTagFix extends AddMissingTagFix { - private final String myName; - - public AddMissingParamTagFix(String name) { - super("param", name); - myName = name; - } - - @Override - @NotNull - public String getName() { - return InspectionsBundle.message("inspection.javadoc.problem.add.param.tag", myName); - } - - @Override - @Nullable - protected PsiElement getAnchor(ProblemDescriptor descriptor) { - PsiElement element = descriptor.getPsiElement(); - PsiElement parent = element == null ? null : element.getParent(); - if (!(parent instanceof PsiDocComment)) return null; - final PsiDocComment docComment = (PsiDocComment)parent; - final PsiDocCommentOwner owner = docComment.getOwner(); - if (!(owner instanceof PsiMethod)) return null; - PsiParameter[] parameters = ((PsiMethod)owner).getParameterList().getParameters(); - PsiParameter myParam = ContainerUtil.find(parameters, new Condition() { - @Override - public boolean value(PsiParameter psiParameter) { - return myName.equals(psiParameter.getName()); - } - }); - if (myParam == null) return null; - - PsiDocTag[] tags = docComment.findTagsByName("param"); - if (tags.length == 0) { //insert as first tag or append to description - tags = docComment.getTags(); - if (tags.length == 0) return null; - return tags[0]; - } - - PsiParameter nextParam = PsiTreeUtil.getNextSiblingOfType(myParam, PsiParameter.class); - while (nextParam != null) { - for (PsiDocTag tag : tags) { - if (matches(nextParam, tag)) { - return tag; - } - } - nextParam = PsiTreeUtil.getNextSiblingOfType(nextParam, PsiParameter.class); - } - - PsiParameter prevParam = PsiTreeUtil.getPrevSiblingOfType(myParam, PsiParameter.class); - while (prevParam != null) { - for (PsiDocTag tag : tags) { - if (matches(prevParam, tag)) { - return PsiTreeUtil.getNextSiblingOfType(tag, PsiDocTag.class); - } - } - prevParam = PsiTreeUtil.getPrevSiblingOfType(prevParam, PsiParameter.class); - } - - return null; - } - - private static boolean matches(final PsiParameter param, final PsiDocTag tag) { - final PsiDocTagValue valueElement = tag.getValueElement(); - return valueElement != null && valueElement.getText().trim().startsWith(param.getName()); - } - } - - private static String extractTagDescription(PsiDocTag tag) { - StringBuilder buf = new StringBuilder(); - PsiElement[] children = tag.getChildren(); - for (PsiElement child : children) { - if (child instanceof PsiDocToken) { - PsiDocToken token = (PsiDocToken)child; - if (token.getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA) { - buf.append(token.getText()); - } - } - else if (child instanceof PsiDocTagValue || child instanceof PsiInlineDocTag) { - buf.append(child.getText()); - } - } - - return buf.toString().trim(); - } - - private static String extractThrowsTagDescription(PsiDocTag tag) { - StringBuilder buf = new StringBuilder(); - PsiElement[] children = tag.getChildren(); - for (PsiElement child : children) { - if (child instanceof PsiDocToken) { - PsiDocToken token = (PsiDocToken)child; - if (token.getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA) { - buf.append(token.getText()); - } - } - } - - return buf.toString().trim(); - } - - private static void checkForBadCharacters(PsiDocComment docComment, - final List problems, - final InspectionManager manager, final boolean onTheFly) { - docComment.accept(new PsiRecursiveElementVisitor(){ - @Override - public void visitElement(PsiElement element) { - super.visitElement(element); - final ASTNode node = element.getNode(); - if (node != null) { - if (node.getElementType() == JavaDocTokenType.DOC_COMMENT_BAD_CHARACTER) { - problems.add(manager.createProblemDescriptor(element, "Illegal character", (LocalQuickFix)null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, onTheFly)); - } - } - } - }); - } - - private void checkForPeriodInDoc(PsiElement context, - PsiDocComment docComment, - List problems, - InspectionManager manager, boolean onTheFly) { - if (IGNORE_JAVADOC_PERIOD) return; - PsiDocTag[] tags = docComment.getTags(); - int dotIndex = docComment.getText().indexOf('.'); - int tagOffset = 0; - if (dotIndex >= 0) { //need to find first valid tag - for (PsiDocTag tag : tags) { - final String tagName = tag.getName(); - final 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) { - problems.add(manager.createProblemDescriptor(docComment.getFirstChild(), - InspectionsBundle.message("inspection.javadoc.problem.descriptor1"), - null, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, onTheFly, false)); - } - } - - @Nullable - private ArrayList getTagValuesProblems(PsiElement context, PsiDocTag[] tags, InspectionManager inspectionManager, - boolean isOnTheFly) { - final ArrayList problems = new ArrayList(2); - for (PsiDocTag tag : tags) { - final JavadocManager manager = JavadocManager.SERVICE.getInstance(tag.getProject()); - String tagName = tag.getName(); - JavadocTagInfo tagInfo = manager.getTagInfo(tagName); - - if (tagInfo == null || !tagInfo.isValidInContext(context)) { - if (checkTagInfo(inspectionManager, tagInfo, tag, isOnTheFly, problems)) continue; - } - - PsiDocTagValue value = tag.getValueElement(); - if (tagInfo != null && !tagInfo.isValidInContext(context)) continue; - String message = tagInfo == null ? null : tagInfo.checkTagValue(value); - - final PsiReference reference = value != null ? value.getReference() : null; - if (message == null && reference != null) { - PsiElement element = reference.resolve(); - if (element == null) { - final int textOffset = value.getTextOffset(); - - if (textOffset == value.getTextRange().getEndOffset()) { - problems.add(inspectionManager.createProblemDescriptor(tag, InspectionsBundle.message("inspection.javadoc.problem.name.expected"), null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - isOnTheFly, true)); - } - } - } - - if (message != null) { - final PsiDocTagValue valueElement = tag.getValueElement(); - if (valueElement == null){ - problems.add(inspectionManager.createProblemDescriptor(tag, InspectionsBundle.message( - "inspection.javadoc.method.problem.missing.tag.description", "" + tag.getName() + ""), (LocalQuickFix)null, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, isOnTheFly)); } - else { - problems.add(createDescriptor(valueElement, message, inspectionManager, isOnTheFly)); - } - } - checkInlineTags(inspectionManager, problems, tag.getDataElements(), manager, isOnTheFly); - } - - return problems.isEmpty() ? null : problems; - } - - private boolean checkTagInfo(InspectionManager inspectionManager, - JavadocTagInfo tagInfo, - PsiDocTag tag, - boolean isOnTheFly, - List problems) { - final String tagName = tag.getName(); - final StringTokenizer tokenizer = new StringTokenizer(myAdditionalJavadocTags, ", "); - while (tokenizer.hasMoreTokens()) { - if (Comparing.strEqual(tagName, tokenizer.nextToken())) return true; - } - - final PsiElement nameElement = tag.getNameElement(); - if (nameElement != null) { - if (tagInfo == null) { - problems.add( - createDescriptor(nameElement, InspectionsBundle.message("inspection.javadoc.problem.wrong.tag", "" + tagName + ""), - new AddUnknownTagToCustoms(tag.getName()), inspectionManager, isOnTheFly)); - } - else { - problems.add(createDescriptor(nameElement, InspectionsBundle.message("inspection.javadoc.problem.disallowed.tag", - "" + tagName + ""), - new AddUnknownTagToCustoms(tag.getName()), inspectionManager, isOnTheFly)); - } - } - return false; - } - - private void checkInlineTags(@NotNull InspectionManager inspectionManager, - @NotNull List problems, - @NotNull PsiElement[] dataElements, - @NotNull JavadocManager manager, - boolean isOnTheFly) { - for (PsiElement dataElement : dataElements) { - if (dataElement instanceof PsiInlineDocTag) { - final PsiInlineDocTag inlineDocTag = (PsiInlineDocTag)dataElement; - final PsiElement nameElement = inlineDocTag.getNameElement(); - if (manager.getTagInfo(inlineDocTag.getName()) == null) { - checkTagInfo(inspectionManager, null, inlineDocTag, isOnTheFly, problems); - } - if (!IGNORE_POINT_TO_ITSELF) { - final PsiDocTagValue value = inlineDocTag.getValueElement(); - if (value != null) { - final PsiReference reference = value.getReference(); - if (reference != null) { - final PsiElement ref = reference.resolve(); - if (ref != null){ - if (PsiTreeUtil.getParentOfType(inlineDocTag, PsiDocCommentOwner.class) == PsiTreeUtil.getParentOfType(ref, PsiDocCommentOwner.class, false)) { - if (nameElement != null) { - problems.add(createDescriptor(nameElement, InspectionsBundle.message("inspection.javadoc.problem.pointing.to.itself"), inspectionManager, - isOnTheFly)); - } - } - } - } - } - } - } - } - } - - @SuppressWarnings({"SimplifiableIfStatement"}) - private boolean isTagRequired(PsiElement context, @NonNls String tag) { + private boolean isTagRequired(PsiElement context, String tag) { if (context instanceof PsiPackage) { return isTagRequired(PACKAGE_OPTIONS, tag); } if (context instanceof PsiClass) { - if (PsiTreeUtil.getParentOfType(context, PsiClass.class) != null) { - return isTagRequired(INNER_CLASS_OPTIONS, tag); - } - - return isTagRequired(TOP_LEVEL_CLASS_OPTIONS, tag); + boolean isInner = PsiTreeUtil.getParentOfType(context, PsiClass.class) != null; + return isTagRequired(isInner ? INNER_CLASS_OPTIONS : TOP_LEVEL_CLASS_OPTIONS, tag); } if (context instanceof PsiMethod) { @@ -963,116 +392,27 @@ public class JavaDocLocalInspectionBase extends BaseJavaBatchLocalInspectionTool return options.REQUIRED_TAGS.contains(tag); } - private boolean isJavaDocRequired(@NotNull final PsiModifierListOwner element) { - PsiModifierListOwner psiElement = element; - final RefJavaUtil refUtil = RefJavaUtil.getInstance(); - int actualAccess = getAccessNumber(refUtil.getAccessModifier(psiElement)); - if (psiElement instanceof PsiPackage) { - return 1 <= getAccessNumber(PACKAGE_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR); + private static boolean isDeprecated(PsiDocCommentOwner element) { + return element.isDeprecated() || element.getContainingClass() != null && element.getContainingClass().isDeprecated(); + } + + private static boolean isInherited(PsiDocComment docComment, PsiElement[] descriptionElements, PsiMethod psiMethod) { + for (PsiElement descriptionElement : descriptionElements) { + if (descriptionElement instanceof PsiInlineDocTag && "inheritDoc".equals(((PsiInlineDocTag)descriptionElement).getName())) { + return true; + } } - if (psiElement instanceof PsiClass) { - PsiClass psiClass = (PsiClass)psiElement; - if (PsiTreeUtil.getParentOfType(psiClass, PsiClass.class) != null) { - return actualAccess <= getAccessNumber(INNER_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR); + if (docComment.findTagByName("inheritDoc") != null) { + JavadocTagInfo tagInfo = JavadocManager.SERVICE.getInstance(psiMethod.getProject()).getTagInfo("inheritDoc"); + if (tagInfo != null && tagInfo.isValidInContext(psiMethod)) { + return true; } - - return actualAccess <= getAccessNumber(TOP_LEVEL_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR); - } - - if (psiElement instanceof PsiMethod) { - psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class); - while (psiElement != null) { - actualAccess = Math.max(actualAccess, getAccessNumber(refUtil.getAccessModifier(psiElement))); - psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class); - } - - return actualAccess <= getAccessNumber(METHOD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR); - } - - if (psiElement instanceof PsiField) { - psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class); - while (psiElement != null) { - actualAccess = Math.max(actualAccess, getAccessNumber(refUtil.getAccessModifier(psiElement))); - psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class); - } - - return actualAccess <= getAccessNumber(FIELD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR); } return false; } - private void checkDuplicateTags(final PsiDocTag[] tags, - List problems, - final InspectionManager manager, boolean isOnTheFly) { - Set documentedParamNames = null; - Set documentedExceptions = null; - Set uniqueTags = null; - for(PsiDocTag tag: tags) { - if ("param".equals(tag.getName())) { - PsiDocTagValue value = tag.getValueElement(); - if (value instanceof PsiDocParamRef) { - PsiDocParamRef paramRef = (PsiDocParamRef)value; - final PsiReference reference = paramRef.getReference(); - if (reference != null) { - final String paramName = reference.getCanonicalText(); - if (documentedParamNames == null) { - documentedParamNames = new HashSet(); - } - if (documentedParamNames.contains(paramName)) { - problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.duplicate.param", paramName), manager, - isOnTheFly)); - } - documentedParamNames.add(paramName); - } - } - } - else if (!myIgnoreDuplicatedThrows && ("throws".equals(tag.getName()) || "exception".equals(tag.getName()))) { - PsiDocTagValue value = tag.getValueElement(); - if (value != null) { - final 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(); - if (documentedExceptions == null) { - documentedExceptions = new HashSet(); - } - if (documentedExceptions.contains(fqName)) { - problems.add(createDescriptor(tag.getNameElement(), - InspectionsBundle.message("inspection.javadoc.problem.duplicate.throws", fqName), - manager, isOnTheFly)); - } - documentedExceptions.add(fqName); - } - } - } - } - else if (ourUniqueTags.contains(tag.getName())) { - if (uniqueTags == null) { - uniqueTags = new HashSet(); - } - if (uniqueTags.contains(tag.getName())) { - problems.add(createDescriptor(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.duplicate.tag", tag.getName()), manager, - isOnTheFly)); - } - uniqueTags.add(tag.getName()); - } - } - } - - private static int getAccessNumber(@NonNls String accessModifier) { - if (accessModifier.startsWith("none")) return 0; - if (accessModifier.startsWith("public")) return 1; - if (accessModifier.startsWith("protected")) return 2; - if (accessModifier.startsWith("package")) return 3; - if (accessModifier.startsWith("private")) return 4; - - return 5; - } - @Override @NotNull public String getDisplayName() { @@ -1097,54 +437,77 @@ public class JavaDocLocalInspectionBase extends BaseJavaBatchLocalInspectionTool return "javadoc"; } - public void setIgnoreEmptyDescriptions(boolean ignoreEmptyDescriptions) { - myIgnoreEmptyDescriptions = ignoreEmptyDescriptions; + protected LocalQuickFix createAddJavadocFix(@NotNull PsiElement nameIdentifier, boolean isOnTheFly) { + return null; } - private class AddUnknownTagToCustoms implements LocalQuickFix { - private final String myTag; + protected LocalQuickFix createAddMissingTagFix(@NotNull String tag, @NotNull String value, boolean isOnTheFly) { + return null; + } - public AddUnknownTagToCustoms(String tag) { - myTag = tag; + protected LocalQuickFix createAddMissingParamTagFix(@NotNull String name, boolean isOnTheFly) { + return null; + } + + protected LocalQuickFix createRegisterTagFix(@NotNull String tag, boolean isOnTheFly) { + return null; + } + + private class ProblemHolderImpl implements JavadocHighlightUtil.ProblemHolder { + private final InspectionManager myManager; + private final boolean myOnTheFly; + private List myProblems = null; + + private ProblemHolderImpl(InspectionManager manager, boolean onTheFly) { + myManager = manager; + myOnTheFly = onTheFly; + } + + public ProblemDescriptor[] problems() { + return myProblems == null || myProblems.isEmpty() ? null : myProblems.toArray(new ProblemDescriptor[myProblems.size()]); } @Override - @NotNull - public String getName() { - return QuickFixBundle.message("add.docTag.to.custom.tags", myTag); + public Project project() { + return myManager.getProject(); } @Override - @NotNull - public String getFamilyName() { - return QuickFixBundle.message("fix.javadoc.family"); - } + public JavaDocLocalInspectionBase inspection() { + return JavaDocLocalInspectionBase.this; + } @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - if (myTag == null) return; - if (!myAdditionalJavadocTags.isEmpty()) { - myAdditionalJavadocTags += "," + myTag; - } - else { - myAdditionalJavadocTags = myTag; - } - final InspectionProfile inspectionProfile = - InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - //correct save settings - InspectionProfileManager.getInstance().fireProfileChanged(inspectionProfile); - //TODO lesya + public void problem(@NotNull PsiElement toHighlight, @NotNull @Nls String message, @Nullable LocalQuickFix fix) { + if (myProblems == null) myProblems = ContainerUtil.newSmartList(); + myProblems.add(myManager.createProblemDescriptor(toHighlight, message, fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, myOnTheFly)); + } - /* + @Override + public void eolProblem(@NotNull PsiElement toHighlight, @NotNull @Nls String message, @Nullable LocalQuickFix fix) { + if (myProblems == null) myProblems = ContainerUtil.newSmartList(); + LocalQuickFix[] fixes = fix != null ? new LocalQuickFix[]{fix} : null; + myProblems.add(myManager.createProblemDescriptor(toHighlight, message, fixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, myOnTheFly, true)); + } - try { - inspectionProfile.save(); - } - catch (IOException e) { - Messages.showErrorDialog(project, e.getMessage(), CommonBundle.getErrorTitle()); - } + @Override + public LocalQuickFix addJavadocFix(@NotNull PsiElement nameIdentifier) { + return createAddJavadocFix(nameIdentifier, myOnTheFly); + } - */ + @Override + public LocalQuickFix addMissingTagFix(@NotNull String tag, @NotNull String value) { + return createAddMissingTagFix(tag, value, myOnTheFly); + } + + @Override + public LocalQuickFix addMissingParamTagFix(@NotNull String name) { + return createAddMissingParamTagFix(name, myOnTheFly); + } + + @Override + public LocalQuickFix registerTagFix(@NotNull String tag) { + return createRegisterTagFix(tag, myOnTheFly); } } -} +} \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavaDocReferenceInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavaDocReferenceInspectionBase.java index bfe239891fff..d32a21279e9d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavaDocReferenceInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavaDocReferenceInspectionBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,7 +75,7 @@ public class JavaDocReferenceInspectionBase extends BaseJavaBatchLocalInspectio final PsiDocTag[] tags = tag.getContainingComment().getTags(); final Set unboundParams = new HashSet(); for (PsiParameter parameter : parameters) { - if (!JavaDocLocalInspectionBase.isFound(tags, parameter)) { + if (!JavadocHighlightUtil.hasTagForParameter(tags, parameter)) { unboundParams.add(parameter.getName()); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavadocHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavadocHighlightUtil.java new file mode 100644 index 000000000000..5a6697869b86 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/javaDoc/JavadocHighlightUtil.java @@ -0,0 +1,492 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * 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.intellij.codeInspection.javaDoc; + +import com.intellij.codeInspection.*; +import com.intellij.codeInspection.reference.RefJavaUtil; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.javadoc.PsiDocParamRef; +import com.intellij.psi.javadoc.*; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class JavadocHighlightUtil { + private static final String[] TAGS_TO_CHECK = {"author", "version", "since"}; + private static final Set UNIQUE_TAGS = ContainerUtil.newHashSet("return", "deprecated", "serial", "serialData"); + + public interface ProblemHolder { + Project project(); + JavaDocLocalInspectionBase inspection(); + + void problem(@NotNull PsiElement toHighlight, @NotNull @Nls String message, @Nullable LocalQuickFix fix); + void eolProblem(@NotNull PsiElement toHighlight, @NotNull @Nls String message, @Nullable LocalQuickFix fix); + + LocalQuickFix addJavadocFix(@NotNull PsiElement nameIdentifier); + LocalQuickFix addMissingTagFix(@NotNull String tag, @NotNull String value); + LocalQuickFix addMissingParamTagFix(@NotNull String name); + LocalQuickFix registerTagFix(@NotNull String tag); + } + + static boolean isJavaDocRequired(@NotNull JavaDocLocalInspectionBase inspection, @NotNull PsiModifierListOwner element) { + int actualAccess = getAccessNumber(RefJavaUtil.getInstance().getAccessModifier(element)); + + if (element instanceof PsiPackage) { + return 1 <= getAccessNumber(inspection.PACKAGE_OPTIONS); + } + + if (element instanceof PsiClass) { + boolean isInner = PsiTreeUtil.getParentOfType(element, PsiClass.class) != null; + return actualAccess <= getAccessNumber(isInner ? inspection.INNER_CLASS_OPTIONS : inspection.TOP_LEVEL_CLASS_OPTIONS); + } + + if (element instanceof PsiMethod) { + element = PsiTreeUtil.getParentOfType(element, PsiClass.class); + while (element != null) { + actualAccess = Math.max(actualAccess, getAccessNumber(RefJavaUtil.getInstance().getAccessModifier(element))); + element = PsiTreeUtil.getParentOfType(element, PsiClass.class); + } + + return actualAccess <= getAccessNumber(inspection.METHOD_OPTIONS); + } + + if (element instanceof PsiField) { + element = PsiTreeUtil.getParentOfType(element, PsiClass.class); + while (element != null) { + actualAccess = Math.max(actualAccess, getAccessNumber(RefJavaUtil.getInstance().getAccessModifier(element))); + element = PsiTreeUtil.getParentOfType(element, PsiClass.class); + } + + return actualAccess <= getAccessNumber(inspection.FIELD_OPTIONS); + } + + return false; + } + + private static int getAccessNumber(JavaDocLocalInspectionBase.Options options) { + return getAccessNumber(options.ACCESS_JAVADOC_REQUIRED_FOR); + } + + private static int getAccessNumber(String accessModifier) { + if (accessModifier.startsWith("none")) return 0; + if (accessModifier.startsWith("public")) return 1; + if (accessModifier.startsWith("protected")) return 2; + if (accessModifier.startsWith("package")) return 3; + if (accessModifier.startsWith("private")) return 4; + + return 5; + } + + static void reportMissingTag(@NotNull PsiElement toHighlight, @NotNull ProblemHolder holder) { + String message = InspectionsBundle.message("inspection.javadoc.problem.descriptor"); + holder.problem(toHighlight, message, holder.addJavadocFix(toHighlight)); + } + + static void checkRequiredTags(@NotNull PsiDocTag[] tags, + @NotNull Predicate tagRequired, + @NotNull PsiElement toHighlight, + @NotNull ProblemHolder holder) { + boolean[] isTagRequired = new boolean[TAGS_TO_CHECK.length]; + boolean[] isTagPresent = new boolean[TAGS_TO_CHECK.length]; + boolean someTagsAreRequired = false; + + for (int i = 0; i < TAGS_TO_CHECK.length; i++) { + someTagsAreRequired |= (isTagRequired[i] = tagRequired.test(TAGS_TO_CHECK[i])); + } + + if (!someTagsAreRequired) return; + + for (PsiDocTag tag : tags) { + int p = ArrayUtil.find(TAGS_TO_CHECK, tag.getName()); + if (p >= 0) isTagPresent[p] = true; + } + + for (int i = 0; i < TAGS_TO_CHECK.length; i++) { + if (isTagRequired[i] && !isTagPresent[i]) { + String tagName = TAGS_TO_CHECK[i]; + String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "@" + tagName + ""); + holder.problem(toHighlight, message, holder.addMissingTagFix(tagName, "")); + } + } + } + + static void checkRequiredTagDescriptions(@NotNull PsiDocTag[] tags, @NotNull ProblemHolder holder) { + for (PsiDocTag tag : tags) { + String tagName = tag.getName(); + if (ArrayUtil.find(TAGS_TO_CHECK, tagName) >= 0 && emptyTag(tag)) { + String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag.description", StringUtil.capitalize(tagName), tagName); + holder.problem(tag.getNameElement(), message, null); + } + } + } + + static void checkTagValues(@NotNull PsiDocTag[] tags, @Nullable PsiElement context, @NotNull ProblemHolder holder) { + JavadocManager docManager = JavadocManager.SERVICE.getInstance(holder.project()); + for (PsiDocTag tag : tags) { + String tagName = tag.getName(); + JavadocTagInfo tagInfo = docManager.getTagInfo(tagName); + + if (tagInfo == null || !tagInfo.isValidInContext(context)) { + if (checkTagInfo(tag, 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()) { + holder.eolProblem(tag, InspectionsBundle.message("inspection.javadoc.problem.name.expected"), null); + } + } + } + + if (message != null) { + PsiDocTagValue valueElement = tag.getValueElement(); + if (valueElement == null) { + String tagText = "" + tag.getName() + ""; + holder.problem(tag, InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", tagText), null); + } + else { + holder.problem(valueElement, message, null); + } + } + + checkInlineTags(tag.getDataElements(), holder); + } + } + + static void checkInlineTags(@NotNull PsiElement[] elements, @NotNull ProblemHolder holder) { + JavadocManager docManager = JavadocManager.SERVICE.getInstance(holder.project()); + for (PsiElement element : elements) { + if (element instanceof PsiInlineDocTag) { + PsiInlineDocTag tag = (PsiInlineDocTag)element; + if (docManager.getTagInfo(tag.getName()) == null) { + checkTagInfo(tag, null, holder); + } + if (!holder.inspection().IGNORE_POINT_TO_ITSELF) { + PsiDocTagValue value = tag.getValueElement(); + if (value != null) { + PsiReference reference = value.getReference(); + if (reference != null) { + PsiElement target = reference.resolve(); + if (target != null) { + if (PsiTreeUtil.getParentOfType(tag, PsiDocCommentOwner.class) == + PsiTreeUtil.getParentOfType(target, PsiDocCommentOwner.class, false)) { + PsiElement nameElement = tag.getNameElement(); + if (nameElement != null) { + holder.problem(nameElement, InspectionsBundle.message("inspection.javadoc.problem.pointing.to.itself"), null); + } + } + } + } + } + } + } + } + } + + private static boolean checkTagInfo(PsiDocTag tag, JavadocTagInfo tagInfo, ProblemHolder holder) { + String tagName = tag.getName(); + + StringTokenizer tokenizer = new StringTokenizer(holder.inspection().myAdditionalJavadocTags, ", "); + 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"; + holder.problem(nameElement, InspectionsBundle.message(key, "" + tagName + ""), holder.registerTagFix(tagName)); + } + + return false; + } + + static void checkForPeriod(@NotNull PsiDocComment docComment, @Nullable PsiElement context, @NotNull ProblemHolder 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.problem(docComment.getFirstChild(), InspectionsBundle.message("inspection.javadoc.problem.descriptor1"), null); + } + } + + static void checkDuplicateTags(@NotNull PsiDocTag[] tags, @NotNull ProblemHolder holder) { + Set documentedParamNames = null; + Set documentedExceptions = null; + Set 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(); + documentedParamNames = set(documentedParamNames); + if (documentedParamNames.contains(paramName)) { + holder.problem(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.duplicate.param", paramName), null); + } + documentedParamNames.add(paramName); + } + } + } + else if (!holder.inspection().isIgnoreDuplicatedThrows() && ("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.problem(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.duplicate.throws", fqName), null); + } + documentedExceptions.add(fqName); + } + } + } + } + else if (UNIQUE_TAGS.contains(tag.getName())) { + uniqueTags = set(uniqueTags); + if (uniqueTags.contains(tag.getName())) { + holder.problem(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.problem.duplicate.tag", tag.getName()), null); + } + uniqueTags.add(tag.getName()); + } + } + } + + static void checkForBadCharacters(@NotNull PsiDocComment docComment, @NotNull ProblemHolder holder) { + docComment.accept(new PsiRecursiveElementVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + ASTNode node = element.getNode(); + if (node != null && node.getElementType() == JavaDocTokenType.DOC_COMMENT_BAD_CHARACTER) { + holder.problem(element, InspectionsBundle.message("inspection.illegal.character"), null); + } + } + }); + } + + static void checkMissingTypeParamTags(@NotNull PsiClass psiClass, + @NotNull PsiDocTag[] tags, + @NotNull PsiElement toHighlight, + @NotNull ProblemHolder holder) { + if (psiClass.hasTypeParameters()) { + List absentParameters = null; + + for (PsiTypeParameter typeParameter : psiClass.getTypeParameters()) { + if (!hasTagForParameter(tags, typeParameter)) { + (absentParameters = list(absentParameters)).add(typeParameter); + } + } + + if (absentParameters != null) { + for (PsiTypeParameter typeParameter : absentParameters) { + String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "@param"); + holder.problem(toHighlight, message, holder.addMissingTagFix("param", "<" + typeParameter.getName() + ">")); + } + } + } + } + + static void checkMissingReturnTag(@NotNull PsiDocTag[] tags, + @NotNull PsiMethod psiMethod, + @NotNull PsiElement toHighlight, + @NotNull ProblemHolder holder) { + if (!psiMethod.isConstructor() && !PsiType.VOID.equals(psiMethod.getReturnType())) { + boolean hasReturnTag = Stream.of(tags).anyMatch(tag -> "return".equals(tag.getName())); + if (!hasReturnTag) { + String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", "@" + "return" + ""); + holder.problem(toHighlight, message, holder.addMissingTagFix("return", "")); + } + } + } + + static void checkMissingParamTags(@NotNull PsiDocTag[] tags, + @NotNull PsiMethod psiMethod, + @NotNull PsiElement toHighlight, + @NotNull ProblemHolder holder) { + List absentParameters = null; + + for (PsiParameter param : psiMethod.getParameterList().getParameters()) { + if (!hasTagForParameter(tags, param)) { + (absentParameters = list(absentParameters)).add(param); + } + } + + if (absentParameters != null) { + for (PsiParameter parameter : absentParameters) { + String name = parameter.getName(); + if (name != null) { + String tagText = "" + name + ""; + String message = InspectionsBundle.message("inspection.javadoc.method.problem.missing.param.tag", tagText); + holder.problem(toHighlight, message, holder.addMissingParamTagFix(name)); + } + } + } + } + + static void checkMissingThrowsTags(@NotNull PsiDocTag[] tags, + @NotNull PsiMethod psiMethod, + @NotNull PsiElement toHighlight, + @NotNull ProblemHolder holder) { + PsiClassType[] thrownTypes = psiMethod.getThrowsList().getReferencedTypes(); + if (thrownTypes.length <= 0) return; + + Map declaredExceptions = ContainerUtil.newLinkedHashMap(); + + for (PsiClassType classType : thrownTypes) { + PsiClass psiClass = classType.resolve(); + if (psiClass != null) { + declaredExceptions.put(classType, psiClass); + } + } + + for (PsiDocTag tag : tags) { + if ("throws".equals(tag.getName()) || "exception".equals(tag.getName())) { + PsiDocTagValue value = tag.getValueElement(); + if (value != null) { + PsiElement firstChild = value.getFirstChild(); + if (firstChild != null) { + PsiElement psiElement = firstChild.getFirstChild(); + if ((psiElement instanceof PsiJavaCodeReferenceElement)) { + PsiElement target = ((PsiJavaCodeReferenceElement)psiElement).resolve(); + if (target instanceof PsiClass) { + for (Iterator it = declaredExceptions.keySet().iterator(); it.hasNext(); ) { + PsiClass psiClass = declaredExceptions.get(it.next()); + if (InheritanceUtil.isInheritorOrSelf((PsiClass)target, psiClass, true)) { + it.remove(); + } + } + } + } + } + } + } + } + + for (PsiClassType declaredException : declaredExceptions.keySet()) { + String tagText = "@throws " + declaredException.getCanonicalText(); + String message = InspectionsBundle.message("inspection.javadoc.problem.missing.tag", tagText); + String firstDeclaredException = declaredException.getCanonicalText(); + holder.problem(toHighlight, message, holder.addMissingTagFix("throws", firstDeclaredException)); + } + } + + static void checkEmptyMethodTagsDescription(@NotNull PsiDocTag[] tags, @NotNull ProblemHolder holder) { + for (PsiDocTag tag : tags) { + if ("return".equals(tag.getName())) { + if (emptyTag(tag)) { + String tagText = "@return"; + holder.problem(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", tagText), null); + } + } + else if ("throws".equals(tag.getName()) || "exception".equals(tag.getName())) { + if (emptyThrowsTag(tag)) { + String tagText = "" + tag.getName() + ""; + holder.problem(tag.getNameElement(), InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", tagText), null); + } + } + else if ("param".equals(tag.getName())) { + PsiDocTagValue valueElement = tag.getValueElement(); + if (valueElement != null && emptyParamTag(tag, valueElement)) { + String tagText = "@param " + valueElement.getText() + ""; + holder.problem(valueElement, InspectionsBundle.message("inspection.javadoc.method.problem.missing.tag.description", tagText), null); + } + } + } + } + + private static Set set(Set set) { + return set != null ? set : ContainerUtil.newHashSet(); + } + + private static List list(List list) { + return list != null ? list : ContainerUtil.newSmartList(); + } + + private static boolean emptyTag(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(JavadocHighlightUtil::empty); + } + + private static boolean emptyThrowsTag(PsiDocTag tag) { + return Stream.of(tag.getChildren()) + .filter(e -> e instanceof PsiDocToken && ((PsiDocToken)e).getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA) + .allMatch(JavadocHighlightUtil::empty); + } + + private static boolean emptyParamTag(PsiDocTag tag, PsiDocTagValue valueElement) { + PsiElement[] dataElements = tag.getDataElements(); + return dataElements.length < 2 || Stream.of(dataElements) + .filter(e -> e != valueElement) + .allMatch(JavadocHighlightUtil::empty); + } + + private static boolean empty(PsiElement e) { + return e.getText().chars().allMatch(c -> c <= ' '); + } + + public static boolean hasTagForParameter(@NotNull PsiDocTag[] tags, PsiElement param) { + for (PsiDocTag tag : tags) { + if ("param".equals(tag.getName())) { + PsiDocTagValue value = tag.getValueElement(); + if (value instanceof PsiDocParamRef) { + PsiReference psiReference = value.getReference(); + if (psiReference != null && psiReference.isReferenceTo(param)) { + return true; + } + } + } + } + + return false; + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaDocCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaDocCompletionContributor.java index a41d3895d733..4e37ce4c1467 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaDocCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaDocCompletionContributor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import com.intellij.codeInspection.InspectionProfile; import com.intellij.codeInspection.SuppressionUtilCore; import com.intellij.codeInspection.javaDoc.JavaDocLocalInspection; import com.intellij.codeInspection.javaDoc.JavaDocLocalInspectionBase; +import com.intellij.codeInspection.javaDoc.JavadocHighlightUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.*; import com.intellij.openapi.project.Project; @@ -342,7 +343,7 @@ public class JavaDocCompletionContributor extends CompletionContributor { if (psiMethod != null) { PsiDocTag[] tags = comment.getTags(); for (PsiParameter param : psiMethod.getParameterList().getParameters()) { - if (!JavaDocLocalInspectionBase.isFound(tags, param)) { + if (!JavadocHighlightUtil.hasTagForParameter(tags, param)) { result.add(tagName + " " + param.getName()); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavaDocLocalInspection.java b/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavaDocLocalInspection.java index 0dd24e56282c..9e24f0362940 100644 --- a/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavaDocLocalInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavaDocLocalInspection.java @@ -3,24 +3,28 @@ */ package com.intellij.codeInspection.javaDoc; +import com.intellij.codeInsight.FileModificationService; +import com.intellij.codeInsight.daemon.QuickFixBundle; import com.intellij.codeInsight.intention.impl.AddJavadocIntention; -import com.intellij.codeInspection.InspectionsBundle; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; +import com.intellij.codeInspection.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.FieldPanel; -import com.intellij.ui.Gray; -import com.intellij.ui.IdeBorderFactory; +import com.intellij.openapi.util.Condition; +import com.intellij.pom.Navigatable; +import com.intellij.profile.codeInspection.InspectionProfileManager; +import com.intellij.profile.codeInspection.InspectionProjectProfileManager; +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.util.PsiTreeUtil; +import com.intellij.ui.*; import com.intellij.ui.components.JBTabbedPane; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,50 +35,100 @@ import javax.swing.event.DocumentEvent; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.Hashtable; public class JavaDocLocalInspection extends JavaDocLocalInspectionBase { - private static final Logger LOG = Logger.getInstance("com.intellij.codeInspection.javaDoc.JavaDocLocalInspection"); @Override public JComponent createOptionsPanel() { return new OptionsPanel(); } @Override - protected LocalQuickFix createAddJavadocFix(@NotNull final PsiElement nameIdentifier, boolean isOnTheFly) { - if (isOnTheFly) { - final AddJavadocIntention intention = new AddJavadocIntention(); - return new LocalQuickFixAndIntentionActionOnPsiElement(nameIdentifier) { - @Override - public void invoke(@NotNull Project project, - @NotNull PsiFile file, - @Nullable("is null when called from inspection") Editor editor, - @NotNull PsiElement startElement, - @NotNull PsiElement endElement) { - intention.invoke(project, editor, startElement); - } + protected LocalQuickFix createAddJavadocFix(@NotNull PsiElement nameIdentifier, boolean isOnTheFly) { + return isOnTheFly ? new AddJavadocFix(nameIdentifier) : null; + } - @NotNull - @Override - public String getText() { - return intention.getText(); - } + @Override + protected LocalQuickFix createAddMissingTagFix(@NotNull String tag, @NotNull String value, boolean isOnTheFly) { + return new AddMissingTagFix(tag, value); + } - @Nls - @NotNull - @Override - public String getFamilyName() { - return intention.getFamilyName(); - } - }; - } + @Override + protected LocalQuickFix createAddMissingParamTagFix(@NotNull String name, boolean isOnTheFly) { + return new AddMissingParamTagFix(name); + } - return super.createAddJavadocFix(nameIdentifier, false); + @Override + protected LocalQuickFix createRegisterTagFix(@NotNull String tag, boolean isOnTheFly) { + return new AddUnknownTagToCustoms(this, tag); } private class OptionsPanel extends JPanel { + public OptionsPanel() { + super(new GridBagLayout()); + GridBagConstraints gc = + new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1, 0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + + String title = InspectionsBundle.message("inspection.javadoc.dialog.title"); + FieldPanel additionalTagsPanel = new FieldPanel(InspectionsBundle.message("inspection.javadoc.label.text"), title, null, null); + additionalTagsPanel.setPreferredSize(new Dimension(150, additionalTagsPanel.getPreferredSize().height)); + additionalTagsPanel.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + Document document = e.getDocument(); + try { + String text = document.getText(0, document.getLength()); + if (text != null) { + myAdditionalJavadocTags = text.trim(); + } + } + catch (BadLocationException ex) { + Logger.getInstance(JavaDocLocalInspection.class).error(ex); + } + } + }); + additionalTagsPanel.setText(myAdditionalJavadocTags); + add(additionalTagsPanel, gc); + + JTabbedPane tabs = new JBTabbedPane(SwingConstants.BOTTOM); + String[] tags = {"@author", "@version", "@since"}; + tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.package"), + createOptionsPanel(new String[]{NONE, PUBLIC}, tags, PACKAGE_OPTIONS)); + tags = new String[]{"@author", "@version", "@since", "@param"}; + tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title"), + createOptionsPanel(new String[]{NONE, PUBLIC, PACKAGE_LOCAL}, tags, TOP_LEVEL_CLASS_OPTIONS)); + tags = new String[]{"@return", "@param", InspectionsBundle.message("inspection.javadoc.throws.or.exception.option")}; + tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.method"), + createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, tags, METHOD_OPTIONS)); + tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.field"), + createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, null, FIELD_OPTIONS)); + tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.inner.class"), + createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, null, INNER_CLASS_OPTIONS)); + add(tabs, gc); + + JCheckBox checkBox = new JCheckBox(InspectionsBundle.message("inspection.javadoc.option.ignore.deprecated"), IGNORE_DEPRECATED); + checkBox.addActionListener(e -> IGNORE_DEPRECATED = checkBox.isSelected()); + gc.gridwidth = 1; + add(checkBox, gc); + + JCheckBox periodCheckBox = new JCheckBox(InspectionsBundle.message("inspection.javadoc.option.ignore.period"), IGNORE_JAVADOC_PERIOD); + periodCheckBox.addActionListener(e -> IGNORE_JAVADOC_PERIOD = periodCheckBox.isSelected()); + add(periodCheckBox, gc); + + JCheckBox ignoreDuplicateThrowsCheckBox = new JCheckBox("Ignore duplicate throws tag", isIgnoreDuplicatedThrows()); + ignoreDuplicateThrowsCheckBox.addActionListener(e -> setIgnoreDuplicatedThrows(ignoreDuplicateThrowsCheckBox.isSelected())); + add(ignoreDuplicateThrowsCheckBox, gc); + + JCheckBox ignorePointToItselfCheckBox = new JCheckBox("Ignore javadoc pointing to itself", IGNORE_POINT_TO_ITSELF); + ignorePointToItselfCheckBox.addActionListener(e -> IGNORE_POINT_TO_ITSELF = ignorePointToItselfCheckBox.isSelected()); + add(ignorePointToItselfCheckBox, gc); + + JCheckBox ignoreSimpleAccessorsCheckBox = new JCheckBox("Ignore simple property accessors", isIgnoreSimpleAccessors()); + ignoreSimpleAccessorsCheckBox.addActionListener(e -> setIgnoreSimpleAccessors(ignoreSimpleAccessorsCheckBox.isSelected())); + add(ignoreSimpleAccessorsCheckBox, gc); + } + private JPanel createOptionsPanel(String[] modifiers, String[] tags, Options options) { JPanel pane = new JPanel(new GridLayout(1, tags == null ? 1 : 2)); @@ -88,11 +142,50 @@ public class JavaDocLocalInspection extends JavaDocLocalInspectionBase { return pane; } + private JPanel createScopePanel(String[] modifiers, Options options) { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createCompoundBorder( + IdeBorderFactory.createTitledBorder(InspectionsBundle.message("inspection.scope.for.title"), true), + BorderFactory.createEmptyBorder(0, 3, 3, 3))); + + @SuppressWarnings("UseOfObsoleteCollectionType") Hashtable sliderLabels = new Hashtable<>(); + for (int i = 0; i < modifiers.length; i++) { + sliderLabels.put(i + 1, new JLabel(modifiers[i])); + } + + JSlider slider = new JSlider(SwingConstants.VERTICAL, 1, modifiers.length, 1); + slider.setLabelTable(sliderLabels); + slider.putClientProperty(UIUtil.JSLIDER_ISFILLED, Boolean.TRUE); + slider.setPreferredSize(JBUI.size(80, 50)); + slider.setPaintLabels(true); + slider.setSnapToTicks(true); + slider.addChangeListener(e -> { + int value = slider.getValue(); + options.ACCESS_JAVADOC_REQUIRED_FOR = modifiers[value - 1]; + for (Integer key : sliderLabels.keySet()) { + sliderLabels.get(key).setForeground(key.intValue() <= value ? JBColor.BLACK : Gray._100); + } + }); + + Color fore = JBColor.BLACK; + for (int i = 0; i < modifiers.length; i++) { + sliderLabels.get(i + 1).setForeground(fore); + if (modifiers[i].equals(options.ACCESS_JAVADOC_REQUIRED_FOR)) { + slider.setValue(i + 1); + fore = Gray._100; + } + } + + panel.add(slider, BorderLayout.WEST); + + return panel; + } + private JPanel createTagsPanel(String[] tags, Options options) { JPanel panel = new JPanel(new GridBagLayout()); - panel.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createTitledBorder( - InspectionsBundle.message("inspection.javadoc.required.tags.option.title"), true), - BorderFactory.createEmptyBorder(0, 3, 3, 3))); + panel.setBorder(BorderFactory.createCompoundBorder( + IdeBorderFactory.createTitledBorder(InspectionsBundle.message("inspection.javadoc.required.tags.option.title"), true), + BorderFactory.createEmptyBorder(0, 3, 3, 3))); GridBagConstraints gc = new GridBagConstraints(); gc.weightx = 1; @@ -100,7 +193,6 @@ public class JavaDocLocalInspection extends JavaDocLocalInspectionBase { gc.fill = GridBagConstraints.HORIZONTAL; gc.anchor = GridBagConstraints.NORTHWEST; - for (int i = 0; i < tags.length; i++) { JCheckBox box = new JCheckBox(tags[i]); gc.gridy = i; @@ -115,8 +207,8 @@ public class JavaDocLocalInspection extends JavaDocLocalInspectionBase { private class MyChangeListener implements ChangeListener { private final JCheckBox myCheckBox; - private final Options myOptions; - private final String myTagName; + private final Options myOptions; + private final String myTagName; public MyChangeListener(JCheckBox checkBox, Options options, String tagName) { myCheckBox = checkBox; @@ -136,144 +228,196 @@ public class JavaDocLocalInspection extends JavaDocLocalInspectionBase { } } } + } - private JPanel createScopePanel(final String[] modifiers, final Options options) { - JPanel panel = new JPanel(new BorderLayout()); - panel.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createTitledBorder( - InspectionsBundle.message("inspection.scope.for.title"), true), - BorderFactory.createEmptyBorder(0, 3, 3, 3))); + /* + * Fixes + */ - final Hashtable sliderLabels = new Hashtable(); - for (int i = 0; i < modifiers.length; i++) { - sliderLabels.put(i + 1, new JLabel(modifiers[i])); - } + private static class AddJavadocFix extends LocalQuickFixAndIntentionActionOnPsiElement { + private final AddJavadocIntention myIntention; - final JSlider slider = new JSlider(SwingConstants.VERTICAL, 1, modifiers.length, 1); - - slider.setLabelTable(sliderLabels); - slider.putClientProperty(UIUtil.JSLIDER_ISFILLED, Boolean.TRUE); - slider.setPreferredSize(JBUI.size(80, 50)); - slider.setPaintLabels(true); - slider.setSnapToTicks(true); - slider.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - int value = slider.getValue(); - options.ACCESS_JAVADOC_REQUIRED_FOR = modifiers[value - 1]; - for (Integer key : sliderLabels.keySet()) { - sliderLabels.get(key).setForeground(key.intValue() <= value ? Color.black : Gray._100); - } - } - }); - - Color fore = Color.black; - for (int i = 0; i < modifiers.length; i++) { - sliderLabels.get(i + 1).setForeground(fore); - - if (modifiers[i].equals(options.ACCESS_JAVADOC_REQUIRED_FOR)) { - slider.setValue(i + 1); - fore = Gray._100; - } - } - - panel.add(slider, BorderLayout.WEST); - - return panel; + public AddJavadocFix(PsiElement nameIdentifier) { + super(nameIdentifier); + myIntention = new AddJavadocIntention(); } - public OptionsPanel() { - super(new GridBagLayout()); - GridBagConstraints gc = new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1, 0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(0,0,0,0),0,0 ); - add(createAdditionalJavadocTagsPanel(), gc); - JTabbedPane tabs = new JBTabbedPane(SwingConstants.BOTTOM); - @NonNls String[] tags = new String[]{"@author", "@version", "@since"}; - tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.package"), createOptionsPanel(new String[]{NONE, PUBLIC}, - tags,PACKAGE_OPTIONS)); - - tags = new String[]{"@author", "@version", "@since", "@param"}; - tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title"), createOptionsPanel(new String[]{NONE, PUBLIC, PACKAGE_LOCAL}, - tags, - TOP_LEVEL_CLASS_OPTIONS)); - tags = new String[]{"@return", "@param", InspectionsBundle.message("inspection.javadoc.throws.or.exception.option")}; - tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.method"), createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, - tags, - METHOD_OPTIONS)); - tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.field"), createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, - null, - FIELD_OPTIONS)); - tabs.add(InspectionsBundle.message("inspection.javadoc.option.tab.title.inner.class"), createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, - null, - INNER_CLASS_OPTIONS)); - add(tabs, gc); - - final JCheckBox checkBox = new JCheckBox(InspectionsBundle.message("inspection.javadoc.option.ignore.deprecated"), - IGNORE_DEPRECATED); - checkBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - IGNORE_DEPRECATED = checkBox.isSelected(); - } - }); - gc.gridwidth = 1; - add(checkBox, gc); - final JCheckBox periodCheckBox = new JCheckBox(InspectionsBundle.message("inspection.javadoc.option.ignore.period"), - IGNORE_JAVADOC_PERIOD); - periodCheckBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - IGNORE_JAVADOC_PERIOD = periodCheckBox.isSelected(); - } - }); - add(periodCheckBox, gc); - - final JCheckBox ignoreDuplicateThrowsCheckBox = new JCheckBox("Ignore duplicate throws tag", - getIgnoreDuplicatedThrows()); - ignoreDuplicateThrowsCheckBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setIgnoreDuplicatedThrows(ignoreDuplicateThrowsCheckBox.isSelected()); - } - }); - add(ignoreDuplicateThrowsCheckBox, gc); - - final JCheckBox ignorePointToItselfCheckBox = new JCheckBox("Ignore javadoc pointing to itself", IGNORE_POINT_TO_ITSELF); - ignorePointToItselfCheckBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - IGNORE_POINT_TO_ITSELF = ignorePointToItselfCheckBox.isSelected(); - } - }); - add(ignorePointToItselfCheckBox, gc); - final JCheckBox ignoreSimpleAccessorsCheckBox = new JCheckBox("Ignore simple property accessors", myIgnoreSimpleAccessors); - ignoreSimpleAccessorsCheckBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - myIgnoreSimpleAccessors = ignoreSimpleAccessorsCheckBox.isSelected(); - } - }); - add(ignoreSimpleAccessorsCheckBox, gc); + @Override + public void invoke(@NotNull Project project, + @NotNull PsiFile file, + @Nullable("is null when called from inspection") Editor editor, + @NotNull PsiElement startElement, + @NotNull PsiElement endElement) { + myIntention.invoke(project, editor, startElement); } - public FieldPanel createAdditionalJavadocTagsPanel(){ - FieldPanel additionalTagsPanel = new FieldPanel(InspectionsBundle.message("inspection.javadoc.label.text"), InspectionsBundle.message("inspection.javadoc.dialog.title"), null, null); - additionalTagsPanel.setPreferredSize(new Dimension(150, additionalTagsPanel.getPreferredSize().height)); - additionalTagsPanel.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { - @Override - protected void textChanged(DocumentEvent e) { - final Document document = e.getDocument(); - try { - final String text = document.getText(0, document.getLength()); - if (text != null) { - myAdditionalJavadocTags = text.trim(); - } - } - catch (BadLocationException e1) { - LOG.error(e1); - } - } - }); - additionalTagsPanel.setText(myAdditionalJavadocTags); - return additionalTagsPanel; + @NotNull + @Override + public String getText() { + //noinspection DialogTitleCapitalization + return myIntention.getText(); + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return myIntention.getFamilyName(); } } -} + + private static class AddMissingTagFix implements LocalQuickFix { + private final String myTag; + private final String myValue; + + public AddMissingTagFix(@NotNull String tag, @NotNull String value) { + myTag = tag; + myValue = value; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiDocComment docComment = PsiTreeUtil.getParentOfType(descriptor.getEndElement(), PsiDocComment.class); + if (docComment != null) { + if (!FileModificationService.getInstance().preparePsiElementsForWrite(docComment)) return; + + PsiDocTag tag = JavaPsiFacade.getInstance(project).getElementFactory().createDocTagFromText("@" + myTag + " " + myValue); + + PsiElement addedTag; + PsiElement anchor = getAnchor(descriptor); + if (anchor != null) { + addedTag = docComment.addBefore(tag, anchor); + } + else { + addedTag = docComment.add(tag); + } + moveCaretAfter(addedTag); + } + } + + @Nullable + protected PsiElement getAnchor(ProblemDescriptor descriptor) { + return null; + } + + private static void moveCaretAfter(PsiElement newCaretPosition) { + PsiElement sibling = newCaretPosition.getNextSibling(); + if (sibling != null) { + ((Navigatable)sibling).navigate(true); + } + } + + @Override + @NotNull + public String getName() { + return InspectionsBundle.message("inspection.javadoc.problem.add.tag", myTag, myValue); + } + + @Override + @NotNull + public String getFamilyName() { + //noinspection DialogTitleCapitalization + return InspectionsBundle.message("inspection.javadoc.problem.add.tag.family"); + } + } + + private static class AddMissingParamTagFix extends AddMissingTagFix { + private final String myName; + + public AddMissingParamTagFix(String name) { + super("param", name); + myName = name; + } + + @Override + @Nullable + protected PsiElement getAnchor(ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + PsiElement parent = element == null ? null : element.getParent(); + if (!(parent instanceof PsiDocComment)) return null; + final PsiDocComment docComment = (PsiDocComment)parent; + final PsiDocCommentOwner owner = docComment.getOwner(); + if (!(owner instanceof PsiMethod)) return null; + PsiParameter[] parameters = ((PsiMethod)owner).getParameterList().getParameters(); + PsiParameter myParam = ContainerUtil.find(parameters, new Condition() { + @Override + public boolean value(PsiParameter psiParameter) { + return myName.equals(psiParameter.getName()); + } + }); + if (myParam == null) return null; + + PsiDocTag[] tags = docComment.findTagsByName("param"); + if (tags.length == 0) { //insert as first tag or append to description + tags = docComment.getTags(); + if (tags.length == 0) return null; + return tags[0]; + } + + PsiParameter nextParam = PsiTreeUtil.getNextSiblingOfType(myParam, PsiParameter.class); + while (nextParam != null) { + for (PsiDocTag tag : tags) { + if (matches(nextParam, tag)) { + return tag; + } + } + nextParam = PsiTreeUtil.getNextSiblingOfType(nextParam, PsiParameter.class); + } + + PsiParameter prevParam = PsiTreeUtil.getPrevSiblingOfType(myParam, PsiParameter.class); + while (prevParam != null) { + for (PsiDocTag tag : tags) { + if (matches(prevParam, tag)) { + return PsiTreeUtil.getNextSiblingOfType(tag, PsiDocTag.class); + } + } + prevParam = PsiTreeUtil.getPrevSiblingOfType(prevParam, PsiParameter.class); + } + + return null; + } + + private static boolean matches(PsiParameter param, PsiDocTag tag) { + PsiDocTagValue valueElement = tag.getValueElement(); + String name = param.getName(); + return valueElement != null && name != null && valueElement.getText().trim().startsWith(name); + } + + @Override + @NotNull + public String getName() { + //noinspection DialogTitleCapitalization + return InspectionsBundle.message("inspection.javadoc.problem.add.param.tag", myName); + } + } + + private static class AddUnknownTagToCustoms implements LocalQuickFix { + private final JavaDocLocalInspectionBase myInspection; + private final String myTag; + + public AddUnknownTagToCustoms(@NotNull JavaDocLocalInspectionBase inspection, @NotNull String tag) { + myInspection = inspection; + myTag = tag; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + myInspection.registerAdditionalTag(myTag); + InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); + InspectionProfileManager.getInstance().fireProfileChanged(profile); + } + + @Override + @NotNull + public String getName() { + return QuickFixBundle.message("add.docTag.to.custom.tags", myTag); + } + + @Override + @NotNull + public String getFamilyName() { + //noinspection DialogTitleCapitalization + return QuickFixBundle.message("fix.javadoc.family"); + } + } +} \ No newline at end of file diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 69cd502c5e81..6b73096a1039 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -270,16 +270,14 @@ inspection.javadoc.required.tags.option.title=Required Tags inspection.javadoc.problem.descriptor=Required Javadoc is absent inspection.javadoc.problem.descriptor1=Period in the documentation is missing. The period is used by the Javadoc tool to generate the comment for the overview page inspection.javadoc.problem.missing.tag=Required tag {0} is missing -inspection.javadoc.problem.missing.author.description=Author is missing after @author tag -inspection.javadoc.problem.missing.version.description=Version is missing after @version tag -inspection.javadoc.problem.missing.since.description=Since is missing after @since tag +inspection.javadoc.problem.missing.tag.description={0} is missing after @{1} tag inspection.javadoc.problem.cannot.resolve=Cannot resolve symbol {0} inspection.javadoc.problem.name.expected=Name expected inspection.javadoc.problem.wrong.tag=Wrong tag {0} inspection.javadoc.problem.disallowed.tag=Tag ''{0}'' is not allowed here inspection.javadoc.method.problem.descriptor=Description is missing in {0} tag for parameter {1} inspection.javadoc.method.problem.missing.tag.description={0} tag description is missing -inspection.javadoc.method.problem.missing.param.tag=Required tag {0} is missing for parameter {1} +inspection.javadoc.method.problem.missing.param.tag=Required tag @param is missing for parameter {0} inspection.javadoc.problem.duplicate.param=Duplicate @param tag for parameter ''{0}'' inspection.javadoc.problem.duplicate.throws=Duplicate @throws or @exception tag for exception ''{0}'' inspection.javadoc.problem.duplicate.tag=Duplicate @{0} tag @@ -688,6 +686,8 @@ detach.library.quickfix.name=Detach library detach.library.roots.quickfix.name=Detach unused library roots inspection.javadoc.problem.pointing.to.itself=Javadoc pointing to itself +inspection.illegal.character=Illegal character + inspection.redirect.template=Injected element has problem: {0} (in {3}). nothing.found=Nothing found special.annotations.list.annotation.pattern=Add Annotations Pattern