[java] (IDEA-279250) replace with javadoc intention changed to inspection (ReplaceWithJavadocInspection) + tests changed

GitOrigin-RevId: 4b27beeecd36dcedaa92483176128186747b7e5b
This commit is contained in:
Olga Klisho
2022-04-12 19:39:07 +03:00
committed by intellij-monorepo-bot
parent c101e4337c
commit caddae14f6
38 changed files with 292 additions and 274 deletions

View File

@@ -1742,6 +1742,10 @@
<localInspection groupPath="Java" language="JAVA" shortName="MissingJavadoc" bundle="messages.JavaBundle" key="inspection.missingJavadoc.display.name"
groupKey="group.names.javadoc.issues" groupBundle="messages.InspectionsBundle" enabledByDefault="false" level="WARNING"
implementationClass="com.intellij.codeInspection.javaDoc.MissingJavadocInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="ReplaceWithJavadoc" bundle="messages.JavaBundle" key="inspection.replace.javadoc.display.name"
groupKey="group.names.javadoc.issues" groupBundle="messages.InspectionsBundle" enabledByDefault="false" level="WARNING"
implementationClass="com.intellij.codeInspection.javaDoc.ReplaceWithJavadocInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="JavadocDeclaration" bundle="messages.JavaBundle" key="inspection.javadocDeclaration.display.name"
groupKey="group.names.javadoc.issues" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.javaDoc.JavadocDeclarationInspection" alternativeId="javadoc"/>
@@ -2286,4 +2290,4 @@
<webServerRootsProvider implementation="org.jetbrains.builtInWebServer.ArtifactWebServerRootsProvider" order="last"/>
</extensions>
</idea-plugin>
</idea-plugin>

View File

@@ -0,0 +1,176 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.javaDoc
import com.intellij.codeInspection.*
import com.intellij.java.JavaBundle
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.javadoc.PsiDocComment
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.annotations.Contract
import java.util.*
import java.util.function.Predicate
import java.util.stream.Collectors
import java.util.stream.Stream
class ReplaceWithJavadocInspection : LocalInspectionTool() {
companion object {
private val LOGGER = Logger.getInstance(
ReplaceWithJavadocInspection::class.java.name)
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : JavaElementVisitor() {
override fun visitElement(element: PsiElement) {
if (element !is PsiComment) return
if (element is PsiDocComment) return
val parent: PsiElement = element.getParent()
if (parent !is PsiMember || parent is PsiAnonymousClass) return
val type: PsiElement? = PsiTreeUtil.getPrevSiblingOfType(element, PsiModifierList::class.java)
if (type != null) return
val fix = ReplaceWithJavadocFix()
holder.registerProblem(element, JavaBundle.message("inspection.replace.with.javadoc.comment"), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fix)
}
}
}
class ReplaceWithJavadocFix : LocalQuickFix {
override fun getFamilyName(): String {
return JavaBundle.message("inspection.replace.with.javadoc")
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement as? PsiComment ?: return
val method = element.parent ?: return
val child = method.firstChild as? PsiComment ?: return
val psiFacade = JavaPsiFacade.getInstance(element.project)
val factory = psiFacade.elementFactory
// the set will contain all the comment nodes that are directly before the method's modifier list
val commentNodes = mutableSetOf<PsiComment>()
val javadocText = prepareJavadocComment(child, commentNodes)
val javadoc = factory.createDocCommentFromText(javadocText)
if (commentNodes.isEmpty()) {
LOGGER.error("The set of visited node comments is empty")
return
}
if (commentNodes.size > 1) {
// remove all the comment nodes except the first one
commentNodes.stream().skip(1).forEach { obj: PsiComment -> obj.delete() }
}
val item = ContainerUtil.getFirstItem(commentNodes)
item.replace(javadoc)
}
@Contract(mutates = "param2")
private fun prepareJavadocComment(comment: PsiComment, visited: MutableSet<PsiComment>): String {
val commentContent: Collection<String> = siblingsComments(comment, visited)
val sb = StringBuilder("/**\n")
for (string in commentContent) {
val line = string.trim { it <= ' ' }
if (line.isEmpty()) continue
sb.append("* ")
sb.append(line)
sb.append("\n")
}
if (comment is PsiDocComment) {
val tags = comment.tags
if (tags.size > 0) {
val start = tags[0].startOffsetInParent
val end = tags[tags.size - 1].textRangeInParent.endOffset
sb.append("* ")
sb.append(comment.getText(), start, end)
sb.append("\n")
}
}
sb.append("*/")
return sb.toString()
}
/**
* Extracts the content of the comment nodes that are right siblings to the comment node
*
* @param comment a comment node to get siblings for
* @param visited a set of visited comment nodes
* @return the list of strings which consists of the content of the comment nodes that are either left or right siblings.
*/
@Contract(mutates = "param2")
private fun siblingsComments(comment: PsiComment,
visited: MutableSet<in PsiComment>): List<String> {
val result = ArrayList<String>()
var node: PsiElement? = comment
do {
if (node is PsiComment) {
val commentNode = node
visited.add(commentNode)
result.addAll(getCommentTextLines(commentNode))
}
node = node!!.nextSibling
}
while (node != null && node !is PsiModifierList)
return result
}
/**
* Extracts the content of a comment as a list of strings.
*
* @param comment [PsiComment] to examine
* @return the content of a comment as a list of strings where each line is an element of the list.
*/
@Contract(pure = true)
private fun getCommentTextLines(comment: PsiComment): Collection<String> {
val lines: Stream<String>
lines = if (comment is PsiDocComment) {
Arrays.stream(comment.descriptionElements)
.map { obj: PsiElement -> obj.text }
}
else {
Arrays.stream(extractLines(comment))
}
return lines
.map { obj: String -> obj.trim { it <= ' ' } }
.filter(Predicate.not { obj: String -> obj.isEmpty() })
.map { line: String ->
if (line.startsWith("*")) line.substring(1)
else line
}
.map { line: String? ->
StringUtil.replace(
line!!, "*/", "*&#47;")
}
.collect(Collectors.toList())
}
/**
* Extracts the content from either a C-style block comment or an end-of-line comment as an array of lines
*
* @param comment [PsiComment] to examine
* @return the content of a comment as an array of lines
*/
@Contract(pure = true)
private fun extractLines(comment: PsiComment): Array<String> {
assert(comment !is PsiDocComment) { "The method can't be called for a javadoc comment." }
val commentText = comment.text
if (comment.tokenType === JavaTokenType.END_OF_LINE_COMMENT) {
return arrayOf(commentText.substring("//".length))
}
val start = "/*".length
val end = commentText.length - "*/".length
val content = commentText.substring(start, end)
return content.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
}
}
}

View File

@@ -0,0 +1,28 @@
<html>
<body>
Reports a regular comment that belongs to a field, method, or class that can be replaced with a Javadoc comment.
<p><b>Example:</b></p>
<pre><code>
public class Main {
/*
* Hello,
*/
// World!
void f() {
}
}
</code></pre>
<p>After the quick-fix is applied:</p>
<pre><code>
public class Main {
/**
* Hello,
* World!
*/
void f() {
}
}
</code></pre>
</body>
</html>

View File

@@ -1,10 +1,9 @@
// "Replace with javadoc" "false"
import java.util.Iterator;
class Main {
{
new Iterable<Integer>() {
// <caret>comment
// comment
@Override
public Iterator<Integer> iterator() {

View File

@@ -1,9 +1,8 @@
// "Replace with javadoc" "false"
class Main {
public static final void main
(String[] args)// 1<caret>
(String[] args)// 1
/* 2 */
{
}

View File

@@ -1,9 +1,8 @@
// "Replace with javadoc" "false"
class Main {
public static final void main// 1
// 2<caret>
// 2
(String[] args) {
}
}

View File

@@ -0,0 +1,8 @@
class Main {
public static final void // 1
// 2
main(String[] args) {
}
}

View File

@@ -0,0 +1,6 @@
class Main {
public static final /* 1 */ void main(String[] args) {
}
}

View File

@@ -0,0 +1,6 @@
class Main {
public /* 1 */ static final void main(String[] args) {
}
}

View File

@@ -1,4 +1,3 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
class Test {
/**
* Test. Don't report type param 'X' as duplicate of param 'X'.
@@ -7,4 +6,4 @@ class Test {
* @param <X> my type
*/
<X> X getValue(String X) {return null;}
}
}

View File

@@ -1,4 +1,3 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
class Test {
/**
* Test.
@@ -8,4 +7,4 @@ class Test {
* <warning descr="Duplicate @param tag for parameter '<X>'">@param</warning> <X> another type
*/
<X> X getValue(String X) {return null;}
}
}

View File

@@ -1,9 +0,0 @@
// "Replace with javadoc" "false"
class Main {
public static final void // 1<caret>
// 2
main(String[] args) {
}
}

View File

@@ -1,7 +0,0 @@
// "Replace with javadoc" "false"
class Main {
public static final /* 1<caret> */ void main(String[] args) {
}
}

View File

@@ -1,7 +0,0 @@
// "Replace with javadoc" "false"
class Main {
public /* 1<caret> */ static final void main(String[] args) {
}
}

View File

@@ -5,6 +5,7 @@ import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
import com.intellij.codeInspection.javaDoc.JavaDocReferenceInspection;
import com.intellij.codeInspection.javaDoc.JavadocDeclarationInspection;
import com.intellij.codeInspection.javaDoc.ReplaceWithJavadocInspection;
import com.intellij.model.psi.PsiSymbolReference;
import com.intellij.openapi.paths.UrlReference;
import com.intellij.openapi.paths.WebReference;
@@ -31,7 +32,7 @@ public class JavadocDeclarationHighlightingTest extends LightDaemonAnalyzerTestC
super.setUp();
myInspection = new JavadocDeclarationInspection();
myInspection.IGNORE_THROWS_DUPLICATE = false;
enableInspectionTools(myInspection, new JavaDocReferenceInspection());
enableInspectionTools(myInspection, new JavaDocReferenceInspection(), new ReplaceWithJavadocInspection());
}
@Override
@@ -123,6 +124,12 @@ public class JavadocDeclarationHighlightingTest extends LightDaemonAnalyzerTestC
public void testOnlyEmptyLinesInSnippet() { doTest(); }
public void testSnippetInstructionsWithUnhandledThrowable() { doTest(); }
public void testJavaDocWithSpaces() { doTest(); }
public void testCommentsAfterArguments() { doTest(); }
public void testAnonymousClass() { doTest(); }
public void testCommentsBeforeArguments() { doTest(); }
public void testCommentsInModifierList() { doTest(); }
public void testCommentsBeforeType() { doTest(); }
public void testCommentsBeforeName() { doTest(); }
public void testIssueLinksInJavaDoc() {
IssueNavigationConfiguration navigationConfiguration = IssueNavigationConfiguration.getInstance(getProject());

View File

@@ -0,0 +1,46 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight.javadoc
import com.intellij.codeInspection.javaDoc.ReplaceWithJavadocInspection
import com.intellij.java.JavaBundle
import com.siyeh.ig.IGQuickFixesTestCase
class ReplaceWithJavadocInspectionFixTest : IGQuickFixesTestCase() {
@Throws(Exception::class)
override fun setUp() {
super.setUp()
myFixture.enableInspections(ReplaceWithJavadocInspection())
myRelativePath = "javadoc/replace_with_javadoc"
myDefaultHint = JavaBundle.message("inspection.replace.with.javadoc")
}
fun testJavadocWithTags() {
doTest()
}
fun testMergeJavadocWIthEndOfLineAndBlock() {
doTest()
}
fun testEndOfLine() {
doTest()
}
fun testEmptyEndOfLine() {
doTest()
}
fun testEmptyBlockComment() {
doTest()
}
fun testCommentsBeforeAndAfterModifierListTypeAndParams() {
doTest()
}
fun testBlockComment() {
doTest()
}
}

View File

@@ -468,6 +468,7 @@ inspection.suspicious.ternary.in.varargs.quickfix=Wrap in array initializer
inspection.insert.literal.underscores.display.name=Unreadable numeric literal
inspection.insert.literal.underscores.family.name=Insert underscores into numeric literal
inspection.missingJavadoc.display.name=Missing Javadoc
inspection.replace.javadoc.display.name=Comment replaceable with Javadoc
inspection.missingJavadoc.label.minimalVisibility=Minimal visibility:
inspection.missingJavadoc.label.requiredTags=Required tags:
inspection.javadocDeclaration.display.name=Javadoc declaration problems
@@ -509,6 +510,8 @@ inspection.javadoc.problem.wrong.tag=Wrong tag {0}
inspection.javadoc.ref.display.name=Declaration has problems in Javadoc references
inspection.javadoc.blank.lines.display.name=Blank line should be replaced with <p> to break lines
inspection.javadoc.blank.lines.message=Blank line will be ignored
inspection.replace.with.javadoc=Replace with Javadoc comment
inspection.replace.with.javadoc.comment=Comment can be converted to Javadoc
inspection.javadoc.blank.lines.fix.name=Insert <p>
inspection.javadoc.blank.lines.fix.family.name=Replace blank lines with <p>
inspection.javadoc.link.as.plain.text.display.name=Link specified as plain text
@@ -1759,4 +1762,4 @@ remove.var.keyword.text=Remove 'var'
intention.family.name.upgrade.jdk=Upgrade JDK
intention.name.upgrade.jdk.to=Upgrade JDK to {0}+
intention.family.name.box.primitive.in.conditional.branch=Box primitive value in conditional branch
progress.title.detecting.jdk=Detecting JDK
progress.title.detecting.jdk=Detecting JDK

View File

@@ -167,12 +167,6 @@
<categoryKey>intention.category.comments</categoryKey>
</intentionAction>
<intentionAction>
<className>com.siyeh.ipp.comment.ReplaceWithJavadocIntention</className>
<bundleName>messages.JavaBundle</bundleName>
<categoryKey>intention.category.comments</categoryKey>
</intentionAction>
<intentionAction>
<className>com.siyeh.ipp.comment.MoveCommentToSeparateLineIntention</className>
<bundleName>messages.JavaBundle</bundleName>

View File

@@ -1,198 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ipp.comment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ipp.base.Intention;
import com.siyeh.ipp.base.PsiElementPredicate;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Converts C-style block comments or end-of-line comments that belong to an element of the {@link PsiMember} type to javadoc.
*/
public class ReplaceWithJavadocIntention extends Intention {
private static final Logger LOGGER = Logger.getInstance(ReplaceWithJavadocIntention.class.getName());
@Override
protected void processIntention(@NotNull PsiElement element) {
if (!(element instanceof PsiComment)) return;
final PsiElement method = element.getParent();
if (method == null) return;
final PsiElement child = method.getFirstChild();
if (!(child instanceof PsiComment)) return;
final PsiComment firstCommentOfMethod = (PsiComment)child;
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(element.getProject());
final PsiElementFactory factory = psiFacade.getElementFactory();
// the set will contain all the comment nodes that are directly before the method's modifier list
final Set<PsiComment> commentNodes = new HashSet<>();
final String javadocText = prepareJavadocComment(firstCommentOfMethod, commentNodes);
final PsiDocComment javadoc = factory.createDocCommentFromText(javadocText);
if (commentNodes.isEmpty()) {
LOGGER.error("The set of visited node comments is empty");
return;
}
if (commentNodes.size() > 1) {
// remove all the comment nodes except the first one
commentNodes.stream().skip(1).forEach(PsiElement::delete);
}
final PsiComment item = ContainerUtil.getFirstItem(commentNodes);
item.replace(javadoc);
}
/**
* Extracts the content of the comment nodes that are right siblings to the comment node
*
* @param comment a comment node to get siblings for
* @param visited a set of visited comment nodes
* @return the list of strings which consists of the content of the comment nodes that are either left or right siblings.
*/
@Contract(mutates = "param2")
private static @NotNull List<@NotNull String> siblingsComments(@NotNull PsiComment comment,
@NotNull Set<? super @NotNull PsiComment> visited) {
final List<String> result = new ArrayList<>();
PsiElement node = comment;
do {
if (node instanceof PsiComment) {
final PsiComment commentNode = (PsiComment)node;
visited.add(commentNode);
result.addAll(getCommentTextLines(commentNode));
}
node = node.getNextSibling();
}
while (node != null && !(node instanceof PsiModifierList));
return result;
}
/**
* Combines the collection of strings into a string in the javadoc format:
* <ul>
* <li>The resulting string starts with <code>/**</code></li>
* <li>Each line from the collection is precluded with '<code>*</code>'</li>
* <li>The resulting string ends with <code>*&#47;</code></li>
* </ul>
* @return the string in the javadoc format.
*/
@Contract(mutates = "param2")
private static @NotNull String prepareJavadocComment(PsiComment comment, @NotNull Set<@NotNull PsiComment> visited) {
final @NotNull Collection<String> commentContent = siblingsComments(comment, visited);
final StringBuilder sb = new StringBuilder("/**\n");
for (String string : commentContent) {
final String line = string.trim();
if (line.isEmpty()) continue;
sb.append("* ");
sb.append(line);
sb.append("\n");
}
if (comment instanceof PsiDocComment) {
PsiDocComment javadoc = (PsiDocComment)comment;
final PsiDocTag[] tags = javadoc.getTags();
if (tags.length > 0) {
final int start = tags[0].getStartOffsetInParent();
final int end = tags[tags.length - 1].getTextRangeInParent().getEndOffset();
sb.append("* ");
sb.append(comment.getText(), start, end);
sb.append("\n");
}
}
sb.append("*/");
return sb.toString();
}
/**
* Extracts the content of a comment as a list of strings.
*
* @param comment {@link PsiComment} to examine
* @return the content of a comment as a list of strings where each line is an element of the list.
*/
@Contract(pure = true)
private static List<String> getCommentTextLines(@NotNull PsiComment comment) {
final Stream<String> lines;
if (comment instanceof PsiDocComment) {
final PsiDocComment docComment = (PsiDocComment)comment;
lines = Arrays.stream(docComment.getDescriptionElements())
.map(PsiElement::getText);
}
else {
lines = Arrays.stream(extractLines(comment));
}
return lines
.map(String::trim)
.filter(Predicate.not(String::isEmpty))
.map(line -> line.startsWith("*") ? line.substring(1) : line)
.map(line -> StringUtil.replace(line, "*/", "*&#47;"))
.collect(Collectors.toList());
}
/**
* Extracts the content from either a C-style block comment or an end-of-line comment as an array of lines
*
* @param comment {@link PsiComment} to examine
* @return the content of a comment as an array of lines
*/
@Contract(pure = true)
private static String @NotNull[] extractLines(@NotNull PsiComment comment) {
assert !(comment instanceof PsiDocComment) : "The method can't be called for a javadoc comment.";
final String commentText = comment.getText();
if (comment.getTokenType() == JavaTokenType.END_OF_LINE_COMMENT) {
return new String[] { commentText.substring("//".length()) };
}
final int start = "/*".length();
final int end = commentText.length() - "*/".length();
final String content = commentText.substring(start, end);
return content.split("\n");
}
/**
* Returns a predicate that returns true when a {@link PsiElement} is of the {@link PsiComment}(excluding {@link PsiDocComment}) type
* which belong to a {@link PsiMember} type to convert it to javadoc.
*
* @return predicate to check if a comment can be converted to a javadoc.
*/
@Override
protected @NotNull PsiElementPredicate getElementPredicate() {
return element -> {
if (!(element instanceof PsiComment)) return false;
if (element instanceof PsiDocComment) return false;
final PsiComment comment = (PsiComment)element;
final PsiElement parent = comment.getParent();
if (!(parent instanceof PsiMember) || parent instanceof PsiAnonymousClass) return false;
// the comment node might have a method as its parent,
// but the comment itself can be defined before/after the modifier list/type/name/parameters of the method
// Such comments are not candidates to be converted to javadoc
final PsiElement type = PsiTreeUtil.getPrevSiblingOfType(comment, PsiModifierList.class);
return type == null;
};
}
}

View File

@@ -1,8 +0,0 @@
public class Main {
<spot>/**
* Hello,
* World!
*/</spot>
void f() {
}
}

View File

@@ -1,8 +0,0 @@
public class Main {
<spot>/*
* Hello,
*/
// World!</spot>
void f() {
}
}

View File

@@ -1,5 +0,0 @@
<html>
<body>
Converts a regular comment that belongs to a field, method, or class into a Javadoc comment.
</body>
</html>

View File

@@ -1,13 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ipp.comment;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import org.jetbrains.annotations.NonNls;
public class ReplaceWithJavadocIntentionTest extends LightQuickFixParameterizedTestCase {
@Override
protected @NonNls String getBasePath() {
return "/comment/to_javadoc";
}
}