[javadoc] IDEA-257641 Unknown tags break rendering

Add two quickfixes when IDEA discovers unknown javadoc tags to enclose such occurrences in `{@code}` or escape the `@` symbol with @

GitOrigin-RevId: 5151bf905ca0a7c342339fc2ae0d127ebfc387d8
This commit is contained in:
Nikita Eshkeev
2021-12-09 07:44:11 +00:00
committed by intellij-monorepo-bot
parent bd42d1b27a
commit 0f547eebd6
10 changed files with 170 additions and 12 deletions
@@ -1618,7 +1618,7 @@ public class JavaDocInfoGenerator {
int pos = buffer.length();
StringBuilder codeSnippetBuilder = new StringBuilder();
generateLiteralValue(codeSnippetBuilder, tag, false);
generateLiteralValue(codeSnippetBuilder, tag, true);
String codeSnippet = codeSnippetBuilder.toString();
if (isCodeBlock) {
codeSnippet = StringsKt.trimIndent(codeSnippet);
@@ -2705,14 +2705,6 @@ public class JavaDocInfoGenerator {
generateAnnotations(buffer, owner, SignaturePlace.ToolTip, true, true, false);
}
/**
* @deprecated reason.
*
* <pre>
* line 1
* line 2
* </pre>
*/
private enum SignaturePlace {
Javadoc, ToolTip
}
@@ -842,10 +842,15 @@ public class JavaDocLocalInspection extends LocalInspectionTool {
@Override
public void problem(@NotNull PsiElement toHighlight, @NotNull @Nls String message, @Nullable LocalQuickFix fix) {
myHolder.registerProblem(myHolder.getManager().createProblemDescriptor(
toHighlight, message, fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, myOnTheFly));
final LocalQuickFix[] fixes = new LocalQuickFix[] { fix };
problemWithFixes(toHighlight, message, fixes);
}
@Override
public void problemWithFixes(@NotNull PsiElement toHighlight, @NotNull @Nls String message, LocalQuickFix@NotNull [] fixes) {
myHolder.registerProblem(myHolder.getManager().createProblemDescriptor(
toHighlight, message, fixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, myOnTheFly, false));
}
@Override
public void eolProblem(@NotNull PsiElement toHighlight, @NotNull @Nls String message, @Nullable LocalQuickFix fix) {
LocalQuickFix[] fixes = fix != null ? new LocalQuickFix[]{fix} : null;
@@ -1,7 +1,9 @@
// 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.intellij.codeInspection.javaDoc;
import com.intellij.codeInspection.CommonQuickFixBundle;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.reference.RefJavaUtil;
import com.intellij.java.JavaBundle;
import com.intellij.lang.ASTNode;
@@ -39,6 +41,7 @@ public final class JavadocHighlightUtil {
JavaDocLocalInspection inspection();
void problem(@NotNull PsiElement toHighlight, @NotNull @Nls String message, @Nullable LocalQuickFix fix);
void problemWithFixes(@NotNull PsiElement toHighlight, @NotNull @Nls String message, LocalQuickFix@NotNull [] fixes);
void eolProblem(@NotNull PsiElement toHighlight, @NotNull @Nls String message, @Nullable LocalQuickFix fix);
LocalQuickFix addJavadocFix(@NotNull PsiElement nameIdentifier);
@@ -241,7 +244,15 @@ public final class JavadocHighlightUtil {
if (nameElement != null) {
String key = tagInfo == null ? "inspection.javadoc.problem.wrong.tag" : "inspection.javadoc.problem.disallowed.tag";
LocalQuickFix fix = tagInfo == null ? holder.registerTagFix(tagName) : holder.removeTagFix(tagName);
holder.problem(nameElement, JavaBundle.message(key, "<code>" + tagName + "</code>"), fix);
final LocalQuickFix[] fixes;
if (tagInfo != null) {
fixes = new LocalQuickFix[]{ fix };
}
else {
final String nameElementText = nameElement.getText();
fixes = new LocalQuickFix[]{ fix, new EncloseWithCodeFix(nameElementText), new EscapeAtQuickFix(nameElementText) };
}
holder.problemWithFixes(nameElement, JavaBundle.message(key, "<code>" + tagName + "</code>"), fixes);
}
return false;
@@ -520,4 +531,87 @@ public final class JavadocHighlightUtil {
return false;
}
private static abstract class AbstractUnknownTagFix implements LocalQuickFix {
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
if (element == null) return;
final PsiElement enclosingTag = element.getParent();
if (enclosingTag == null) return;
final PsiElement javadoc = enclosingTag.getParent();
if (javadoc == null) return;
final PsiDocComment donorJavadoc = createDonorJavadoc(element);
final PsiElement codeTag = extractElement(donorJavadoc);
if (codeTag == null) return;
for (var e = enclosingTag.getFirstChild(); e != element && e != null; e = e.getNextSibling()) {
javadoc.addBefore(e, enclosingTag);
}
javadoc.addBefore(codeTag, enclosingTag);
for (var e = element.getNextSibling(); e != null; e = e.getNextSibling()) {
javadoc.addBefore(e, enclosingTag);
}
final PsiElement sibling = enclosingTag.getNextSibling();
if (sibling != null && sibling.getNode().getElementType() == TokenType.WHITE_SPACE) {
javadoc.addBefore(sibling, enclosingTag);
}
enclosingTag.delete();
}
protected abstract @NotNull PsiDocComment createDonorJavadoc(@NotNull PsiElement element);
protected abstract @Nullable PsiElement extractElement(@Nullable PsiDocComment donorJavadoc);
}
private static class EncloseWithCodeFix extends AbstractUnknownTagFix {
private final String myName;
private EncloseWithCodeFix(String name) {
myName = name;
}
@Override
public @NotNull String getFamilyName() {
return CommonQuickFixBundle.message("fix.replace.x.with.y", myName, "{@code " + myName + "}");
}
@Override
protected @NotNull PsiDocComment createDonorJavadoc(@NotNull PsiElement element) {
final PsiElementFactory instance = PsiElementFactory.getInstance(element.getProject());
return instance.createDocCommentFromText(String.format("/** {@code %s} */", element.getText()));
}
@Override
protected @Nullable PsiElement extractElement(@Nullable PsiDocComment donorJavadoc) {
return PsiTreeUtil.findChildOfType(donorJavadoc, PsiInlineDocTag.class);
}
}
private static class EscapeAtQuickFix extends AbstractUnknownTagFix {
private final String myName;
private EscapeAtQuickFix(String name) {
myName = name;
}
@Override
public @NotNull String getFamilyName() {
return CommonQuickFixBundle.message("fix.replace.x.with.y", myName, "&#064;" + myName.substring(1));
}
@Override
protected @NotNull PsiDocComment createDonorJavadoc(@NotNull PsiElement element) {
final PsiElementFactory instance = PsiElementFactory.getInstance(element.getProject());
return instance.createDocCommentFromText("/** &#064;" + element.getText().substring(1) + " */");
}
@Override
protected @Nullable PsiElement extractElement(@Nullable PsiDocComment donorJavadoc) {
if (donorJavadoc == null) return null;
return donorJavadoc.getChildren()[2];
}
}
}
@@ -0,0 +1,14 @@
// "Replace '@Override' with '{@code @Override}'" "true"
class Foo {
/**
* {@code @Override}
* <pre>
* {@code hello, world}
* </pre>
*/
public void foo(){
}
}
@@ -0,0 +1,14 @@
// "Replace '@Override' with '&#064;Override'" "true"
class Foo {
/**
* &#064;Override
* <pre>
* {@code hello, world}
* </pre>
*/
public void foo(){
}
}
@@ -0,0 +1,14 @@
// "Replace '@Override' with '{@code @Override}'" "true"
class Foo {
/**
* @Override<caret>
* <pre>
* {@code hello, world}
* </pre>
*/
public void foo(){
}
}
@@ -0,0 +1,14 @@
// "Replace '@Override' with '&#064;Override'" "true"
class Foo {
/**
* @Override<caret>
* <pre>
* {@code hello, world}
* </pre>
*/
public void foo(){
}
}
@@ -0,0 +1,3 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">Main</span></pre></div><div class='content'>
<code style='font-size:100%;'><span style=""><span style="">Hello&amp;lt;World&amp;gt;&#32;message;</span></span></code>
</div><table class='sections'><p></table>
@@ -0,0 +1,7 @@
/**
* {@code
* Hello<World> message;
* }
*/
class Main {
}
@@ -118,6 +118,7 @@ public class JavaDocInfoGeneratorTest extends JavaCodeInsightTestCase {
public void testInlineTagSummary() { useJava10(); doTestClass(); }
public void testLeadingSpacesInPre() { doTestClass(); }
public void testPreInDeprecated() { doTestClass(); }
public void testEscapeHtmlInCode() { doTestClass(); }
public void testRepeatableAnnotations() {
useJava8();