diff --git a/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavadocBlankLinesInspection.java b/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavadocBlankLinesInspection.java index 12c8e2733b2e..c701c4e146d2 100644 --- a/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavadocBlankLinesInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavadocBlankLinesInspection.java @@ -9,14 +9,18 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.javadoc.PsiDocTag; import com.intellij.psi.javadoc.PsiDocToken; import com.intellij.psi.javadoc.PsiInlineDocTag; -import com.intellij.util.ObjectUtils; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.xml.util.HtmlUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static com.intellij.openapi.util.text.CharFilter.NOT_WHITESPACE_FILTER; + public class JavadocBlankLinesInspection extends LocalInspectionTool { @Override public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { @@ -24,30 +28,79 @@ public class JavadocBlankLinesInspection extends LocalInspectionTool { @Override public void visitDocToken(PsiDocToken token) { super.visitDocToken(token); - PsiElement nextSibling = token.getNextSibling(); + PsiElement nextWhitespace = token.getNextSibling(); + PsiElement prevWhitespace = token.getPrevSibling(); if (token.getTokenType() == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS && - token.getPrevSibling() instanceof PsiWhiteSpace && - nextSibling instanceof PsiWhiteSpace && !isBeforeParagraphOrBlockTag(nextSibling)) { + prevWhitespace instanceof PsiWhiteSpace && + nextWhitespace instanceof PsiWhiteSpace && + !isAfterParagraphOrBlockTag(prevWhitespace) && + !isBeforeParagraphOrBlockTag(nextWhitespace) && + !isAfterPreTag(token)) { holder.registerProblem(token, JavaBundle.message("inspection.javadoc.blank.lines.message"), new InsertParagraphTagFix(token)); } } }; } - private static boolean isBeforeParagraphOrBlockTag(PsiElement element) { - PsiDocToken maybeLeadingAsterisks = ObjectUtils.tryCast(element.getNextSibling(), PsiDocToken.class); - if (maybeLeadingAsterisks == null || maybeLeadingAsterisks.getTokenType() != JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) { - return false; + private static boolean isAfterPreTag(PsiDocToken token) { + PsiElement parent = token.getParent(); + if (parent instanceof PsiInlineDocTag) { + return isAfterPreTagInner(PsiTreeUtil.getPrevSiblingOfType(parent, PsiDocToken.class)); } - PsiElement nextSibling = maybeLeadingAsterisks.getNextSibling(); - if (nextSibling == null) return false; - return nextSibling.getText().stripLeading().startsWith("
") || - isBlockTag(nextSibling) || - isBlockTag(nextSibling.getNextSibling()); + return isAfterPreTagInner(token); } - private static boolean isBlockTag(PsiElement element) { - return element instanceof PsiDocTag && !(element instanceof PsiInlineDocTag); + private static boolean isAfterPreTagInner(PsiDocToken token) { + boolean result = false; + while (token != null) { + String text = token.getText(); + int closingPreTagIndex = StringUtil.toLowerCase(text).lastIndexOf(""); + int openingPreTagIndex = StringUtil.toLowerCase(text).lastIndexOf("
");
+ result = openingPreTagIndex != -1 && (closingPreTagIndex == -1 || closingPreTagIndex < openingPreTagIndex);
+ if (closingPreTagIndex != -1 || openingPreTagIndex != -1) break;
+ token = PsiTreeUtil.getPrevSiblingOfType(token, PsiDocToken.class);
+ }
+ return result;
+ }
+
+ private static boolean isAfterParagraphOrBlockTag(PsiElement element) {
+ PsiElement prevSibling = element.getPrevSibling();
+ if (!(prevSibling instanceof PsiDocToken)) return true;
+ if (((PsiDocToken)prevSibling).getTokenType() != JavaDocTokenType.DOC_COMMENT_DATA) return true;
+ String text = prevSibling.getText();
+ return endsWithHtmlBlockTag(text) || isNullOrBlockTag(prevSibling);
+ }
+
+ private static boolean isBeforeParagraphOrBlockTag(PsiElement element) {
+ PsiElement nextSibling = element.getNextSibling();
+ if (!(nextSibling instanceof PsiDocToken)) return true;
+ if (((PsiDocToken)nextSibling).getTokenType() != JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) return true;
+ nextSibling = nextSibling.getNextSibling();
+ if (nextSibling == null) return true;
+ String text = nextSibling.getText();
+ return isNullOrBlockTag(nextSibling) ||
+ startsWithHtmlBlockTag(text) ||
+ isNullOrBlockTag(nextSibling.getNextSibling());
+ }
+
+ private static boolean startsWithHtmlBlockTag(String text) {
+ text = text.stripLeading();
+ if (text.isEmpty() || text.charAt(0) != '<') return false;
+ String maybeBlockTag = text.substring(1, text.indexOf('>'));
+ String trimmed = StringUtil.trim(maybeBlockTag.strip(), ch -> NOT_WHITESPACE_FILTER.accept(ch) && ch != '/');
+ return HtmlUtil.isHtmlBlockTag(trimmed) || "br".equalsIgnoreCase(trimmed);
+ }
+
+ private static boolean endsWithHtmlBlockTag(String text) {
+ text = text.stripTrailing();
+ if (text.isEmpty() || text.charAt(text.length() - 1) != '>') return false;
+ String maybeBlockTag = text.substring(text.lastIndexOf('<') + 1, text.length() - 1);
+ String trimmed = StringUtil.trim(maybeBlockTag.strip(), ch -> NOT_WHITESPACE_FILTER.accept(ch) && ch != '/');
+ return HtmlUtil.isHtmlBlockTag(trimmed) || "br".equalsIgnoreCase(trimmed);
+ }
+
+ private static boolean isNullOrBlockTag(PsiElement element) {
+ return element == null || (element instanceof PsiDocTag && !(element instanceof PsiInlineDocTag));
}
private static class InsertParagraphTagFix extends LocalQuickFixAndIntentionActionOnPsiElement {
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeBetweenJavadocBlockTags.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeBetweenJavadocBlockTags.java
new file mode 100644
index 000000000000..05d6e07bf774
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeBetweenJavadocBlockTags.java
@@ -0,0 +1,13 @@
+// "Insert " "false"
+class Test {
+ /**
+ * Answer to the ultimate question of life, the universe, and everything
+ *
+ * @author Douglas Adams
+ *
+ * @return The number 42
+ */
+ int answer() {
+ return 42;
+ }
+}
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeBr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeBr.java
new file mode 100644
index 000000000000..1492c0cb27f9
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeBr.java
@@ -0,0 +1,15 @@
+// "Insert " "false"
+class Test {
+ /**
+ *
+ * Blah
+ *
+ *
+ * Blah
+ * Blah
+ *
+ */
+ int answer() {
+ return 42;
+ }
+}
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeCommentEnd.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeCommentEnd.java
new file mode 100644
index 000000000000..1e0f5ec733b1
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeCommentEnd.java
@@ -0,0 +1,12 @@
+// "Insert " "false"
+class Test {
+ /**
+ * Answer to the ultimate question of life, the universe, and everything
+ *
+ * @return The number 42
+ *
+ */
+ int answer() {
+ return 42;
+ }
+}
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeCommentStart.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeCommentStart.java
new file mode 100644
index 000000000000..db74ae04487a
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeCommentStart.java
@@ -0,0 +1,12 @@
+// "Insert " "false"
+class Test {
+ /**
+ *
+ * Answer to the ultimate question of life, the universe, and everything
+ *
+ * @return The number 42
+ */
+ int answer() {
+ return 42;
+ }
+}
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeHtmlBlockTag1.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeHtmlBlockTag1.java
new file mode 100644
index 000000000000..047bd2744dc5
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeHtmlBlockTag1.java
@@ -0,0 +1,16 @@
+// "Insert " "false"
+class Test {
+ /**
+ * Answer to the ultimate question of life, the universe, and everything
+ *
{@code
+ * 6 * 7 = 42
+ * }
+ *
+ * That's it. That's all there is.
+ *
+ * @return The number 42
+ */
+ int answer() {
+ return 42;
+ }
+}
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeParagraphTag.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeHtmlBlockTag2.java
similarity index 100%
rename from java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeParagraphTag.java
rename to java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeHtmlBlockTag2.java
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeJavadocBlockTagsWithoutSpace.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeJavadocBlockTagsWithoutSpace.java
new file mode 100644
index 000000000000..d3423306d298
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforeJavadocBlockTagsWithoutSpace.java
@@ -0,0 +1,11 @@
+// "Insert " "false"
+class Test {
+ /**
+ * Answer to the ultimate question of life, the universe, and everything
+ *
+ *@return The number 42
+ */
+ int answer() {
+ return 42;
+ }
+}
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforePreformattedText.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforePreformattedText.java
new file mode 100644
index 000000000000..81122160cf73
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforePreformattedText.java
@@ -0,0 +1,12 @@
+// "Insert " "false"
+class Test {
+ /**
+ *
+ * Blah
+ *
+ * Blah
+ * Blah
+ *
+ */
+ void foo() { }
+}
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforePreformattedTextInsideInlineTag.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforePreformattedTextInsideInlineTag.java
new file mode 100644
index 000000000000..6ccfa6cbe75e
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/javadocBlankLines/beforePreformattedTextInsideInlineTag.java
@@ -0,0 +1,13 @@
+// "Insert " "false"
+class Test {
+ /**
+ *
{@code
+ * 1
+ *
+ * 2 3 4
+ * 5 6
+ * }
+ *
+ */
+ void foo() { }
+}