IDEA-389345 javadoc: better handling of @deprecated tag with mk comments

Also, `MissingJavadocInspection` is now enabled by default.


(cherry picked from commit 153ae8c588cba01c7444cb0eb59df2a8d525cd27)

IJ-CR-209257

GitOrigin-RevId: f913c5093788926ee3edbf81fab7571daaf0eb16
This commit is contained in:
Mathias
2026-06-23 14:34:32 +00:00
committed by intellij-monorepo-bot
parent c7265abc99
commit 99a88337f7
17 changed files with 134 additions and 17 deletions
@@ -1,6 +1,7 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.migration;
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
import com.intellij.codeInsight.javadoc.JavaDocUtil;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.modcommand.ModPsiUpdater;
@@ -8,11 +9,19 @@ import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.Strings;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.JavaDocTokenType;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaDocumentedElement;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiNameValuePair;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
@@ -91,9 +100,27 @@ public final class MarkdownDocumentationCommentsMigrationInspection extends Base
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
if (element instanceof PsiDocToken) element = element.getParent();
if (!(element instanceof PsiDocComment)) return;
String markdown = convertToMarkdown(appendElementText(element, new StringBuilder()).toString());
String indent = getElementIndent(element);
if (!(element instanceof PsiDocComment docComment)) return;
String result = convertAndPostProcess(element);
Document document = element.getContainingFile().getFileDocument();
if (addDeprecatedAnnotationIfNecessary(project, docComment)) {
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document);
}
int startOffset = element.getTextOffset();
int endOffset = element.getNextSibling() instanceof PsiWhiteSpace whiteSpace
? whiteSpace.getTextOffset() + whiteSpace.getTextLength()
: startOffset + element.getTextLength();
document.replaceString(startOffset, endOffset, result);
}
/// @return The converted and indent post-processed Markdown comment
private static String convertAndPostProcess(PsiElement docComment) {
String markdown = convertToMarkdown(appendElementText(docComment, new StringBuilder()).toString());
String indent = getElementIndent(docComment);
String[] lines = markdown.split("\n");
StringBuilder result = new StringBuilder(markdown.length() + (indent.length() + 4) * lines.length);
for (String line : lines) {
@@ -108,13 +135,7 @@ public final class MarkdownDocumentationCommentsMigrationInspection extends Base
result.append('\n');
}
result.append(indent);
Document document = element.getContainingFile().getFileDocument();
int startOffset = element.getTextOffset();
int endOffset = element.getNextSibling() instanceof PsiWhiteSpace whiteSpace
? whiteSpace.getTextOffset() + whiteSpace.getTextLength()
: startOffset + element.getTextLength();
document.replaceString(startOffset, endOffset, result);
return result.toString();
}
private static StringBuilder appendElementText(@NotNull PsiElement element, StringBuilder result) {
@@ -197,7 +218,7 @@ public final class MarkdownDocumentationCommentsMigrationInspection extends Base
String result = visitor.getResult();
// (mbo) Not the proudest of this one but some combinations of Javadoc tag and HTML cannot reasonnably be handled with jsoup
// (mbo) Not the proudest of this one, but some combinations of Javadoc tag and HTML cannot reasonably be handled with jsoup
// unescape element between internal HTML tags. It is expected that internal tags are not nested.
Matcher internalTagMatcher = Pattern.compile(
"<(?:%s|%s)>(.*?)</(?:%s|%s)>".formatted(
@@ -328,6 +349,28 @@ public final class MarkdownDocumentationCommentsMigrationInspection extends Base
.append(']');
}
}
/// Add the annotation if necessary, as the deprecated tag alone is not enough to indicate deprecation
/// according to the javadoc Markdown specs
///
/// @return Whether the annotation was added (or is already there)
@ApiStatus.Internal
private static boolean addDeprecatedAnnotationIfNecessary(Project project, PsiDocComment docComment) {
if (!JavaFeature.ANNOTATIONS.isSufficient(LanguageLevelProjectExtension.getInstance(project).getLanguageLevel()) ||
docComment.findTagByName("deprecated") == null) {
return false;
}
PsiJavaDocumentedElement owner = docComment.getOwner();
if (owner instanceof PsiModifierListOwner modifierListOwner) {
PsiModifierList modifierList = modifierListOwner.getModifierList();
if (modifierList != null) {
AddAnnotationPsiFix.addPhysicalAnnotationIfAbsent(CommonClassNames.JAVA_LANG_DEPRECATED, PsiNameValuePair.EMPTY_ARRAY,
modifierList);
return true;
}
}
return false;
}
}
@@ -1261,7 +1261,7 @@
implementationClass="com.siyeh.ig.javadoc.HtmlTagCanBeJavadocTagInspection" cleanupTool="true"/>
<localInspection groupPath="Java" language="JAVA" shortName="MissingDeprecatedAnnotation" bundle="messages.InspectionGadgetsBundle"
key="missing.deprecated.annotation.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.javadoc.issues" enabledByDefault="false" level="WARNING" runForWholeFile="true"
groupKey="group.names.javadoc.issues" enabledByDefault="true" level="WARNING" runForWholeFile="true"
implementationClass="com.siyeh.ig.javadoc.MissingDeprecatedAnnotationInspection" cleanupTool="true"/>
<globalInspection groupPath="Java" language="JAVA" shortName="MissingPackageInfo" bundle="messages.InspectionGadgetsBundle"
key="missing.package.info.display.name" groupBundle="messages.InspectionsBundle"
@@ -2573,6 +2573,11 @@ public class JavaDocInfoGenerator {
}
private void generateDeprecatedSection(StringBuilder buffer, PsiDocComment comment) {
if (comment.isMarkdownComment() &&
comment.getOwner() instanceof PsiModifierListOwner owner &&
!owner.hasAnnotation(CommonClassNames.JAVA_LANG_DEPRECATED)) {
return;
}
generateSingleTagSection(buffer, comment, "deprecated", JavaBundle.messagePointer("javadoc.deprecated"));
}
@@ -602,7 +602,7 @@ public final class PsiImplUtil {
public static boolean isDeprecatedByDocTag(@NotNull PsiJavaDocumentedElement owner) {
PsiDocComment docComment = owner.getDocComment();
return docComment != null && docComment.findTagByName("deprecated") != null;
return docComment != null && !docComment.isMarkdownComment() && docComment.findTagByName("deprecated") != null;
}
/**
@@ -14,6 +14,7 @@ import com.intellij.psi.impl.java.stubs.JavaStubElementTypes;
import com.intellij.psi.impl.java.stubs.PsiClassStub;
import com.intellij.psi.impl.java.stubs.PsiFieldStub;
import com.intellij.psi.impl.java.stubs.PsiModifierListStub;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.LightTreeUtil;
import com.intellij.psi.stubs.StubElement;
@@ -51,6 +52,9 @@ public final class RecordUtil {
}
public static boolean isDeprecatedByDocComment(@NotNull LighterAST tree, @NotNull LighterASTNode comment) {
if (comment.getTokenType() == JavaDocElementType.DOC_MARKDOWN_COMMENT) {
return false;
}
String text = LightTreeUtil.toFilteredString(tree, comment, null);
if (text.contains(DEPRECATED_TAG)) {
JavaDocLexer lexer = new JavaDocLexer(LanguageLevel.HIGHEST);
@@ -1,4 +1,5 @@
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class"></icon>&nbsp;<a href="psi_element://X"><code><span style="color:#000000;">X</span></code></a></div><div class="definition"><pre><span style="color:#000080;font-weight:bold;">public</span>&nbsp;<span style="">&lt;</span><span style="color:#20999d;">T</span><span style="color:#000080;font-weight:bold;"> extends </span><a href="psi_element://java.lang.Number"><code><span style="color:#000000;">Number</span></code></a><span style="">&gt;</span>&nbsp;<span style="color:#000080;font-weight:bold;">double</span>&nbsp;<span style="color:#000000;">calculateMd</span><span style="">(</span><br> <i><span style="color:#808000;">@</span><a href="psi_element://org.jetbrains.annotations.NotNull"><code><span style="color:#808000;">NotNull</span></code></a></i><sup><font color="808080" size="3"><i>i</i></font></sup><a href="inferred.annotations"><icon src="AllIcons.Ide.External_link_arrow"></icon></a>&nbsp;<span style="color:#20999d;">T</span>&nbsp;<span style="color:#000000;">a</span><span style="">,</span>
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class"></icon>&nbsp;<a href="psi_element://X"><code><span style="color:#000000;">X</span></code></a></div><div class="definition"><pre><span style="color:#808000;">@</span><a href="psi_element://java.lang.Deprecated"><code><span style="color:#808000;">Deprecated</span></code></a>&nbsp;
<span style="color:#000080;font-weight:bold;">public</span>&nbsp;<span style="">&lt;</span><span style="color:#20999d;">T</span><span style="color:#000080;font-weight:bold;"> extends </span><a href="psi_element://java.lang.Number"><code><span style="color:#000000;">Number</span></code></a><span style="">&gt;</span>&nbsp;<span style="color:#000080;font-weight:bold;">double</span>&nbsp;<span style="color:#000000;">calculateMd</span><span style="">(</span><br> <i><span style="color:#808000;">@</span><a href="psi_element://org.jetbrains.annotations.NotNull"><code><span style="color:#808000;">NotNull</span></code></a></i><sup><font color="808080" size="3"><i>i</i></font></sup><a href="inferred.annotations"><icon src="AllIcons.Ide.External_link_arrow"></icon></a>&nbsp;<span style="color:#20999d;">T</span>&nbsp;<span style="color:#000000;">a</span><span style="">,</span>
<i><span style="color:#808000;">@</span><a href="psi_element://org.jetbrains.annotations.NotNull"><code><span style="color:#808000;">NotNull</span></code></a></i><sup><font color="808080" size="3"><i>i</i></font></sup><a href="inferred.annotations"><icon src="AllIcons.Ide.External_link_arrow"></icon></a>&nbsp;<span style="color:#20999d;">T</span>&nbsp;<span style="color:#000000;">b</span><br><span style="">)</span></pre></div><hr><div class="content"><p>Sample method demonstrating all Javadoc tags <br>
Returns calculated result. <br>
<code><span style="">Math.<wbr>E</span></code> <br>
@@ -1,4 +1,5 @@
import java.lang.Math;
import java.lang.Deprecated;
class X {
@@ -30,6 +31,7 @@ class X {
/// @provides Math
/// @uses Math
/// @hidden
@Deprecated
public <T extends Number> double calculate<caret>Md(T a, T b) {
return a.doubleValue() + b.doubleValue();
}
@@ -0,0 +1 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">X</span></pre></div><table class='sections'><p></table>
@@ -0,0 +1,2 @@
/// @deprecated Oops look at who forgot the annotation
class X {}
@@ -0,0 +1,22 @@
import java.lang.Deprecated;
public class DeprecatedUsages {
///<caret> @deprecated We before java 5 there were no annotations
/// so that how you were expected to deprecate something
@Deprecated
void onlyTag() {}
/// @deprecated
@Deprecated
void emptyTag() {}
/// @deprecated
@Deprecated
void emptyTagWithAnnotation() {}
/// @deprecated Markdown Javadoc updated the meaning of the deprecated tag.
/// This is a bit of heresy and honestly artificial friction but so be it.
@Deprecated
void tagWithAnnotation() {}
}
@@ -0,0 +1,24 @@
import java.lang.Deprecated;
public class DeprecatedUsages {
<warning descr="Javadoc comment can be Markdown documentation comment">/**<caret></warning>
* @deprecated We before java 5 there were no annotations
* so that how you were expected to deprecate something
*/
void onlyTag() {}
<warning descr="Javadoc comment can be Markdown documentation comment">/**</warning> @deprecated */
void emptyTag() {}
<warning descr="Javadoc comment can be Markdown documentation comment">/**</warning> @deprecated */
@Deprecated
void emptyTagWithAnnotation() {}
<warning descr="Javadoc comment can be Markdown documentation comment">/**</warning>
* @deprecated Markdown Javadoc updated the meaning of the deprecated tag.
* This is a bit of heresy and honestly artificial friction but so be it.
*/
@Deprecated
void tagWithAnnotation() {}
}
@@ -298,6 +298,7 @@ public class MarkdownDocumentationCommentsMigration {
///
/// This method will remain in place until finalizers have been removed from
/// most existing code.
@Deprecated
@Override
protected void finalize() throws Throwable {
super.finalize();
@@ -6,6 +6,7 @@ public class MyClass2 extends String implements Runnable{
*/
int field1 = 0;
/// @deprecated The annotation is missing so no real deprecation
Object field2[];
java.lang.Object[] field3;
@@ -268,6 +268,7 @@ public class JavaDocInfoGeneratorTest extends JavaCodeInsightTestCase {
public void testWrongfulInnerClassReferences() { doTestClass(); }
public void testImplicitConstructor() { doTestClass(); }
public void testCommatHtmlEntity() { doTestClass(); }
public void testDeprecatedTagNoAnnotationMarkdown() { doTestClass(); }
public void testRepeatableAnnotations() {
useJava8();
@@ -180,12 +180,18 @@ public class JavaStubsTest extends LightJavaCodeInsightFixtureTestCase {
}
public void test_deprecated_enum_constant() {
PsiClass cls = myFixture.addClass("enum Foo { c1, @Deprecated c2, /** @deprecated */ c3 }");
PsiClass cls = myFixture.addClass("""
enum Foo {
c1, @Deprecated c2, /** @deprecated */ c3,
/// @deprecated no real deprecation
c4
}""");
assertFalse(((PsiFileImpl)cls.getContainingFile()).isContentsLoaded());
assertFalse(cls.getFields()[0].isDeprecated());
assertTrue(cls.getFields()[1].isDeprecated());
assertTrue(cls.getFields()[2].isDeprecated());
assertFalse(cls.getFields()[3].isDeprecated());
assertFalse(((PsiFileImpl)cls.getContainingFile()).isContentsLoaded());
}
@@ -308,6 +308,9 @@ public class SrcRepositoryUseTest extends JavaPsiTestCase {
PsiField field = aClass.findFieldByName("field1", false);
assertTrue(field.isDeprecated());
PsiField field2 = aClass.findFieldByName("field2", false);
assertFalse(field2.isDeprecated());
teardownLoadingFilter();
}
@@ -2,18 +2,20 @@
package com.siyeh.ig.migration;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.testFramework.TestDataPath;
import com.siyeh.ig.LightJavaInspectionTestCase;
import org.jetbrains.annotations.Nullable;
/**
* @author Bas Leijdekkers
*/
@TestDataPath("$CONTENT_ROOT/testData/ig/com/siyeh/igtest/migration/markdown_documentation_comments_migration")
public class MarkdownDocumentationCommentsMigrationInspectionTest extends LightJavaInspectionTestCase {
public void testMarkdownDocumentationCommentsMigration() { check(); }
public void testReferencesNoEscape() { check(); }
public void testCodeBlocks() { check(); }
public void testDeprecatedUsages() { check(); }
@Override
protected @Nullable InspectionProfileEntry getInspection() {
@@ -24,5 +26,4 @@ public class MarkdownDocumentationCommentsMigrationInspectionTest extends LightJ
doTest();
checkQuickFixAll();
}
}