From fe84aaab31f54650010c3a6f07fce13bfaf98308 Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 30 Oct 2025 00:08:27 +0300 Subject: [PATCH 1/2] Refactoring JavaDocLocalInspection. --- .../javaDoc/JavaDocLocalInspection.java | 2391 +++++++++-------- .../javaDoc/JavaDocNotNecessaryFilter.java | 6 +- 2 files changed, 1231 insertions(+), 1166 deletions(-) diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInspection/javaDoc/JavaDocLocalInspection.java b/plugin/src/main/java/com/intellij/java/impl/codeInspection/javaDoc/JavaDocLocalInspection.java index d2aef8b7da..91a728ff17 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInspection/javaDoc/JavaDocLocalInspection.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInspection/javaDoc/JavaDocLocalInspection.java @@ -13,14 +13,13 @@ import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PropertyUtil; import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; -import consulo.application.Application; import consulo.codeEditor.Editor; import consulo.codeEditor.ScrollType; import consulo.fileEditor.FileEditorManager; import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.ast.ASTNode; -import consulo.language.editor.inspection.InspectionsBundle; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.editor.inspection.ProblemHighlightType; @@ -47,7 +46,6 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import org.jdom.Element; -import org.jetbrains.annotations.NonNls; import javax.swing.*; import javax.swing.event.ChangeEvent; @@ -56,1311 +54,1378 @@ import javax.swing.text.BadLocationException; import javax.swing.text.Document; import java.awt.*; +import java.util.List; import java.util.*; @ExtensionImpl public class JavaDocLocalInspection extends BaseLocalInspectionTool { - @NonNls private static final String NONE = "none"; - @NonNls private static final String PUBLIC = "public"; - @NonNls private static final String PROTECTED = "protected"; - @NonNls private static final String PACKAGE_LOCAL = "package"; - @NonNls private static final String PRIVATE = "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"); - } - - private static final String IGNORE_ACCESSORS_ATTR_NAME = "IGNORE_ACCESSORS"; - - public static class Options implements JDOMExternalizable { - @NonNls public String ACCESS_JAVADOC_REQUIRED_FOR = NONE; - @NonNls 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; - } + private static final String NONE = "none"; + private static final String PUBLIC = "public"; + private static final String PROTECTED = "protected"; + private static final String PACKAGE_LOCAL = "package"; + private static final String PRIVATE = "private"; + private static final Set ourUniqueTags = new HashSet<>(); + public static final String SHORT_NAME = "JavaDoc"; - @Override - public void readExternal(Element element) throws InvalidDataException { - DefaultJDOMExternalizer.readExternal(this, element); + static { + ourUniqueTags.add("return"); + ourUniqueTags.add("deprecated"); + ourUniqueTags.add("serial"); + ourUniqueTags.add("serialData"); } - @Override - public void writeExternal(Element element) throws WriteExternalException { - DefaultJDOMExternalizer.writeExternal(this, element); - } - } + private static final String IGNORE_ACCESSORS_ATTR_NAME = "IGNORE_ACCESSORS"; - @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", ""); - public boolean IGNORE_DEPRECATED = false; - public boolean IGNORE_JAVADOC_PERIOD = true; - public boolean IGNORE_DUPLICATED_THROWS = false; - public boolean IGNORE_POINT_TO_ITSELF = false; - public String myAdditionalJavadocTags = ""; + public static class Options implements JDOMExternalizable { + public String ACCESS_JAVADOC_REQUIRED_FOR = NONE; + public String REQUIRED_TAGS = ""; - private boolean myIgnoreEmptyDescriptions = false; - private boolean myIgnoreSimpleAccessors = false; + public Options() { + } - public void setIgnoreSimpleAccessors(boolean ignoreSimpleAccessors) { - myIgnoreSimpleAccessors = ignoreSimpleAccessors; - } + public Options(String ACCESS_JAVADOC_REQUIRED_FOR, String REQUIRED_TAGS) { + this.ACCESS_JAVADOC_REQUIRED_FOR = ACCESS_JAVADOC_REQUIRED_FOR; + this.REQUIRED_TAGS = REQUIRED_TAGS; + } - private static final Logger LOG = Logger.getInstance(JavaDocLocalInspection.class); + @Override + public void readExternal(Element element) throws InvalidDataException { + DefaultJDOMExternalizer.readExternal(this, element); + } - private class OptionsPanel extends JPanel { - private JPanel createOptionsPanel(String[] modifiers, String[] tags, Options options) { - JPanel pane = new JPanel(new GridLayout(1, tags == null ? 1 : 2)); + @Override + public void writeExternal(Element element) throws WriteExternalException { + DefaultJDOMExternalizer.writeExternal(this, element); + } + } - pane.add(createScopePanel(modifiers, options)); - if (tags != null) { - pane.add(createTagsPanel(tags, options)); - } + 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; + public boolean IGNORE_DUPLICATED_THROWS = false; + public boolean IGNORE_POINT_TO_ITSELF = false; + public String myAdditionalJavadocTags = ""; - pane.validate(); + private boolean myIgnoreEmptyDescriptions = false; + private boolean myIgnoreSimpleAccessors = false; - return pane; + public void setIgnoreSimpleAccessors(boolean ignoreSimpleAccessors) { + myIgnoreSimpleAccessors = ignoreSimpleAccessors; } - private JPanel createTagsPanel(String[] tags, Options options) { - JPanel panel = new JPanel(new GridBagLayout()); - panel.setBorder(BorderFactory.createCompoundBorder( - IdeBorderFactory.createTitledBorder(InspectionLocalize.inspectionJavadocRequiredTagsOptionTitle().get(), true), - BorderFactory.createEmptyBorder(0, 3, 3, 3) - )); - - GridBagConstraints gc = new GridBagConstraints(); - gc.weightx = 1; - gc.weighty = 0; - 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; - if (i == tags.length - 1) gc.weighty = 1; - panel.add(box, gc); - box.setSelected(isTagRequired(options, tags[i])); - box.addChangeListener(new MyChangeListener(box, options, tags[i])); - } - - return panel; - } + private static final Logger LOG = Logger.getInstance(JavaDocLocalInspection.class); - private class MyChangeListener implements ChangeListener { - private final JCheckBox myCheckBox; - private final Options myOptions; - private final String myTagName; + private class OptionsPanel extends JPanel { + private JPanel createOptionsPanel(String[] modifiers, String[] tags, Options options) { + JPanel pane = new JPanel(new GridLayout(1, tags == null ? 1 : 2)); - public MyChangeListener(JCheckBox checkBox, Options options, String tagName) { - myCheckBox = checkBox; - myOptions = options; - myTagName = tagName; - } + pane.add(createScopePanel(modifiers, options)); + if (tags != null) { + pane.add(createTagsPanel(tags, options)); + } - @Override - public void stateChanged(ChangeEvent e) { - if (myCheckBox.isSelected()) { - if (!isTagRequired(myOptions, myTagName)) { - myOptions.REQUIRED_TAGS += myTagName; - } - } - else { - myOptions.REQUIRED_TAGS = myOptions.REQUIRED_TAGS.replaceAll(myTagName, ""); + pane.validate(); + + return pane; } - } - } - private JPanel createScopePanel(final String[] modifiers, final Options options) { - JPanel panel = new JPanel(new BorderLayout()); - panel.setBorder(BorderFactory.createCompoundBorder( - IdeBorderFactory.createTitledBorder(InspectionLocalize.inspectionScopeForTitle().get(), true), - BorderFactory.createEmptyBorder(0, 3, 3, 3) - )); + private JPanel createTagsPanel(String[] tags, Options options) { + JPanel panel = new JPanel(new GridBagLayout()); + panel.setBorder(BorderFactory.createCompoundBorder( + IdeBorderFactory.createTitledBorder(InspectionLocalize.inspectionJavadocRequiredTagsOptionTitle().get(), true), + BorderFactory.createEmptyBorder(0, 3, 3, 3) + )); + + GridBagConstraints gc = new GridBagConstraints(); + gc.weightx = 1; + gc.weighty = 0; + gc.fill = GridBagConstraints.HORIZONTAL; + gc.anchor = GridBagConstraints.NORTHWEST; - final Hashtable sliderLabels = new Hashtable<>(); - for (int i = 0; i < modifiers.length; i++) { - sliderLabels.put(i + 1, new JLabel(modifiers[i])); - } - final JSlider slider = new JSlider(SwingConstants.VERTICAL, 1, modifiers.length, 1); + for (int i = 0; i < tags.length; i++) { + JCheckBox box = new JCheckBox(tags[i]); + gc.gridy = i; + if (i == tags.length - 1) { + gc.weighty = 1; + } + panel.add(box, gc); + box.setSelected(isTagRequired(options, tags[i])); + box.addChangeListener(new MyChangeListener(box, options, tags[i])); + } - slider.setLabelTable(sliderLabels); - slider.putClientProperty(UIUtil.JSLIDER_ISFILLED, Boolean.TRUE); - slider.setPreferredSize(new Dimension(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 <= value ? Color.black : Gray._100); + return panel; } - }); - Color fore = Color.black; - for (int i = 0; i < modifiers.length; i++) { - sliderLabels.get(i + 1).setForeground(fore); + private class MyChangeListener implements ChangeListener { + private final JCheckBox myCheckBox; + private final Options myOptions; + private final String myTagName; - if (modifiers[i].equals(options.ACCESS_JAVADOC_REQUIRED_FOR)) { - slider.setValue(i + 1); - fore = Gray._100; + public MyChangeListener(JCheckBox checkBox, Options options, String tagName) { + myCheckBox = checkBox; + myOptions = options; + myTagName = tagName; + } + + @Override + public void stateChanged(ChangeEvent e) { + if (myCheckBox.isSelected()) { + if (!isTagRequired(myOptions, myTagName)) { + myOptions.REQUIRED_TAGS += myTagName; + } + } + else { + myOptions.REQUIRED_TAGS = myOptions.REQUIRED_TAGS.replaceAll(myTagName, ""); + } + } } - } - panel.add(slider, BorderLayout.WEST); + private JPanel createScopePanel(String[] modifiers, Options options) { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createCompoundBorder( + IdeBorderFactory.createTitledBorder(InspectionLocalize.inspectionScopeForTitle().get(), true), + BorderFactory.createEmptyBorder(0, 3, 3, 3) + )); - return panel; - } + Hashtable sliderLabels = new Hashtable<>(); + for (int i = 0; i < modifiers.length; i++) { + sliderLabels.put(i + 1, new JLabel(modifiers[i])); + } - public OptionsPanel() { - super(new GridBagLayout()); - GridBagConstraints gc = new GridBagConstraints( - 0, GridBagConstraints.RELATIVE, 2, 1, 1, 0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, - JBUI.emptyInsets(),0,0 - ); - add(createAdditionalJavadocTagsPanel(), gc); - JTabbedPane tabs = new JBTabbedPane(SwingConstants.BOTTOM); - @NonNls String[] tags = new String[]{"@author", "@version", "@since", "@param"}; - tabs.add( - InspectionLocalize.inspectionJavadocOptionTabTitle().get(), - createOptionsPanel(new String[]{NONE, PUBLIC, PACKAGE_LOCAL}, tags, TOP_LEVEL_CLASS_OPTIONS) - ); - tags = new String[]{"@return", "@param", InspectionLocalize.inspectionJavadocThrowsOrExceptionOption().get()}; - tabs.add( - InspectionLocalize.inspectionJavadocOptionTabTitleMethod().get(), - createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, tags, METHOD_OPTIONS) - ); - tabs.add( - InspectionLocalize.inspectionJavadocOptionTabTitleField().get(), - createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, null, FIELD_OPTIONS) - ); - tabs.add( - InspectionLocalize.inspectionJavadocOptionTabTitleInnerClass().get(), - createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, null, INNER_CLASS_OPTIONS) - ); - add(tabs, gc); - - final JCheckBox checkBox = new JCheckBox(InspectionLocalize.inspectionJavadocOptionIgnoreDeprecated().get(), IGNORE_DEPRECATED); - checkBox.addActionListener(e -> IGNORE_DEPRECATED = checkBox.isSelected()); - gc.gridwidth = 1; - add(checkBox, gc); - final JCheckBox periodCheckBox = new JCheckBox( - InspectionLocalize.inspectionJavadocOptionIgnorePeriod().get(), - IGNORE_JAVADOC_PERIOD - ); - periodCheckBox.addActionListener(e -> IGNORE_JAVADOC_PERIOD = periodCheckBox.isSelected()); - add(periodCheckBox, gc); - - final JCheckBox ignoreDuplicateThrowsCheckBox = new JCheckBox("Ignore duplicate throws tag", IGNORE_DUPLICATED_THROWS); - ignoreDuplicateThrowsCheckBox.addActionListener(e -> IGNORE_DUPLICATED_THROWS = ignoreDuplicateThrowsCheckBox.isSelected()); - add(ignoreDuplicateThrowsCheckBox, gc); - - final 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); - final JCheckBox ignoreSimpleAccessorsCheckBox = new JCheckBox("Ignore simple property accessors", myIgnoreSimpleAccessors); - ignoreSimpleAccessorsCheckBox.addActionListener(e -> myIgnoreSimpleAccessors = ignoreSimpleAccessorsCheckBox.isSelected()); - add(ignoreSimpleAccessorsCheckBox, gc); - } + JSlider slider = new JSlider(SwingConstants.VERTICAL, 1, modifiers.length, 1); + + slider.setLabelTable(sliderLabels); + slider.putClientProperty(UIUtil.JSLIDER_ISFILLED, Boolean.TRUE); + slider.setPreferredSize(new Dimension(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 <= value ? Color.black : Gray._100); + } + }); - public FieldPanel createAdditionalJavadocTagsPanel(){ - FieldPanel additionalTagsPanel = new FieldPanel( - InspectionLocalize.inspectionJavadocLabelText().get(), - InspectionLocalize.inspectionJavadocDialogTitle().get(), - 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(); + 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; + } } - } - catch (BadLocationException e1) { - LOG.error(e1); - } - } - }); - additionalTagsPanel.setText(myAdditionalJavadocTags); - return additionalTagsPanel; + + panel.add(slider, BorderLayout.WEST); + + return panel; + } + + public OptionsPanel() { + super(new GridBagLayout()); + GridBagConstraints gc = new GridBagConstraints( + 0, GridBagConstraints.RELATIVE, 2, 1, 1, 0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, + JBUI.emptyInsets(), 0, 0 + ); + add(createAdditionalJavadocTagsPanel(), gc); + JTabbedPane tabs = new JBTabbedPane(SwingConstants.BOTTOM); + String[] tags = new String[]{"@author", "@version", "@since", "@param"}; + tabs.add( + InspectionLocalize.inspectionJavadocOptionTabTitle().get(), + createOptionsPanel(new String[]{NONE, PUBLIC, PACKAGE_LOCAL}, tags, TOP_LEVEL_CLASS_OPTIONS) + ); + tags = new String[]{"@return", "@param", InspectionLocalize.inspectionJavadocThrowsOrExceptionOption().get()}; + tabs.add( + InspectionLocalize.inspectionJavadocOptionTabTitleMethod().get(), + createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, tags, METHOD_OPTIONS) + ); + tabs.add( + InspectionLocalize.inspectionJavadocOptionTabTitleField().get(), + createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, null, FIELD_OPTIONS) + ); + tabs.add( + InspectionLocalize.inspectionJavadocOptionTabTitleInnerClass().get(), + createOptionsPanel(new String[]{NONE, PUBLIC, PROTECTED, PACKAGE_LOCAL, PRIVATE}, null, INNER_CLASS_OPTIONS) + ); + add(tabs, gc); + + JCheckBox checkBox = new JCheckBox(InspectionLocalize.inspectionJavadocOptionIgnoreDeprecated().get(), IGNORE_DEPRECATED); + checkBox.addActionListener(e -> IGNORE_DEPRECATED = checkBox.isSelected()); + gc.gridwidth = 1; + add(checkBox, gc); + JCheckBox periodCheckBox = new JCheckBox( + InspectionLocalize.inspectionJavadocOptionIgnorePeriod().get(), + IGNORE_JAVADOC_PERIOD + ); + periodCheckBox.addActionListener(e -> IGNORE_JAVADOC_PERIOD = periodCheckBox.isSelected()); + add(periodCheckBox, gc); + + JCheckBox ignoreDuplicateThrowsCheckBox = new JCheckBox("Ignore duplicate throws tag", IGNORE_DUPLICATED_THROWS); + ignoreDuplicateThrowsCheckBox.addActionListener(e -> IGNORE_DUPLICATED_THROWS = 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", myIgnoreSimpleAccessors); + ignoreSimpleAccessorsCheckBox.addActionListener(e -> myIgnoreSimpleAccessors = ignoreSimpleAccessorsCheckBox.isSelected()); + add(ignoreSimpleAccessorsCheckBox, gc); + } + + public FieldPanel createAdditionalJavadocTagsPanel() { + FieldPanel additionalTagsPanel = new FieldPanel( + InspectionLocalize.inspectionJavadocLabelText().get(), + InspectionLocalize.inspectionJavadocDialogTitle().get(), + 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 e1) { + LOG.error(e1); + } + } + }); + additionalTagsPanel.setText(myAdditionalJavadocTags); + return additionalTagsPanel; + } } - } - - @Override - public JComponent createOptionsPanel() { - return new OptionsPanel(); - } - - @Override - public void writeSettings(@Nonnull 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); + + @Override + public JComponent createOptionsPanel() { + return new OptionsPanel(); } - } - - @Override - public void readSettings(@Nonnull Element node) throws InvalidDataException { - super.readSettings(node); - final Element ignoreAccessorsTag = node.getChild(IGNORE_ACCESSORS_ATTR_NAME); - if (ignoreAccessorsTag != null) { - myIgnoreSimpleAccessors = Boolean.parseBoolean(ignoreAccessorsTag.getAttributeValue("value")); + + @Override + public void writeSettings(@Nonnull Element node) throws WriteExternalException { + super.writeSettings(node); + if (myIgnoreSimpleAccessors) { + Element option = new Element(IGNORE_ACCESSORS_ATTR_NAME); + option.setAttribute("value", String.valueOf(true)); + node.addContent(option); + } } - } - - private static ProblemDescriptor createDescriptor( - @Nonnull PsiElement element, - String template, - InspectionManager manager, - boolean onTheFly - ) { - return manager.createProblemDescriptor(element, template, onTheFly, null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - } - - private static ProblemDescriptor createDescriptor( - @Nonnull PsiElement element, - String template, - @Nonnull 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 String tag, String value) { - myTag = tag; - myValue = value; + + @Override + public void readSettings(@Nonnull Element node) throws InvalidDataException { + super.readSettings(node); + Element ignoreAccessorsTag = node.getChild(IGNORE_ACCESSORS_ATTR_NAME); + if (ignoreAccessorsTag != null) { + myIgnoreSimpleAccessors = Boolean.parseBoolean(ignoreAccessorsTag.getAttributeValue("value")); + } } - public AddMissingTagFix(String tag) { - this(tag, ""); + + private static ProblemDescriptor createDescriptor( + @Nonnull PsiElement element, + String template, + InspectionManager manager, + boolean onTheFly + ) { + return manager.createProblemDescriptor(element, template, onTheFly, null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); } - @Override - @Nonnull - public LocalizeValue getName() { - return InspectionLocalize.inspectionJavadocProblemAddTag(myTag, myValue); + private static ProblemDescriptor createDescriptor( + @Nonnull PsiElement element, + String template, + @Nonnull LocalQuickFix fix, + InspectionManager manager, + boolean onTheFly + ) { + return manager.createProblemDescriptor(element, template, fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, onTheFly); } - @Override - @RequiredReadAction - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); - try { - final PsiDocCommentOwner owner = PsiTreeUtil.getParentOfType(descriptor.getEndElement(), PsiDocCommentOwner.class); - if (owner != null) { - if (!CodeInsightUtil.preparePsiElementsForWrite(owner)) return; - final PsiDocComment docComment = owner.getDocComment(); - final PsiDocTag tag = factory.createDocTagFromText("@" + myTag + " " + myValue); - if (docComment != null) { - PsiElement addedTag; - final PsiElement anchor = getAnchor(descriptor); - if (anchor != null) { - addedTag = docComment.addBefore(tag, anchor); + private static class AddMissingTagFix implements LocalQuickFix { + private final String myTag; + private final String myValue; + + public AddMissingTagFix(String tag, String value) { + myTag = tag; + myValue = value; + } + + public AddMissingTagFix(String tag) { + this(tag, ""); + } + + @Override + @Nonnull + public LocalizeValue getName() { + return InspectionLocalize.inspectionJavadocProblemAddTag(myTag, myValue); + } + + @Override + @RequiredWriteAction + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + try { + PsiDocCommentOwner owner = PsiTreeUtil.getParentOfType(descriptor.getEndElement(), PsiDocCommentOwner.class); + if (owner != null) { + if (!CodeInsightUtil.preparePsiElementsForWrite(owner)) { + return; + } + PsiDocComment docComment = owner.getDocComment(); + PsiDocTag tag = factory.createDocTagFromText("@" + myTag + " " + myValue); + if (docComment != null) { + PsiElement addedTag; + PsiElement anchor = getAnchor(descriptor); + if (anchor != null) { + addedTag = docComment.addBefore(tag, anchor); + } + else { + addedTag = docComment.add(tag); + } + moveCaretTo(addedTag); + } + } } - else { - addedTag = docComment.add(tag); + catch (IncorrectOperationException e) { + LOG.error(e); } - moveCaretTo(addedTag); - } } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - @Nullable - protected PsiElement getAnchor(ProblemDescriptor descriptor) { - return null; + @Nullable + protected PsiElement getAnchor(ProblemDescriptor descriptor) { + return null; + } + + @RequiredReadAction + private static void moveCaretTo(PsiElement newCaretPosition) { + Project project = newCaretPosition.getProject(); + PsiFile psiFile = newCaretPosition.getContainingFile(); + Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); + if (editor != null && IJSwingUtilities.hasFocus(editor.getComponent())) { + PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); + if (file == psiFile) { + editor.getCaretModel().moveToOffset(newCaretPosition.getTextRange().getEndOffset()); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + } + } + } } + @Override + @Nullable @RequiredReadAction - private static void moveCaretTo(final PsiElement newCaretPosition) { - Project project = newCaretPosition.getProject(); - final PsiFile psiFile = newCaretPosition.getContainingFile(); - final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); - if (editor != null && IJSwingUtilities.hasFocus(editor.getComponent())) { - final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); - if (file == psiFile) { - editor.getCaretModel().moveToOffset(newCaretPosition.getTextRange().getEndOffset()); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - } - } - } - } - - @Override - @Nullable - @RequiredReadAction - public ProblemDescriptor[] checkClass(@Nonnull PsiClass psiClass, @Nonnull InspectionManager manager, boolean isOnTheFly, Object state) { - if (psiClass instanceof PsiAnonymousClass) return null; - // if (psiClass instanceof JspClass) return null; - if (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); - if (docComment == null) { - return required - ? new ProblemDescriptor[]{createDescriptor(elementToHighlight, - InspectionLocalize.inspectionJavadocProblemDescriptor().get(), manager, isOnTheFly)} - : null; - } + public ProblemDescriptor[] checkClass( + @Nonnull PsiClass psiClass, + @Nonnull InspectionManager manager, + boolean isOnTheFly, + Object state + ) { + if (psiClass instanceof PsiAnonymousClass || psiClass instanceof PsiTypeParameter) { + return null; + } + //if (psiClass instanceof JspClass) { + // return null; + //} + if (IGNORE_DEPRECATED && psiClass.isDeprecated()) { + return null; + } + PsiDocComment docComment = psiClass.getDocComment(); + PsiIdentifier nameIdentifier = psiClass.getNameIdentifier(); + PsiElement elementToHighlight = nameIdentifier != null ? nameIdentifier : psiClass; + boolean required = isJavaDocRequired(psiClass); + if (docComment == null) { + return required + ? new ProblemDescriptor[]{createDescriptor( + elementToHighlight, + InspectionLocalize.inspectionJavadocProblemDescriptor().get(), + manager, + isOnTheFly + )} + : null; + } - PsiDocTag[] tags = docComment.getTags(); - @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(psiClass, tag); - } - - if (someTagsAreRequired) { + PsiDocTag[] tags = docComment.getTags(); + String[] tagsToCheck = {"author", "version", "since"}; + LocalizeValue[] absentDescriptionKeys = { + InspectionLocalize.inspectionJavadocProblemMissingAuthorDescription(), + InspectionLocalize.inspectionJavadocProblemMissingVersionDescription(), + InspectionLocalize.inspectionJavadocProblemMissingSinceDescription() + }; + List 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++) { + String tag = tagsToCheck[i]; + someTagsAreRequired |= isTagRequired[i] = isTagRequired(psiClass, tag); + } + + if (someTagsAreRequired) { + for (PsiDocTag tag : tags) { + String tagName = tag.getName(); + for (int i = 0; i < tagsToCheck.length; i++) { + String tagToCheck = tagsToCheck[i]; + if (tagToCheck.equals(tagName)) { + isTagPresent[i] = true; + } + } + } + } + + for (int i = 0; i < tagsToCheck.length; i++) { + String tagToCheck = tagsToCheck[i]; + if (isTagRequired[i] && !isTagPresent[i]) { + problems.add(createMissingTagDescriptor(elementToHighlight, tagToCheck, manager, isOnTheFly)); + } + } + } + + List tagProblems = getTagValuesProblems(psiClass, tags, manager, isOnTheFly); + if (tagProblems != null) { + problems.addAll(tagProblems); + } + checkForPeriodInDoc(docComment, problems, manager, isOnTheFly); + checkInlineTags( + manager, + problems, + docComment.getDescriptionElements(), + JavadocManager.SERVICE.getInstance(docComment.getProject()), + isOnTheFly + ); + checkForBadCharacters(docComment, problems, manager, isOnTheFly); 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++) { + String tagToCheck = tagsToCheck[i]; + if (tagToCheck.equals(tag.getName()) && extractTagDescription(tag).isEmpty()) { + problems.add(createDescriptor( + elementToHighlight, + absentDescriptionKeys[i].get(), + manager, + isOnTheFly + )); + } } - } } - } - 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)); + checkDuplicateTags(tags, problems, manager, isOnTheFly); + + if (required && isTagRequired(psiClass, "param") && psiClass.hasTypeParameters() && nameIdentifier != null) { + List absentParameters = null; + PsiTypeParameter[] typeParameters = psiClass.getTypeParameters(); + for (PsiTypeParameter typeParameter : typeParameters) { + if (!isFound(tags, typeParameter)) { + if (absentParameters == null) { + absentParameters = new ArrayList<>(1); + } + absentParameters.add(typeParameter); + } + } + if (absentParameters != null) { + for (PsiTypeParameter psiTypeParameter : absentParameters) { + problems.add(createMissingParamTagDescriptor(nameIdentifier, psiTypeParameter, manager, isOnTheFly)); + } + } } - } - } - ArrayList tagProblems = getTagValuesProblems(psiClass, tags, manager, isOnTheFly); - if (tagProblems != null) { - problems.addAll(tagProblems); + return problems.isEmpty() + ? null + : problems.toArray(new ProblemDescriptor[problems.size()]); } - checkForPeriodInDoc(docComment, problems, manager, isOnTheFly); - checkInlineTags(manager, problems, docComment.getDescriptionElements(), - JavadocManager.SERVICE.getInstance(docComment.getProject()), isOnTheFly); - checkForBadCharacters(docComment, problems, 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).length() == 0) { - problems.add(createDescriptor(elementToHighlight, InspectionsBundle.message(absentDescriptionKeys[i]), manager, isOnTheFly)); - } - } + + @RequiredReadAction + private static ProblemDescriptor createMissingParamTagDescriptor( + PsiIdentifier nameIdentifier, + PsiTypeParameter psiTypeParameter, + InspectionManager manager, + boolean isOnTheFly + ) { + LocalizeValue message = InspectionLocalize.inspectionJavadocProblemMissingTag("@param"); + return createDescriptor( + nameIdentifier, + message.get(), + new AddMissingTagFix("param", "<" + psiTypeParameter.getName() + ">"), + manager, + isOnTheFly + ); } - checkDuplicateTags(tags, problems, manager, isOnTheFly); + @Override + @Nullable + @RequiredReadAction + public ProblemDescriptor[] checkField( + @Nonnull PsiField psiField, + @Nonnull InspectionManager manager, + boolean isOnTheFly, + Object state + ) { + if (IGNORE_DEPRECATED && (psiField.isDeprecated() || psiField.getContainingClass().isDeprecated())) { + return null; + } - 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); + PsiDocComment docComment = psiField.getDocComment(); + if (docComment == null) { + return isJavaDocRequired(psiField) + ? new ProblemDescriptor[]{createDescriptor(psiField.getNameIdentifier(), + InspectionLocalize.inspectionJavadocProblemDescriptor().get(), manager, isOnTheFly + )} + : null; } - } - if (absentParameters != null) { - for (PsiTypeParameter psiTypeParameter : absentParameters) { - problems.add(createMissingParamTagDescriptor(nameIdentifier, psiTypeParameter, manager, isOnTheFly)); + + List problems = new ArrayList<>(2); + List 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(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()]); } - return problems.isEmpty() - ? null - : problems.toArray(new ProblemDescriptor[problems.size()]); - } - - @RequiredReadAction - private static ProblemDescriptor createMissingParamTagDescriptor( - final PsiIdentifier nameIdentifier, - final PsiTypeParameter psiTypeParameter, - final InspectionManager manager, boolean isOnTheFly - ) { - LocalizeValue message = InspectionLocalize.inspectionJavadocProblemMissingTag("@param"); - return createDescriptor( - nameIdentifier, - message.get(), - new AddMissingTagFix("param", "<" + psiTypeParameter.getName() + ">"), - manager, - isOnTheFly - ); - } - - @Override - @Nullable - @RequiredReadAction - public ProblemDescriptor[] checkField(@Nonnull PsiField psiField, @Nonnull InspectionManager manager, boolean isOnTheFly, Object state) { - if (IGNORE_DEPRECATED && (psiField.isDeprecated() || psiField.getContainingClass().isDeprecated())) { - return null; - } + @Override + @Nullable + @RequiredReadAction + public ProblemDescriptor[] checkMethod( + @Nonnull PsiMethod method, + @Nonnull InspectionManager manager, + boolean isOnTheFly, + Object state + ) { + //if (psiMethod instanceof JspHolderMethod) return null; + if (IGNORE_DEPRECATED && (method.isDeprecated() || method.getContainingClass().isDeprecated())) { + return null; + } + if (myIgnoreSimpleAccessors && PropertyUtil.isSimplePropertyAccessor(method)) { + return null; + } + PsiDocComment docComment = method.getDocComment(); + PsiMethod[] superMethods = method.findSuperMethods(); + boolean required = isJavaDocRequired(method); + if (docComment == null) { + if (required) { + if (superMethods.length > 0) { + return null; + } + boolean javaDocNotNecessary = method.getApplication().getExtensionPoint(JavaDocNotNecessaryFilter.class) + .anyMatchSafe(filter -> filter.isJavaDocNotNecessary(method)); + if (javaDocNotNecessary) { + return null; + } + PsiIdentifier nameIdentifier = method.getNameIdentifier(); + return nameIdentifier != null + ? new ProblemDescriptor[]{createDescriptor( + nameIdentifier, + InspectionLocalize.inspectionJavadocProblemDescriptor().get(), + manager, + isOnTheFly + )} : null; + } + else { + return null; + } + } - PsiDocComment docComment = psiField.getDocComment(); - if (docComment == null) { - return isJavaDocRequired(psiField) - ? new ProblemDescriptor[]{createDescriptor(psiField.getNameIdentifier(), - InspectionLocalize.inspectionJavadocProblemDescriptor().get(), manager, isOnTheFly)} - : null; - } + PsiElement[] descriptionElements = docComment.getDescriptionElements(); + for (PsiElement descriptionElement : descriptionElements) { + if (descriptionElement instanceof PsiInlineDocTag inlineDocTag && "inheritDoc".equals(inlineDocTag.getName())) { + return null; + } + } - 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(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()]); - } - - @Override - @Nullable - @RequiredReadAction - public ProblemDescriptor[] checkMethod(@Nonnull PsiMethod psiMethod, @Nonnull InspectionManager manager, boolean isOnTheFly, Object state) { - //if (psiMethod instanceof JspHolderMethod) return null; - if (IGNORE_DEPRECATED && (psiMethod.isDeprecated() || psiMethod.getContainingClass().isDeprecated())) { - return null; - } - if (myIgnoreSimpleAccessors && PropertyUtil.isSimplePropertyAccessor(psiMethod)) { - return null; - } - PsiDocComment docComment = psiMethod.getDocComment(); - final PsiMethod[] superMethods = psiMethod.findSuperMethods(); - final boolean required = isJavaDocRequired(psiMethod); - if (docComment == null) { - if (required) { - if (superMethods.length > 0) return null; - for (JavaDocNotNecessaryFilter addin : Application.get().getExtensionList(JavaDocNotNecessaryFilter.class)) { - if (addin.isJavaDocNotNecessary(psiMethod)) return null; - } - if (superMethods.length == 0) { - final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier(); - return nameIdentifier != null - ? new ProblemDescriptor[] { createDescriptor(nameIdentifier, InspectionLocalize.inspectionJavadocProblemDescriptor().get(), manager, isOnTheFly)} : null; - } - else { - return null; - } - } - else { - return null; - } - } + List problems = new ArrayList<>(2); - final PsiElement[] descriptionElements = docComment.getDescriptionElements(); - for (PsiElement descriptionElement : descriptionElements) { - if (descriptionElement instanceof PsiInlineDocTag) { - if ("inheritDoc".equals(((PsiInlineDocTag)descriptionElement).getName())) return null; - } - } + checkInlineTags( + manager, + problems, + descriptionElements, + JavadocManager.SERVICE.getInstance(docComment.getProject()), + isOnTheFly + ); - final ArrayList 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)){ - return null; - } - } + PsiDocTag tagByName = docComment.findTagByName("inheritDoc"); + if (tagByName != null) { + String tagName = tagByName.getName(); + JavadocTagInfo tagInfo = JavadocManager.SERVICE.getInstance(tagByName.getProject()).getTagInfo(tagName); + if (tagInfo != null && tagInfo.isValidInContext(method)) { + return null; + } + } - PsiDocTag[] tags = docComment.getTags(); + PsiDocTag[] tags = docComment.getTags(); + + boolean isReturnRequired = false; + boolean isReturnAbsent = true; + if (superMethods.length == 0 + && !method.isConstructor() + && !PsiType.VOID.equals(method.getReturnType()) + && isTagRequired(method, "return")) { + isReturnRequired = true; + for (PsiDocTag tag : tags) { + if ("return".equals(tag.getName())) { + isReturnAbsent = false; + break; + } + } + } - 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; + List absentParameters = null; + if (required && superMethods.length == 0 && isTagRequired(method, "param")) { + PsiParameter[] params = method.getParameterList().getParameters(); + for (PsiParameter param : params) { + if (!isFound(tags, param)) { + if (absentParameters == null) { + absentParameters = new ArrayList<>(2); + } + absentParameters.add(param); + } + } } - } - } - 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 (required && isReturnRequired && isReturnAbsent) { + PsiIdentifier psiIdentifier = method.getNameIdentifier(); + if (psiIdentifier != null) { + problems.add(createMissingTagDescriptor(psiIdentifier, "return", manager, isOnTheFly)); + } } - } - } - if (required && isReturnRequired && isReturnAbsent) { - final PsiIdentifier psiIdentifier = psiMethod.getNameIdentifier(); - if (psiIdentifier != null) { - problems.add(createMissingTagDescriptor(psiIdentifier, "return", manager, isOnTheFly)); - } - } + if (absentParameters != null) { + for (PsiParameter psiParameter : absentParameters) { + PsiIdentifier nameIdentifier = method.getNameIdentifier(); + if (nameIdentifier != null) { + problems.add(createMissingParamTagDescriptor(nameIdentifier, psiParameter, manager, isOnTheFly)); + } + } + } - if (absentParameters != null) { - for (PsiParameter psiParameter : absentParameters) { - final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier(); - if (nameIdentifier != null) { - problems.add(createMissingParamTagDescriptor(nameIdentifier, psiParameter, manager, isOnTheFly)); + if (!myIgnoreEmptyDescriptions) { + for (PsiDocTag tag : tags) { + if ("param".equals(tag.getName())) { + PsiElement[] dataElements = tag.getDataElements(); + PsiDocTagValue valueElement = tag.getValueElement(); + boolean hasProblemsWithTag = dataElements.length < 2; + if (!hasProblemsWithTag) { + StringBuilder buf = new StringBuilder(); + for (PsiElement element : dataElements) { + if (element != valueElement) { + buf.append(element.getText()); + } + } + hasProblemsWithTag = buf.toString().trim().length() == 0; + } + if (hasProblemsWithTag && valueElement != null) { + problems.add(createDescriptor( + valueElement, + InspectionLocalize.inspectionJavadocMethodProblemMissingTagDescription( + "@param " + valueElement.getText() + "" + ).get(), + manager, + isOnTheFly + )); + } + } + } } - } - } - 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()); - } + if (required && superMethods.length == 0 && isTagRequired(method, "@throws") + && method.getThrowsList().getReferencedTypes().length > 0) { + Map declaredExceptions = new HashMap<>(); + PsiClassType[] classTypes = method.getThrowsList().getReferencedTypes(); + for (PsiClassType classType : classTypes) { + PsiClass psiClass = classType.resolve(); + if (psiClass != null) { + declaredExceptions.put(classType, psiClass); + } } - hasProblemsWithTag = buf.toString().trim().length() == 0; - } - if (hasProblemsWithTag) { - if (valueElement != null) { - problems.add(createDescriptor( - valueElement, - InspectionLocalize.inspectionJavadocMethodProblemMissingTagDescription( - "@param " + valueElement.getText() + "" - ).get(), - manager, - isOnTheFly - )); + processThrowsTags(tags, declaredExceptions, manager, problems, isOnTheFly); + if (!declaredExceptions.isEmpty()) { + for (PsiClassType declaredException : declaredExceptions.keySet()) { + problems.add(createMissingThrowsTagDescriptor(method, manager, declaredException, isOnTheFly)); + } } - } } - } - } - if (required && superMethods.length == 0 && isTagRequired(psiMethod, "@throws") - && psiMethod.getThrowsList().getReferencedTypes().length > 0) { - final Map declaredExceptions = new HashMap<>(); - 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(psiMethod, manager, declaredException, isOnTheFly)); - } - } - } + List tagProblems = getTagValuesProblems(method, tags, manager, isOnTheFly); + if (tagProblems != null) { + problems.addAll(tagProblems); + } - ArrayList tagProblems = getTagValuesProblems(psiMethod, tags, manager, isOnTheFly); - if (tagProblems != null) { - problems.addAll(tagProblems); + checkForPeriodInDoc(docComment, problems, manager, isOnTheFly); + checkForBadCharacters(docComment, problems, manager, isOnTheFly); + for (PsiDocTag tag : tags) { + if ("param".equals(tag.getName())) { + if (extractTagDescription(tag).isEmpty() && tag.getValueElement() instanceof PsiDocParamRef paramRef) { + for (PsiParameter param : method.getParameterList().getParameters()) { + if (paramRef.getReference().isReferenceTo(param)) { + problems.add(createDescriptor( + paramRef, + InspectionLocalize.inspectionJavadocMethodProblemDescriptor( + "@param", + "" + param.getName() + "" + ).get(), + manager, + isOnTheFly + )); + } + } + } + } + else if ("return".equals(tag.getName()) && !myIgnoreEmptyDescriptions && extractTagDescription(tag).length() == 0) { + LocalizeValue message = InspectionLocalize.inspectionJavadocMethodProblemMissingTagDescription("@return"); + ProblemDescriptor descriptor = manager.createProblemDescriptor( + tag.getNameElement(), + message.get(), + (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()]); } - checkForPeriodInDoc(docComment, problems, manager, isOnTheFly); - checkForBadCharacters(docComment, problems, manager, isOnTheFly); - for (PsiDocTag tag : tags) { - if ("param".equals(tag.getName())) { - if (extractTagDescription(tag).length() == 0) { - 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, - InspectionLocalize.inspectionJavadocMethodProblemDescriptor( - "@param", - "" + param.getName() + "" - ).get(), - manager, - isOnTheFly - )); - } + @RequiredReadAction + public static boolean isFound(PsiDocTag[] tags, PsiElement param) { + for (PsiDocTag tag : tags) { + if ("param".equals(tag.getName()) && tag.getValueElement() instanceof PsiDocParamRef paramRef) { + PsiReference psiReference = paramRef.getReference(); + if (psiReference != null && psiReference.isReferenceTo(param)) { + return true; + } } - } - } - } - else if ("return".equals(tag.getName()) && !myIgnoreEmptyDescriptions && extractTagDescription(tag).length() == 0) { - LocalizeValue message = InspectionLocalize.inspectionJavadocMethodProblemMissingTagDescription("@return"); - ProblemDescriptor descriptor = manager.createProblemDescriptor( - tag.getNameElement(), - message.get(), - (LocalQuickFix)null, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - isOnTheFly - ); - problems.add(descriptor); - } + } + return false; } - checkDuplicateTags(tags, problems, manager, isOnTheFly); - - return problems.isEmpty() - ? null - : problems.toArray(new ProblemDescriptor[problems.size()]); - } - - @RequiredReadAction - 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; - } - - @RequiredReadAction - private void processThrowsTags( - final PsiDocTag[] tags, - final Map declaredExceptions, - final InspectionManager mananger, - @Nonnull final ArrayList 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 exceptionClass){ - 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).length() == 0) { - problems.add(createDescriptor( - tag.getNameElement(), - InspectionLocalize.inspectionJavadocMethodProblemMissingTagDescription("" + tag.getName() + "").get(), - mananger, - isOnTheFly - )); - } - it.remove(); + @RequiredReadAction + private void processThrowsTags( + PsiDocTag[] tags, + Map declaredExceptions, + InspectionManager manager, + @Nonnull List problems, + boolean isOnTheFly + ) { + for (PsiDocTag tag : tags) { + if ("throws".equals(tag.getName()) || "exception".equals(tag.getName())) { + PsiDocTagValue value = tag.getValueElement(); + if (value == null) { + continue; + } + PsiElement firstChild = value.getFirstChild(); + if (!(firstChild != null && firstChild.getFirstChild() instanceof PsiJavaCodeReferenceElement ref)) { + continue; + } + PsiElement element = ref.resolve(); + if (element instanceof PsiClass exceptionClass) { + for (Iterator it = declaredExceptions.keySet().iterator(); it.hasNext(); ) { + PsiClassType classType = it.next(); + PsiClass psiClass = declaredExceptions.get(classType); + if (InheritanceUtil.isInheritorOrSelf(exceptionClass, psiClass, true)) { + if (!myIgnoreEmptyDescriptions && extractThrowsTagDescription(tag).isEmpty()) { + problems.add(createDescriptor( + tag.getNameElement(), + InspectionLocalize.inspectionJavadocMethodProblemMissingTagDescription( + "" + tag.getName() + "" + ).get(), + manager, + isOnTheFly + )); + } + it.remove(); + } + } + } } - } } - } - } - } - - @Nullable - private static ProblemDescriptor createMissingThrowsTagDescriptor( - final PsiMethod method, - final InspectionManager manager, - final PsiClassType exceptionClassType, - boolean isOnTheFly - ) { - @NonNls String tag = "throws"; - LocalizeValue message = - InspectionLocalize.inspectionJavadocProblemMissingTag("@" + tag + " " + exceptionClassType.getCanonicalText()); - final String firstDeclaredException = exceptionClassType.getCanonicalText(); - final PsiIdentifier nameIdentifier = method.getNameIdentifier(); - return nameIdentifier != null ? createDescriptor( - nameIdentifier, - message.get(), - new AddMissingTagFix(tag, firstDeclaredException), - manager, - isOnTheFly - ) : null; - } - - private static ProblemDescriptor createMissingTagDescriptor( - PsiElement elementToHighlight, - @NonNls String tag, - final InspectionManager manager, - boolean isOnTheFly - ) { - LocalizeValue message = InspectionLocalize.inspectionJavadocProblemMissingTag("@" + tag + ""); - return createDescriptor(elementToHighlight, message.get(), new AddMissingTagFix(tag), manager, isOnTheFly); - } - - private static ProblemDescriptor createMissingParamTagDescriptor( - PsiElement elementToHighlight, - PsiParameter param, - final InspectionManager manager, - boolean isOnTheFly - ) { - LocalizeValue message = InspectionLocalize.inspectionJavadocMethodProblemMissingParamTag( - "@param", - "" + param.getName() + "" - ); - return createDescriptor(elementToHighlight, message.get(), 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 - @Nonnull - public LocalizeValue getName() { - return InspectionLocalize.inspectionJavadocProblemAddParamTag(myName); + @Nullable + private static ProblemDescriptor createMissingThrowsTagDescriptor( + PsiMethod method, + InspectionManager manager, + PsiClassType exceptionClassType, + boolean isOnTheFly + ) { + String tag = "throws"; + LocalizeValue message = + InspectionLocalize.inspectionJavadocProblemMissingTag("@" + tag + " " + exceptionClassType.getCanonicalText()); + String firstDeclaredException = exceptionClassType.getCanonicalText(); + PsiIdentifier nameIdentifier = method.getNameIdentifier(); + return nameIdentifier != null ? createDescriptor( + nameIdentifier, + message.get(), + new AddMissingTagFix(tag, firstDeclaredException), + manager, + isOnTheFly + ) : null; + } + + private static ProblemDescriptor createMissingTagDescriptor( + PsiElement elementToHighlight, + String tag, + InspectionManager manager, + boolean isOnTheFly + ) { + LocalizeValue message = InspectionLocalize.inspectionJavadocProblemMissingTag("@" + tag + ""); + return createDescriptor(elementToHighlight, message.get(), new AddMissingTagFix(tag), manager, isOnTheFly); + } + + private static ProblemDescriptor createMissingParamTagDescriptor( + PsiElement elementToHighlight, + PsiParameter param, + InspectionManager manager, + boolean isOnTheFly + ) { + LocalizeValue message = InspectionLocalize.inspectionJavadocMethodProblemMissingParamTag( + "@param", + "" + param.getName() + "" + ); + return createDescriptor(elementToHighlight, message.get(), new AddMissingParamTagFix(param.getName()), manager, isOnTheFly); } - @Override - @Nullable - @RequiredReadAction - protected PsiElement getAnchor(ProblemDescriptor descriptor) { - PsiElement element = descriptor.getPsiElement(); - PsiElement parent = element == null ? null : element.getParent(); - if (!(parent instanceof PsiMethod)) return null; - PsiParameter[] parameters = ((PsiMethod)parent).getParameterList().getParameters(); - PsiParameter myParam = ContainerUtil.find(parameters, psiParameter -> myName.equals(psiParameter.getName())); - if (myParam == null) return null; - - final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(myParam, PsiMethod.class); - LOG.assertTrue(psiMethod != null); - final PsiDocComment docComment = psiMethod.getDocComment(); - LOG.assertTrue(docComment != 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; - } + private static class AddMissingParamTagFix extends AddMissingTagFix { + private final String myName; + + public AddMissingParamTagFix(String name) { + super("param", name); + myName = name; } - 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); - } + @Nonnull + @Override + public LocalizeValue getName() { + return InspectionLocalize.inspectionJavadocProblemAddParamTag(myName); + } + + @Nullable + @Override + @RequiredReadAction + protected PsiElement getAnchor(ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + PsiElement parent = element == null ? null : element.getParent(); + if (!(parent instanceof PsiMethod method)) { + return null; + } + PsiParameter[] parameters = method.getParameterList().getParameters(); + PsiParameter myParam = ContainerUtil.find(parameters, psiParameter -> myName.equals(psiParameter.getName())); + if (myParam == null) { + return null; + } + + PsiMethod psiMethod = PsiTreeUtil.getParentOfType(myParam, PsiMethod.class); + LOG.assertTrue(psiMethod != null); + PsiDocComment docComment = psiMethod.getDocComment(); + LOG.assertTrue(docComment != 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; } - prevParam = PsiTreeUtil.getPrevSiblingOfType(prevParam, PsiParameter.class); - } - return null; + @RequiredReadAction + private static boolean matches(PsiParameter param, PsiDocTag tag) { + PsiDocTagValue valueElement = tag.getValueElement(); + return valueElement != null && valueElement.getText().trim().startsWith(param.getName()); + } } @RequiredReadAction - private static boolean matches(final PsiParameter param, final PsiDocTag tag) { - final PsiDocTagValue valueElement = tag.getValueElement(); - return valueElement != null && valueElement.getText().trim().startsWith(param.getName()); - } - } - - @RequiredReadAction - 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) { - buf.append(child.getText()); - } else if (child instanceof PsiInlineDocTag) { - buf.append(child.getText()); - } - } + private static String extractTagDescription(PsiDocTag tag) { + StringBuilder buf = new StringBuilder(); + PsiElement[] children = tag.getChildren(); + for (PsiElement child : children) { + if (child instanceof PsiDocToken token) { + if (token.getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA) { + buf.append(token.getText()); + } + } + else if (child instanceof PsiDocTagValue) { + buf.append(child.getText()); + } + else if (child instanceof PsiInlineDocTag) { + buf.append(child.getText()); + } + } - String s = buf.toString(); - return s.trim(); - } - - @RequiredReadAction - 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()); - } - } + String s = buf.toString(); + return s.trim(); } - return buf.toString().trim(); - } - - private void checkForBadCharacters(PsiDocComment docComment, - final ArrayList 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)); - } - } - } - }); - } - - @RequiredReadAction - private void checkForPeriodInDoc( - PsiDocComment docComment, - ArrayList 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 - final PsiDocCommentOwner owner = PsiTreeUtil.getParentOfType(docComment, PsiDocCommentOwner.class); - for (PsiDocTag tag : tags) { - final String tagName = tag.getName(); - final JavadocTagInfo tagInfo = JavadocManager.SERVICE.getInstance(tag.getProject()).getTagInfo(tagName); - if (tagInfo != null && tagInfo.isValidInContext(owner) && !tagInfo.isInline()) { - tagOffset = tag.getTextOffset(); - break; - } - } - } + @RequiredReadAction + private static String extractThrowsTagDescription(PsiDocTag tag) { + StringBuilder buf = new StringBuilder(); + PsiElement[] children = tag.getChildren(); + for (PsiElement child : children) { + if (child instanceof PsiDocToken token && token.getTokenType() == JavaDocTokenType.DOC_COMMENT_DATA) { + buf.append(token.getText()); + } + } - if (dotIndex == -1 || tagOffset > 0 && dotIndex + docComment.getTextOffset() > tagOffset) { - problems.add(manager.createProblemDescriptor( - docComment.getFirstChild(), - InspectionLocalize.inspectionJavadocProblemDescriptor1().get(), - null, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - onTheFly, - false - )); + return buf.toString().trim(); + } + + private 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); + ASTNode node = element.getNode(); + if (node != null && node.getElementType() == JavaDocTokenType.DOC_COMMENT_BAD_CHARACTER) { + problems.add(manager.createProblemDescriptor( + element, + "Illegal character", + (LocalQuickFix) null, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + onTheFly + )); + } + } + }); } - } - - @Nullable - @RequiredReadAction - private ArrayList getTagValuesProblems( - PsiDocCommentOwner 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)) - && checkTagInfo(inspectionManager, tagInfo, tag, isOnTheFly, problems)) { - continue; - } - - PsiDocTagValue value = tag.getValueElement(); - final JavadocTagInfo info = manager.getTagInfo(tagName); - if (info != null && !info.isValidInContext(context)) continue; - String message = info == null ? null : info.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, - InspectionLocalize.inspectionJavadocProblemNameExpected().get(), - null, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - isOnTheFly, - true + + @RequiredReadAction + private void checkForPeriodInDoc( + 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 + PsiDocCommentOwner owner = PsiTreeUtil.getParentOfType(docComment, PsiDocCommentOwner.class); + for (PsiDocTag tag : tags) { + String tagName = tag.getName(); + JavadocTagInfo tagInfo = JavadocManager.SERVICE.getInstance(tag.getProject()).getTagInfo(tagName); + if (tagInfo != null && tagInfo.isValidInContext(owner) && !tagInfo.isInline()) { + tagOffset = tag.getTextOffset(); + break; + } + } + } + + if (dotIndex == -1 || tagOffset > 0 && dotIndex + docComment.getTextOffset() > tagOffset) { + problems.add(manager.createProblemDescriptor( + docComment.getFirstChild(), + InspectionLocalize.inspectionJavadocProblemDescriptor1().get(), + null, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + onTheFly, + false )); - } - } - } - - if (message != null) { - final PsiDocTagValue valueElement = tag.getValueElement(); - if (valueElement == null){ - problems.add(inspectionManager.createProblemDescriptor( - tag, - InspectionLocalize.inspectionJavadocMethodProblemMissingTagDescription("" + tag.getName() + "").get(), - null, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - isOnTheFly, - true - )); - } else { - problems.add(createDescriptor(valueElement, message, inspectionManager, isOnTheFly)); - } - } - checkInlineTags(inspectionManager, problems, tag.getDataElements(), manager, isOnTheFly); + } } - return problems.isEmpty() ? null : problems; - } + @Nullable + @RequiredReadAction + private List getTagValuesProblems( + PsiDocCommentOwner context, + PsiDocTag[] tags, + InspectionManager inspectionManager, + boolean isOnTheFly + ) { + List problems = new ArrayList<>(2); + for (PsiDocTag tag : tags) { + JavadocManager manager = JavadocManager.SERVICE.getInstance(tag.getProject()); + String tagName = tag.getName(); + JavadocTagInfo tagInfo = manager.getTagInfo(tagName); - private boolean checkTagInfo(InspectionManager inspectionManager, JavadocTagInfo tagInfo, PsiDocTag tag, boolean isOnTheFly, ArrayList problems) { - final String tagName = tag.getName(); - final StringTokenizer tokenizer = new StringTokenizer(myAdditionalJavadocTags, ", "); - while (tokenizer.hasMoreTokens()) { - if (Comparing.strEqual(tagName, tokenizer.nextToken())) return true; - } + if ((tagInfo == null || !tagInfo.isValidInContext(context)) + && checkTagInfo(inspectionManager, tagInfo, tag, isOnTheFly, problems)) { + continue; + } - final PsiElement nameElement = tag.getNameElement(); - if (nameElement != null) { - if (tagInfo == null) { - problems.add( - createDescriptor( - nameElement, - InspectionLocalize.inspectionJavadocProblemWrongTag("" + tagName + "").get(), - new AddUnknownTagToCustoms(tag.getName()), - inspectionManager, - isOnTheFly - )); - } - else { - problems.add(createDescriptor( - nameElement, - InspectionLocalize.inspectionJavadocProblemDisallowedTag("" + tagName + "").get(), - new AddUnknownTagToCustoms(tag.getName()), inspectionManager, isOnTheFly) - ); - } - } - return false; - } - - @RequiredReadAction - private void checkInlineTags( - final InspectionManager inspectionManager, - final ArrayList problems, - final PsiElement[] dataElements, - final 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, - InspectionLocalize.inspectionJavadocProblemPointingToItself().get(), - inspectionManager, - isOnTheFly + PsiDocTagValue value = tag.getValueElement(); + JavadocTagInfo info = manager.getTagInfo(tagName); + if (info != null && !info.isValidInContext(context)) { + continue; + } + String message = info == null ? null : info.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()) { + problems.add(inspectionManager.createProblemDescriptor( + tag, + InspectionLocalize.inspectionJavadocProblemNameExpected().get(), + null, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + isOnTheFly, + true + )); + } + } + } + + if (message != null) { + PsiDocTagValue valueElement = tag.getValueElement(); + if (valueElement == null) { + problems.add(inspectionManager.createProblemDescriptor( + tag, + InspectionLocalize.inspectionJavadocMethodProblemMissingTagDescription("" + tag.getName() + "").get(), + null, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + isOnTheFly, + true )); - } } - } + else { + problems.add(createDescriptor(valueElement, message, inspectionManager, isOnTheFly)); + } } - } + checkInlineTags(inspectionManager, problems, tag.getDataElements(), manager, isOnTheFly); } - } - } - } - @SuppressWarnings({"SimplifiableIfStatement"}) - private boolean isTagRequired(PsiElement context, @NonNls String tag) { - if (context instanceof PsiClass) { - if (PsiTreeUtil.getParentOfType(context, PsiClass.class) != null) { - return isTagRequired(INNER_CLASS_OPTIONS, tag); - } + return problems.isEmpty() ? null : problems; + } + + private boolean checkTagInfo( + InspectionManager inspectionManager, + JavadocTagInfo tagInfo, + PsiDocTag tag, + boolean isOnTheFly, + List problems + ) { + String tagName = tag.getName(); + StringTokenizer tokenizer = new StringTokenizer(myAdditionalJavadocTags, ", "); + while (tokenizer.hasMoreTokens()) { + if (Comparing.strEqual(tagName, tokenizer.nextToken())) { + return true; + } + } - return isTagRequired(TOP_LEVEL_CLASS_OPTIONS, tag); + PsiElement nameElement = tag.getNameElement(); + if (nameElement != null) { + if (tagInfo == null) { + problems.add(createDescriptor( + nameElement, + InspectionLocalize.inspectionJavadocProblemWrongTag("" + tagName + "").get(), + new AddUnknownTagToCustoms(tag.getName()), + inspectionManager, + isOnTheFly + )); + } + else { + problems.add(createDescriptor( + nameElement, + InspectionLocalize.inspectionJavadocProblemDisallowedTag("" + tagName + "").get(), + new AddUnknownTagToCustoms(tag.getName()), inspectionManager, isOnTheFly + )); + } + } + return false; } - if (context instanceof PsiMethod) { - return isTagRequired(METHOD_OPTIONS, tag); + @RequiredReadAction + private void checkInlineTags( + InspectionManager inspectionManager, + List problems, + PsiElement[] dataElements, + JavadocManager manager, + boolean isOnTheFly + ) { + for (PsiElement dataElement : dataElements) { + if (dataElement instanceof PsiInlineDocTag inlineDocTag) { + PsiElement nameElement = inlineDocTag.getNameElement(); + if (manager.getTagInfo(inlineDocTag.getName()) == null) { + checkTagInfo(inspectionManager, null, inlineDocTag, isOnTheFly, problems); + } + if (!IGNORE_POINT_TO_ITSELF) { + PsiDocTagValue value = inlineDocTag.getValueElement(); + if (value != null) { + PsiReference reference = value.getReference(); + if (reference != null) { + 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, + InspectionLocalize.inspectionJavadocProblemPointingToItself().get(), + inspectionManager, + isOnTheFly + )); + } + } + } + } + } + } + } + } } - if (context instanceof PsiField) { - return isTagRequired(FIELD_OPTIONS, tag); - } + @SuppressWarnings({"SimplifiableIfStatement"}) + private boolean isTagRequired(PsiElement context, String tag) { + if (context instanceof PsiClass) { + if (PsiTreeUtil.getParentOfType(context, PsiClass.class) != null) { + return isTagRequired(INNER_CLASS_OPTIONS, tag); + } - return false; - } + return isTagRequired(TOP_LEVEL_CLASS_OPTIONS, tag); + } - private static boolean isTagRequired(Options options, String tag) { - return options.REQUIRED_TAGS.contains(tag); - } + if (context instanceof PsiMethod) { + return isTagRequired(METHOD_OPTIONS, tag); + } - private boolean isJavaDocRequired(PsiModifierListOwner psiElement) { - final RefJavaUtil refUtil = RefJavaUtil.getInstance(); - int actualAccess = getAccessNumber(refUtil.getAccessModifier(psiElement)); - 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 (context instanceof PsiField) { + return isTagRequired(FIELD_OPTIONS, tag); + } - return actualAccess <= getAccessNumber(TOP_LEVEL_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR); + return false; } - 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); + private static boolean isTagRequired(Options options, String tag) { + return options.REQUIRED_TAGS.contains(tag); } - 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); - } + private boolean isJavaDocRequired(PsiModifierListOwner psiElement) { + RefJavaUtil refUtil = RefJavaUtil.getInstance(); + int actualAccess = getAccessNumber(refUtil.getAccessModifier(psiElement)); + if (psiElement instanceof PsiClass psiClass) { + if (PsiTreeUtil.getParentOfType(psiClass, PsiClass.class) != null) { + return actualAccess <= getAccessNumber(INNER_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR); + } - return actualAccess <= getAccessNumber(FIELD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR); + 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; } - return false; - } - - @RequiredReadAction - private void checkDuplicateTags( - final PsiDocTag[] tags, - ArrayList 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<>(); + @RequiredReadAction + private void checkDuplicateTags( + PsiDocTag[] tags, + List problems, + InspectionManager manager, + boolean isOnTheFly + ) { + Set documentedParamNames = null; + Set documentedExceptions = null; + Set uniqueTags = null; + for (PsiDocTag tag : tags) { + if ("param".equals(tag.getName())) { + if (tag.getValueElement() instanceof PsiDocParamRef paramRef) { + PsiReference reference = paramRef.getReference(); + if (reference != null) { + String paramName = reference.getCanonicalText(); + if (documentedParamNames == null) { + documentedParamNames = new HashSet<>(); + } + if (documentedParamNames.contains(paramName)) { + problems.add(createDescriptor( + tag.getNameElement(), + InspectionLocalize.inspectionJavadocProblemDuplicateParam(paramName).get(), + manager, + isOnTheFly + )); + } + documentedParamNames.add(paramName); + } + } } - if (documentedParamNames.contains(paramName)) { - problems.add(createDescriptor( - tag.getNameElement(), - InspectionLocalize.inspectionJavadocProblemDuplicateParam(paramName).get(), - manager, - isOnTheFly - )); + else if (!IGNORE_DUPLICATED_THROWS && ("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 refElement + && refElement.resolve() instanceof PsiClass psiClass) { + String fqName = psiClass.getQualifiedName(); + if (documentedExceptions == null) { + documentedExceptions = new HashSet<>(); + } + if (documentedExceptions.contains(fqName)) { + problems.add(createDescriptor( + tag.getNameElement(), + InspectionLocalize.inspectionJavadocProblemDuplicateThrows(fqName).get(), + manager, + isOnTheFly + )); + } + documentedExceptions.add(fqName); + } + } } - documentedParamNames.add(paramName); - } - } - } - else if (!IGNORE_DUPLICATED_THROWS && ("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(); - if (refElement != null) { - PsiElement element = refElement.resolve(); - if (element instanceof PsiClass) { - String fqName = ((PsiClass)element).getQualifiedName(); - if (documentedExceptions == null) { - documentedExceptions = new HashSet<>(); + else if (JavaDocLocalInspection.ourUniqueTags.contains(tag.getName())) { + if (uniqueTags == null) { + uniqueTags = new HashSet<>(); } - if (documentedExceptions.contains(fqName)) { - problems.add(createDescriptor( - tag.getNameElement(), - InspectionLocalize.inspectionJavadocProblemDuplicateThrows(fqName).get(), - manager, - isOnTheFly - )); + if (uniqueTags.contains(tag.getName())) { + problems.add(createDescriptor( + tag.getNameElement(), + InspectionLocalize.inspectionJavadocProblemDuplicateTag(tag.getName()).get(), + manager, + isOnTheFly + )); } - documentedExceptions.add(fqName); - } + uniqueTags.add(tag.getName()); } - } } - } - else if (JavaDocLocalInspection.ourUniqueTags.contains(tag.getName())) { - if (uniqueTags == null) { - uniqueTags = new HashSet<>(); + } + + private static int getAccessNumber(String accessModifier) { + if (accessModifier.startsWith(NONE)) { + return 0; } - if (uniqueTags.contains(tag.getName())) { - problems.add(createDescriptor( - tag.getNameElement(), - InspectionLocalize.inspectionJavadocProblemDuplicateTag(tag.getName()).get(), - manager, - isOnTheFly - )); + if (accessModifier.startsWith(PUBLIC)) { + return 1; } - 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 - @Nonnull - public LocalizeValue getDisplayName() { - return InspectionLocalize.inspectionJavadocDisplayName(); - } - - @Override - @Nonnull - public LocalizeValue getGroupDisplayName() { - return InspectionLocalize.groupNamesJavadocIssues(); - } - - @Override - @Nonnull - public String getShortName() { - return SHORT_NAME; - } - - public void setIgnoreEmptyDescriptions(boolean ignoreEmptyDescriptions) { - myIgnoreEmptyDescriptions = ignoreEmptyDescriptions; - } - - private class AddUnknownTagToCustoms implements LocalQuickFix { - private final String myTag; - - public AddUnknownTagToCustoms(String tag) { - myTag = tag; + if (accessModifier.startsWith(PROTECTED)) { + return 2; + } + if (accessModifier.startsWith(PACKAGE_LOCAL)) { + return 3; + } + if (accessModifier.startsWith(PRIVATE)) { + return 4; + } + + return 5; } + @Nonnull @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspectionJavadocDisplayName(); + } + @Nonnull - public LocalizeValue getName() { - return JavaQuickFixLocalize.addDoctagToCustomTags(myTag); + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesJavadocIssues(); } + @Nonnull @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - if (myTag == null) return; - if (myAdditionalJavadocTags.length() > 0) { - myAdditionalJavadocTags += "," + myTag; - } - else { - myAdditionalJavadocTags = myTag; - } - final InspectionProfile inspectionProfile = - InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - //correct save settings - InspectionProfileManager.getInstance().fireProfileChanged(inspectionProfile); - //TODO lesya - - /* - - try { - inspectionProfile.save(); - } - catch (IOException e) { - Messages.showErrorDialog(project, e.getMessage(), CommonBundle.getErrorTitle()); - } - - */ + public String getShortName() { + return SHORT_NAME; + } + + public void setIgnoreEmptyDescriptions(boolean ignoreEmptyDescriptions) { + myIgnoreEmptyDescriptions = ignoreEmptyDescriptions; + } + + private class AddUnknownTagToCustoms implements LocalQuickFix { + private final String myTag; + + public AddUnknownTagToCustoms(String tag) { + myTag = tag; + } + + @Nonnull + @Override + public LocalizeValue getName() { + return JavaQuickFixLocalize.addDoctagToCustomTags(myTag); + } + + @Override + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + if (myTag == null) { + return; + } + if (myAdditionalJavadocTags.length() > 0) { + myAdditionalJavadocTags += "," + myTag; + } + else { + myAdditionalJavadocTags = myTag; + } + InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); + //correct save settings + InspectionProfileManager.getInstance().fireProfileChanged(inspectionProfile); + //TODO lesya + + /* + try { + inspectionProfile.save(); + } + catch (IOException e) { + Messages.showErrorDialog(project, e.getMessage(), CommonBundle.getErrorTitle()); + } + */ + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInspection/javaDoc/JavaDocNotNecessaryFilter.java b/plugin/src/main/java/com/intellij/java/impl/codeInspection/javaDoc/JavaDocNotNecessaryFilter.java index 70824ae17a..3a275abec3 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInspection/javaDoc/JavaDocNotNecessaryFilter.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInspection/javaDoc/JavaDocNotNecessaryFilter.java @@ -7,10 +7,10 @@ /** * @author VISTALL - * @since 20/12/2022 + * @since 2022-12-20 */ @ExtensionAPI(ComponentScope.APPLICATION) public interface JavaDocNotNecessaryFilter { - @RequiredReadAction - boolean isJavaDocNotNecessary(PsiMethod method); + @RequiredReadAction + boolean isJavaDocNotNecessary(PsiMethod method); } From 33b0aae8893b81ab4040c9180da23a4953bc3c6a Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 30 Oct 2025 00:28:20 +0300 Subject: [PATCH 2/2] Refactoring JavaDocCommentFixer. --- .../documentation/JavaDocCommentFixer.java | 565 +++++++++--------- 1 file changed, 289 insertions(+), 276 deletions(-) diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/documentation/JavaDocCommentFixer.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/documentation/JavaDocCommentFixer.java index 149631930e..aede3a8d5a 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/documentation/JavaDocCommentFixer.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/documentation/JavaDocCommentFixer.java @@ -24,11 +24,12 @@ import com.intellij.java.language.psi.javadoc.PsiDocTag; import com.intellij.java.language.psi.javadoc.PsiDocTagValue; import com.intellij.java.language.psi.javadoc.PsiDocToken; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; import consulo.codeEditor.Editor; import consulo.document.Document; import consulo.document.util.TextRange; -import consulo.ide.impl.idea.util.containers.ContainerUtilRt; import consulo.language.Language; import consulo.language.editor.documentation.DocCommentFixer; import consulo.language.editor.inspection.ProblemDescriptor; @@ -43,321 +44,333 @@ import consulo.util.lang.ObjectUtil; import consulo.util.lang.Pair; import consulo.util.lang.StringUtil; - import jakarta.annotation.Nonnull; + import java.util.*; /** * @author Denis Zhdanov - * @since 9/20/12 8:44 PM + * @since 2012-09-20 */ @ExtensionImpl public class JavaDocCommentFixer implements DocCommentFixer { + @Nonnull + private static final String PARAM_TAG = "@param"; + + /** + * Lists tags eligible for moving caret to after javadoc fixing. The main idea is that we want to locate caret at the + * incomplete tag description after fixing the doc comment. + *

+ * Example: + *

+     *   class Test {
+     *     /**
+     *      * Method description
+     *      *
+     *      * @param i    'i' argument
+     *      * @param j    [we want to move the caret here because j's description is missing]
+     *      */
+     *     void test(int i, int j) {
+     *     }
+     *   }
+     * 
+ */ + @Nonnull + private static final Set CARET_ANCHOR_TAGS = Set.of(PARAM_TAG, "@throws", "@return"); + + @Nonnull + private static final Comparator COMPARATOR = Comparator.comparing(e -> e.getTextRange().getEndOffset()); + + @Nonnull + private static final String PARAM_TAG_NAME = "param"; + + @Override + @RequiredWriteAction + public void fixComment(@Nonnull Project project, @Nonnull Editor editor, @Nonnull PsiComment comment) { + if (!(comment instanceof PsiDocComment docComment)) { + return; + } - @Nonnull - private static final String PARAM_TAG = "@param"; - - /** - * Lists tags eligible for moving caret to after javadoc fixing. The main idea is that we want to locate caret at the - * incomplete tag description after fixing the doc comment. - *

- * Example: - *

-   *   class Test {
-   *     /**
-   *      * Method description
-   *      *
-   *      * @param i    'i' argument
-   *      * @param j    [we want to move the caret here because j's description is missing]
-   *      */
-   *     void test(int i, int j) {
-   *     }
-   *   }
-   * 
- */ - @Nonnull - private static final Set CARET_ANCHOR_TAGS = ContainerUtilRt.newHashSet(PARAM_TAG, "@throws", "@return"); - - @Nonnull - private static final Comparator COMPARATOR = (e1, e2) -> e2.getTextRange().getEndOffset() - e1.getTextRange().getEndOffset(); - - @Nonnull - private static final String PARAM_TAG_NAME = "param"; - - @Override - public void fixComment(@Nonnull Project project, @Nonnull Editor editor, @Nonnull PsiComment comment) { - if (!(comment instanceof PsiDocComment)) { - return; - } + PsiJavaDocumentedElement owner = docComment.getOwner(); + if (owner == null) { + return; + } - PsiDocComment docComment = (PsiDocComment) comment; - PsiJavaDocumentedElement owner = docComment.getOwner(); - if (owner == null) { - return; - } + PsiFile file = comment.getContainingFile(); + if (file == null) { + return; + } - PsiFile file = comment.getContainingFile(); - if (file == null) { - return; - } + JavaDocReferenceInspection referenceInspection = new JavaDocReferenceInspection(); + JavaDocLocalInspection localInspection = getDocLocalInspection(); - JavaDocReferenceInspection referenceInspection = new JavaDocReferenceInspection(); - JavaDocLocalInspection localInspection = getDocLocalInspection(); - - InspectionManager inspectionManager = InspectionManager.getInstance(project); - ProblemDescriptor[] referenceProblems = null; - ProblemDescriptor[] otherProblems = null; - if (owner instanceof PsiClass) { - referenceProblems = referenceInspection.checkClass(((PsiClass) owner), inspectionManager, false, ObjectUtil.NULL); - otherProblems = localInspection.checkClass(((PsiClass) owner), inspectionManager, false, ObjectUtil.NULL); - } else if (owner instanceof PsiField) { - referenceProblems = referenceInspection.checkField(((PsiField) owner), inspectionManager, false, ObjectUtil.NULL); - otherProblems = localInspection.checkField(((PsiField) owner), inspectionManager, false, ObjectUtil.NULL); - } else if (owner instanceof PsiMethod) { - referenceProblems = referenceInspection.checkMethod((PsiMethod) owner, inspectionManager, false, ObjectUtil.NULL); - otherProblems = localInspection.checkMethod((PsiMethod) owner, inspectionManager, false, ObjectUtil.NULL); - } + InspectionManager inspectionManager = InspectionManager.getInstance(project); + ProblemDescriptor[] referenceProblems = null; + ProblemDescriptor[] otherProblems = null; + if (owner instanceof PsiClass psiClass) { + referenceProblems = referenceInspection.checkClass(psiClass, inspectionManager, false, ObjectUtil.NULL); + otherProblems = localInspection.checkClass(psiClass, inspectionManager, false, ObjectUtil.NULL); + } + else if (owner instanceof PsiField field) { + referenceProblems = referenceInspection.checkField(field, inspectionManager, false, ObjectUtil.NULL); + otherProblems = localInspection.checkField(field, inspectionManager, false, ObjectUtil.NULL); + } + else if (owner instanceof PsiMethod method) { + referenceProblems = referenceInspection.checkMethod(method, inspectionManager, false, ObjectUtil.NULL); + otherProblems = localInspection.checkMethod(method, inspectionManager, false, ObjectUtil.NULL); + } - if (referenceProblems != null) { - fixReferenceProblems(referenceProblems, project); - } - if (otherProblems != null) { - fixCommonProblems(otherProblems, comment, editor.getDocument(), project); + if (referenceProblems != null) { + fixReferenceProblems(referenceProblems, project); + } + if (otherProblems != null) { + fixCommonProblems(otherProblems, comment, editor.getDocument(), project); + } + + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()); + ensureContentOrdered(docComment, editor.getDocument()); + locateCaret(docComment, editor, file); } - PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()); - ensureContentOrdered(docComment, editor.getDocument()); - locateCaret(docComment, editor, file); - } + @Nonnull + private static JavaDocLocalInspection getDocLocalInspection() { + JavaDocLocalInspection localInspection = new JavaDocLocalInspection(); - @Nonnull - private static JavaDocLocalInspection getDocLocalInspection() { - JavaDocLocalInspection localInspection = new JavaDocLocalInspection(); + //region visibility + localInspection.TOP_LEVEL_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = PsiModifier.PRIVATE; + localInspection.INNER_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = PsiModifier.PRIVATE; + localInspection.FIELD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = PsiModifier.PRIVATE; + localInspection.METHOD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = PsiModifier.PRIVATE; + //endregion - //region visibility - localInspection.TOP_LEVEL_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = PsiModifier.PRIVATE; - localInspection.INNER_CLASS_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = PsiModifier.PRIVATE; - localInspection.FIELD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = PsiModifier.PRIVATE; - localInspection.METHOD_OPTIONS.ACCESS_JAVADOC_REQUIRED_FOR = PsiModifier.PRIVATE; - //endregion + localInspection.setIgnoreEmptyDescriptions(true); - localInspection.setIgnoreEmptyDescriptions(true); + //region class type arguments + if (!localInspection.TOP_LEVEL_CLASS_OPTIONS.REQUIRED_TAGS.contains(PARAM_TAG)) { + localInspection.TOP_LEVEL_CLASS_OPTIONS.REQUIRED_TAGS += PARAM_TAG; + } + if (!localInspection.INNER_CLASS_OPTIONS.REQUIRED_TAGS.contains(PARAM_TAG)) { + localInspection.INNER_CLASS_OPTIONS.REQUIRED_TAGS += PARAM_TAG; + } + //endregion - //region class type arguments - if (!localInspection.TOP_LEVEL_CLASS_OPTIONS.REQUIRED_TAGS.contains(PARAM_TAG)) { - localInspection.TOP_LEVEL_CLASS_OPTIONS.REQUIRED_TAGS += PARAM_TAG; + return localInspection; } - if (!localInspection.INNER_CLASS_OPTIONS.REQUIRED_TAGS.contains(PARAM_TAG)) { - localInspection.INNER_CLASS_OPTIONS.REQUIRED_TAGS += PARAM_TAG; - } - //endregion - - return localInspection; - } - - @SuppressWarnings("unchecked") - private static void fixReferenceProblems(@Nonnull ProblemDescriptor[] problems, @Nonnull Project project) { - for (ProblemDescriptor problem : problems) { - QuickFix[] fixes = problem.getFixes(); - if (fixes != null) { - fixes[0].applyFix(project, problem); - } + + @RequiredWriteAction + @SuppressWarnings("unchecked") + private static void fixReferenceProblems(@Nonnull ProblemDescriptor[] problems, @Nonnull Project project) { + for (ProblemDescriptor problem : problems) { + QuickFix[] fixes = problem.getFixes(); + if (fixes != null) { + fixes[0].applyFix(project, problem); + } + } } - } - - /** - * This fixer is based on existing javadoc inspections - there are two of them. One detects invalid references (to nonexistent - * method parameter or non-declared checked exception). Another one handles all other cases (parameter documentation is missing; - * parameter doesn't have a description etc). This method handles result of the second exception - * - * @param problems detected problems - * @param comment target comment to fix - * @param document target document which contains text of the comment being fixed - * @param project current project - */ - @SuppressWarnings("unchecked") - private static void fixCommonProblems(@Nonnull ProblemDescriptor[] problems, @Nonnull PsiComment comment, @Nonnull final Document document, @Nonnull Project project) { - List toRemove = new ArrayList<>(); - for (ProblemDescriptor problem : problems) { - PsiElement element = problem.getPsiElement(); - if (element == null) { - continue; - } - if ((!(element instanceof PsiDocToken) || !JavaDocTokenType.DOC_COMMENT_START.equals(((PsiDocToken) element).getTokenType())) && comment.getTextRange().contains(element.getTextRange())) { - // Unnecessary element like '@return' at the void method's javadoc. - for (PsiElement e = element; e != null; e = e.getParent()) { - if (e instanceof PsiDocTag) { - toRemove.add(e); - break; - } + + /** + * This fixer is based on existing javadoc inspections - there are two of them. One detects invalid references (to nonexistent + * method parameter or non-declared checked exception). Another one handles all other cases (parameter documentation is missing; + * parameter doesn't have a description etc). This method handles result of the second exception + * + * @param problems detected problems + * @param comment target comment to fix + * @param document target document which contains text of the comment being fixed + * @param project current project + */ + @RequiredWriteAction + @SuppressWarnings("unchecked") + private static void fixCommonProblems( + @Nonnull ProblemDescriptor[] problems, + @Nonnull PsiComment comment, + @Nonnull Document document, + @Nonnull Project project + ) { + List toRemove = new ArrayList<>(); + for (ProblemDescriptor problem : problems) { + PsiElement element = problem.getPsiElement(); + if (element == null) { + continue; + } + if (!(element instanceof PsiDocToken docToken && JavaDocTokenType.DOC_COMMENT_START.equals(docToken.getTokenType())) + && comment.getTextRange().contains(element.getTextRange())) { + // Unnecessary element like '@return' at the void method's javadoc. + for (PsiElement e = element; e != null; e = e.getParent()) { + if (e instanceof PsiDocTag) { + toRemove.add(e); + break; + } + } + } + else { + // Problems like 'missing @param'. + QuickFix[] fixes = problem.getFixes(); + if (fixes != null && fixes.length > 0) { + fixes[0].applyFix(project, problem); + } + } } - } else { - // Problems like 'missing @param'. - QuickFix[] fixes = problem.getFixes(); - if (fixes != null && fixes.length > 0) { - fixes[0].applyFix(project, problem); + + if (toRemove.isEmpty()) { + return; + } + if (toRemove.size() > 1) { + Collections.sort(toRemove, COMPARATOR); } - } - } - if (toRemove.isEmpty()) { - return; - } - if (toRemove.size() > 1) { - Collections.sort(toRemove, COMPARATOR); + PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project); + psiDocumentManager.doPostponedOperationsAndUnblockDocument(document); + CharSequence text = document.getCharsSequence(); + for (PsiElement element : toRemove) { + int startOffset = element.getTextRange().getStartOffset(); + int startLine = document.getLineNumber(startOffset); + int i = CharArrayUtil.shiftBackward(text, startOffset - 1, " \t"); + if (i >= 0) { + char c = text.charAt(i); + if (c == '*') { + i = CharArrayUtil.shiftBackward(text, i - 1, " \t"); + } + } + if (i >= 0 && text.charAt(i) == '\n') { + startOffset = Math.max(i, document.getLineStartOffset(startLine) - 1); + } + + int endOffset = element.getTextRange().getEndOffset(); + // Javadoc PSI is awkward, it includes next line text before the next tag. That's why we need to strip it. + i = CharArrayUtil.shiftBackward(text, endOffset - 1, " \t*"); + if (i > 0 && text.charAt(i) == '\n') { + endOffset = i; + } + document.deleteString(startOffset, endOffset); + } + psiDocumentManager.commitDocument(document); } - PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project); - psiDocumentManager.doPostponedOperationsAndUnblockDocument(document); - CharSequence text = document.getCharsSequence(); - for (PsiElement element : toRemove) { - int startOffset = element.getTextRange().getStartOffset(); - int startLine = document.getLineNumber(startOffset); - int i = CharArrayUtil.shiftBackward(text, startOffset - 1, " \t"); - if (i >= 0) { - char c = text.charAt(i); - if (c == '*') { - i = CharArrayUtil.shiftBackward(text, i - 1, " \t"); + @RequiredReadAction + private static void ensureContentOrdered(@Nonnull PsiDocComment comment, @Nonnull Document document) { + //region Parse existing doc comment parameters. + List current = new ArrayList<>(); + Map> tagInfoByName = new HashMap<>(); + for (PsiDocTag tag : comment.getTags()) { + if (!PARAM_TAG_NAME.equals(tag.getName())) { + continue; + } + PsiDocTagValue valueElement = tag.getValueElement(); + if (valueElement == null) { + continue; + } + String paramName = valueElement.getText(); + if (paramName != null) { + current.add(paramName); + tagInfoByName.put(paramName, parseTagValue(tag, document)); + } } - } - if (i >= 0 && text.charAt(i) == '\n') { - startOffset = Math.max(i, document.getLineStartOffset(startLine) - 1); - } - - int endOffset = element.getTextRange().getEndOffset(); - // Javadoc PSI is awkward, it includes next line text before the next tag. That's why we need to strip it. - i = CharArrayUtil.shiftBackward(text, endOffset - 1, " \t*"); - if (i > 0 && text.charAt(i) == '\n') { - endOffset = i; - } - document.deleteString(startOffset, endOffset); - } - psiDocumentManager.commitDocument(document); - } - - private static void ensureContentOrdered(@Nonnull PsiDocComment comment, @Nonnull Document document) { - //region Parse existing doc comment parameters. - List current = new ArrayList<>(); - Map> tagInfoByName = new HashMap<>(); - for (PsiDocTag tag : comment.getTags()) { - if (!PARAM_TAG_NAME.equals(tag.getName())) { - continue; - } - PsiDocTagValue valueElement = tag.getValueElement(); - if (valueElement == null) { - continue; - } - String paramName = valueElement.getText(); - if (paramName != null) { - current.add(paramName); - tagInfoByName.put(paramName, parseTagValue(tag, document)); - } - } - //endregion + //endregion - //region Calculate desired parameters order - List ordered = new ArrayList<>(); - PsiJavaDocumentedElement owner = comment.getOwner(); - if ((owner instanceof PsiMethod)) { - PsiParameter[] parameters = ((PsiMethod) owner).getParameterList().getParameters(); - for (PsiParameter parameter : parameters) { - ordered.add(parameter.getName()); - } - } - if (owner instanceof PsiTypeParameterListOwner) { - PsiTypeParameter[] typeParameters = ((PsiTypeParameterListOwner) owner).getTypeParameters(); - for (PsiTypeParameter parameter : typeParameters) { - ordered.add(String.format("<%s>", parameter.getName())); - } - } - //endregion + //region Calculate desired parameters order + List ordered = new ArrayList<>(); + PsiJavaDocumentedElement owner = comment.getOwner(); + if (owner instanceof PsiMethod method) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + for (PsiParameter parameter : parameters) { + ordered.add(parameter.getName()); + } + } + if (owner instanceof PsiTypeParameterListOwner typeParameterListOwner) { + for (PsiTypeParameter parameter : typeParameterListOwner.getTypeParameters()) { + ordered.add(String.format("<%s>", parameter.getName())); + } + } + //endregion - //region Fix order if necessary. - if (current.size() != ordered.size()) { - // Something is wrong, stop the processing. - return; - } + //region Fix order if necessary. + if (current.size() != ordered.size()) { + // Something is wrong, stop the processing. + return; + } - boolean changed = false; - for (int i = current.size() - 1; i >= 0; i--) { - String newTag = ordered.get(i); - String oldTag = current.get(i); - if (newTag.equals(oldTag)) { - continue; - } - TextRange range = tagInfoByName.get(oldTag).first; - document.replaceString(range.getStartOffset(), range.getEndOffset(), tagInfoByName.get(newTag).second); - changed = true; - } + boolean changed = false; + for (int i = current.size(); --i >= 0; ) { + String newTag = ordered.get(i); + String oldTag = current.get(i); + if (newTag.equals(oldTag)) { + continue; + } + TextRange range = tagInfoByName.get(oldTag).first; + document.replaceString(range.getStartOffset(), range.getEndOffset(), tagInfoByName.get(newTag).second); + changed = true; + } - if (changed) { - PsiDocumentManager manager = PsiDocumentManager.getInstance(comment.getProject()); - manager.commitDocument(document); - } - //endregion - } - - @Nonnull - private static Pair parseTagValue(@Nonnull PsiDocTag tag, @Nonnull Document document) { - PsiDocTagValue valueElement = tag.getValueElement(); - assert valueElement != null; - - int startOffset = valueElement.getTextRange().getStartOffset(); - int endOffset = tag.getTextRange().getEndOffset(); - // Javadoc PSI is rather weird... - CharSequence text = document.getCharsSequence(); - int i = CharArrayUtil.shiftBackward(text, endOffset - 1, " \t*"); - if (i > 0 && text.charAt(i) == '\n') { - endOffset = i; + if (changed) { + PsiDocumentManager manager = PsiDocumentManager.getInstance(comment.getProject()); + manager.commitDocument(document); + } + //endregion } - return Pair.create(TextRange.create(startOffset, endOffset), text.subSequence(startOffset, endOffset).toString()); - } - - private static void locateCaret(@Nonnull PsiDocComment comment, @Nonnull Editor editor, @Nonnull PsiFile file) { - Document document = editor.getDocument(); - int lineToNavigate = -1; - for (PsiDocTag tag : comment.getTags()) { - PsiElement nameElement = tag.getNameElement(); - if (nameElement == null || !CARET_ANCHOR_TAGS.contains(nameElement.getText())) { - continue; - } - boolean good = false; - PsiElement[] dataElements = tag.getDataElements(); - if (dataElements != null) { + @Nonnull + @RequiredReadAction + private static Pair parseTagValue(@Nonnull PsiDocTag tag, @Nonnull Document document) { PsiDocTagValue valueElement = tag.getValueElement(); - for (PsiElement element : dataElements) { - if (element == valueElement) { - continue; - } - if (!StringUtil.isEmptyOrSpaces(element.getText())) { - good = true; - break; - } - } - } - if (!good) { - int offset = tag.getTextRange().getEndOffset(); + assert valueElement != null; + + int startOffset = valueElement.getTextRange().getStartOffset(); + int endOffset = tag.getTextRange().getEndOffset(); + // Javadoc PSI is rather weird... CharSequence text = document.getCharsSequence(); - int i = CharArrayUtil.shiftBackward(text, offset - 1, " \t*"); + int i = CharArrayUtil.shiftBackward(text, endOffset - 1, " \t*"); if (i > 0 && text.charAt(i) == '\n') { - offset = i - 1; + endOffset = i; } - lineToNavigate = document.getLineNumber(offset); - break; - } + + return Pair.create(TextRange.create(startOffset, endOffset), text.subSequence(startOffset, endOffset).toString()); } - if (lineToNavigate >= 0) { - editor.getCaretModel().moveToOffset(document.getLineEndOffset(lineToNavigate)); - JavadocNavigationDelegate.navigateToLineEnd(editor, file); + @RequiredReadAction + private static void locateCaret(@Nonnull PsiDocComment comment, @Nonnull Editor editor, @Nonnull PsiFile file) { + Document document = editor.getDocument(); + int lineToNavigate = -1; + for (PsiDocTag tag : comment.getTags()) { + PsiElement nameElement = tag.getNameElement(); + if (nameElement == null || !CARET_ANCHOR_TAGS.contains(nameElement.getText())) { + continue; + } + boolean good = false; + PsiElement[] dataElements = tag.getDataElements(); + if (dataElements != null) { + PsiDocTagValue valueElement = tag.getValueElement(); + for (PsiElement element : dataElements) { + if (element == valueElement) { + continue; + } + if (!StringUtil.isEmptyOrSpaces(element.getText())) { + good = true; + break; + } + } + } + if (!good) { + int offset = tag.getTextRange().getEndOffset(); + CharSequence text = document.getCharsSequence(); + int i = CharArrayUtil.shiftBackward(text, offset - 1, " \t*"); + if (i > 0 && text.charAt(i) == '\n') { + offset = i - 1; + } + lineToNavigate = document.getLineNumber(offset); + break; + } + } + + if (lineToNavigate >= 0) { + editor.getCaretModel().moveToOffset(document.getLineEndOffset(lineToNavigate)); + JavadocNavigationDelegate.navigateToLineEnd(editor, file); + } } - } - @Nonnull - @Override - public Language getLanguage() { - return JavaLanguage.INSTANCE; - } + @Nonnull + @Override + public Language getLanguage() { + return JavaLanguage.INSTANCE; + } }