Preserving leading and trailing asterisks in javadoc while formatting (IDEA-138799)
authorYaroslav Lepenkin <yaroslav.lepenkin@jetbrains.com>
Thu, 16 Apr 2015 16:09:08 +0000 (19:09 +0300)
committerYaroslav Lepenkin <yaroslav.lepenkin@jetbrains.com>
Thu, 16 Apr 2015 17:30:04 +0000 (20:30 +0300)
java/java-impl/src/com/intellij/psi/impl/source/codeStyle/javadoc/CommentFormatter.java
java/java-impl/src/com/intellij/psi/impl/source/codeStyle/javadoc/JDComment.java
java/java-impl/src/com/intellij/psi/impl/source/codeStyle/javadoc/JDParser.java
java/java-tests/testSrc/com/intellij/psi/formatter/java/JavadocFormatterTest.java

index e08bb4844969f935fe661f7237a1fc87e4d778f3..8e3b75b6fab7ae9aedba8523ea755281a3fe330a 100644 (file)
@@ -15,7 +15,6 @@
  */
 package com.intellij.psi.impl.source.codeStyle.javadoc;
 
  */
 package com.intellij.psi.impl.source.codeStyle.javadoc;
 
-import com.intellij.codeInsight.javadoc.JavaDocUtil;
 import com.intellij.ide.highlighter.JavaFileType;
 import com.intellij.lang.ASTNode;
 import com.intellij.lang.java.JavaLanguage;
 import com.intellij.ide.highlighter.JavaFileType;
 import com.intellij.lang.ASTNode;
 import com.intellij.lang.java.JavaLanguage;
@@ -31,9 +30,12 @@ import com.intellij.psi.impl.source.SourceTreeToPsiMap;
 import com.intellij.psi.javadoc.PsiDocComment;
 import com.intellij.psi.util.PsiUtil;
 import com.intellij.util.IncorrectOperationException;
 import com.intellij.psi.javadoc.PsiDocComment;
 import com.intellij.psi.util.PsiUtil;
 import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.text.CharArrayUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import static com.intellij.psi.impl.source.codeStyle.javadoc.JDParser.CommentInfo;
+
 /**
  * @author max
  */
 /**
  * @author max
  */
@@ -63,47 +65,12 @@ public class CommentFormatter {
     if (!getSettings().ENABLE_JAVADOC_FORMATTING) return;
 
     PsiElement psiElement = SourceTreeToPsiMap.treeElementToPsi(element);
     if (!getSettings().ENABLE_JAVADOC_FORMATTING) return;
 
     PsiElement psiElement = SourceTreeToPsiMap.treeElementToPsi(element);
-    processElementComment(psiElement);
-  }
-
-  private void processElementComment(@Nullable PsiElement psiElement) {
-    if (psiElement instanceof PsiClass) {
-      String newCommentText = formatClassComment((PsiClass)psiElement);
-      replaceDocComment(newCommentText, (PsiDocCommentOwner)psiElement);
-    }
-    else if (psiElement instanceof PsiMethod) {
-      String newCommentText = formatMethodComment((PsiMethod)psiElement);
-      replaceDocComment(newCommentText, (PsiDocCommentOwner)psiElement);
-    }
-    else if (psiElement instanceof PsiField) {
-      String newCommentText = formatFieldComment((PsiField)psiElement);
-      replaceDocComment(newCommentText, (PsiDocCommentOwner)psiElement);
-    }
-    else if (psiElement instanceof PsiDocComment) {
-      PsiDocComment comment = (PsiDocComment)psiElement;
-      if (JavaDocUtil.isInsidePackageInfo(comment)) {
-        String newCommentText = formatPackageComment(comment);
-        replaceCommentText(newCommentText, comment);
-      } else {
-        processElementComment(psiElement.getParent());
-      }
+    if (psiElement != null) {
+      getParser().formatCommentText(psiElement, this);
     }
   }
 
     }
   }
 
-  private String formatPackageComment(@NotNull PsiDocComment comment) {
-    final String info = getCommentInfo(comment);
-    if (info == null) return null;
-
-    JDComment jdComment = getParser().parse(info, new JDComment(this));
-    return jdComment.generate("");
-  }
-
-  private void replaceDocComment(@Nullable String newCommentText, @NotNull final PsiDocCommentOwner psiDocCommentOwner) {
-    final PsiDocComment oldComment = psiDocCommentOwner.getDocComment();
-    replaceCommentText(newCommentText, oldComment);
-  }
-
-  private void replaceCommentText(@Nullable String newCommentText, @Nullable PsiDocComment oldComment) {
+  public void replaceCommentText(@Nullable String newCommentText, @Nullable PsiDocComment oldComment) {
     if (newCommentText != null) newCommentText = stripSpaces(newCommentText);
     if (newCommentText == null || oldComment == null || newCommentText.equals(oldComment.getText())) {
       return;
     if (newCommentText != null) newCommentText = stripSpaces(newCommentText);
     if (newCommentText == null || oldComment == null || newCommentText.equals(oldComment.getText())) {
       return;
@@ -142,53 +109,22 @@ public class CommentFormatter {
   }
 
   @Nullable
   }
 
   @Nullable
-  private String formatClassComment(@NotNull PsiClass psiClass) {
-    final String info = getOrigCommentInfo(psiClass);
-    if (info == null) return null;
-
-    JDComment comment = getParser().parse(info, new JDClassComment(this));
-    return comment.generate(getIndent(psiClass));
-  }
-
-  @Nullable
-  private String formatMethodComment(@NotNull PsiMethod psiMethod) {
-    final String info = getOrigCommentInfo(psiMethod);
-    if (info == null) return null;
-
-    JDComment comment = getParser().parse(info, new JDMethodComment(this));
-    return comment.generate(getIndent(psiMethod));
-  }
-
-  @Nullable
-  private String formatFieldComment(@NotNull PsiField psiField) {
-    final String info = getOrigCommentInfo(psiField);
-    if (info == null) return null;
-
-    JDComment comment = getParser().parse(info, new JDComment(this));
-    return comment.generate(getIndent(psiField));
-  }
-
-
-  /**
-   * Returns the original comment info of the specified element or null
-   *
-   * @param element the specified element
-   * @return text chunk
-   */
-  @Nullable
-  private static String getOrigCommentInfo(PsiDocCommentOwner element) {
+  public static CommentInfo getOrigCommentInfo(PsiDocCommentOwner element) {
     PsiElement e = element.getFirstChild();
     if (!(e instanceof PsiComment)) {
       //no comments for this element
       return null;
     }
     else {
     PsiElement e = element.getFirstChild();
     if (!(e instanceof PsiComment)) {
       //no comments for this element
       return null;
     }
     else {
-      return getCommentInfo(((PsiComment)e));
+      return getCommentInfo((PsiComment)e);
     }
   }
 
   @Nullable
     }
   }
 
   @Nullable
-  private static String getCommentInfo(PsiComment element) {
+  public static CommentInfo getCommentInfo(PsiComment element) {
+    String commentHeader = null;
+    String commentFooter = null;
+
     StringBuilder sb = new StringBuilder();
     PsiElement e = element;
     boolean first = true;
     StringBuilder sb = new StringBuilder();
     PsiElement e = element;
     boolean first = true;
@@ -201,11 +137,18 @@ public class CommentFormatter {
           sb.append(text.substring(2).trim());
         }
         else if (text.startsWith("/*")) {
           sb.append(text.substring(2).trim());
         }
         else if (text.startsWith("/*")) {
-          if (text.charAt(2) == '*') {
-            text = text.substring(3, Math.max(3, text.length() - 2));
+          int commentHeaderEndOffset = CharArrayUtil.shiftForward(text, 1, "*");
+          int commentFooterStartOffset = CharArrayUtil.shiftBackward(text, text.length() - 2, "*");
+
+          if (commentHeaderEndOffset <= commentFooterStartOffset) {
+            commentHeader = text.substring(0, commentHeaderEndOffset);
+            commentFooter = text.substring(commentFooterStartOffset + 1);
+            text = text.substring(commentHeaderEndOffset, commentFooterStartOffset + 1);
           }
           else {
           }
           else {
-            text = text.substring(2, Math.max(2, text.length() - 2));
+            commentHeader = text.substring(0, commentHeaderEndOffset);
+            text = "";
+            commentFooter = "";
           }
           sb.append(text);
         }
           }
           sb.append(text);
         }
@@ -217,7 +160,7 @@ public class CommentFormatter {
       e = e.getNextSibling();
     }
 
       e = e.getNextSibling();
     }
 
-    return sb.toString();
+    return new CommentInfo(commentHeader, sb.toString(), commentFooter);
   }
 
   /**
   }
 
   /**
@@ -226,6 +169,9 @@ public class CommentFormatter {
    * @return indentation size
    */
   private int getIndentSpecial(@NotNull PsiElement element) {
    * @return indentation size
    */
   private int getIndentSpecial(@NotNull PsiElement element) {
+    if (element instanceof PsiDocComment) {
+      return 0;
+    }
     LOG.assertTrue(element instanceof PsiClass ||
                    element instanceof PsiField ||
                    element instanceof PsiMethod);
     LOG.assertTrue(element instanceof PsiClass ||
                    element instanceof PsiField ||
                    element instanceof PsiMethod);
@@ -252,7 +198,7 @@ public class CommentFormatter {
    * @return indent which would be used for the given element when it's formatted according to the current code style settings
    */
   @NotNull
    * @return indent which would be used for the given element when it's formatted according to the current code style settings
    */
   @NotNull
-  private String getIndent(@NotNull PsiElement element) {
+  public String getIndent(@NotNull PsiElement element) {
     return StringUtil.repeatSymbol(' ', getIndentSpecial(element));
   }
 }
     return StringUtil.repeatSymbol(' ', getIndentSpecial(element));
   }
 }
index 0d51c0d2111f8bcfb03cd1559073d185bf1b88c4..69bdd3c41bf9f201850682be5127a53bb4f9cf7d 100644 (file)
@@ -37,6 +37,8 @@ public class JDComment {
   private String mySince;
   private String myDeprecated;
   private boolean myMultiLineComment;
   private String mySince;
   private String myDeprecated;
   private boolean myMultiLineComment;
+  private String myFirstLine = "/**";
+  private String myEndLine = "*/";
 
   public JDComment(@NotNull CommentFormatter formatter) {
     myFormatter = formatter;
 
   public JDComment(@NotNull CommentFormatter formatter) {
     myFormatter = formatter;
@@ -128,13 +130,13 @@ public class JDComment {
         || sb.indexOf("\n") != sb.length() - 1) // If comment has become multiline after formatting - it must be shown as multiline.
                                                 // Last symbol is always '\n', so we need to check if there is one more LF symbol before it.
     {
         || sb.indexOf("\n") != sb.length() - 1) // If comment has become multiline after formatting - it must be shown as multiline.
                                                 // Last symbol is always '\n', so we need to check if there is one more LF symbol before it.
     {
-      sb.insert(0, "/**\n");
+      sb.insert(0, myFirstLine + '\n');
       sb.append(indent);
     } else {
       sb.replace(0, prefix.length(), "/** ");
       sb.deleteCharAt(sb.length()-1);
     }
       sb.append(indent);
     } else {
       sb.replace(0, prefix.length(), "/** ");
       sb.deleteCharAt(sb.length()-1);
     }
-    sb.append(" */");
+    sb.append(' ').append(myEndLine);
 
     return sb.toString();
   }
 
     return sb.toString();
   }
@@ -142,6 +144,14 @@ public class JDComment {
   protected void generateSpecial(@NotNull String prefix, @NotNull StringBuilder sb) {
   }
 
   protected void generateSpecial(@NotNull String prefix, @NotNull StringBuilder sb) {
   }
 
+  public void setFirstCommentLine(@NotNull String firstCommentLine) {
+    myFirstLine = firstCommentLine;
+  }
+
+  public void setLastCommentLine(@NotNull String lastCommentLine) {
+    myEndLine = lastCommentLine;
+  }
+
   public void addSeeAlso(@NotNull String seeAlso) {
     if (mySeeAlsoList == null) {
       mySeeAlsoList = ContainerUtilRt.newArrayList();
   public void addSeeAlso(@NotNull String seeAlso) {
     if (mySeeAlsoList == null) {
       mySeeAlsoList = ContainerUtilRt.newArrayList();
index f6dc9b9697f236205ff5dac21d17343ea8edfd71..4745c3890ceec4db5cdd01948f639a7183c1a0d8 100644 (file)
@@ -19,7 +19,9 @@ import com.intellij.lang.java.JavaLanguage;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.pom.java.LanguageLevel;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.pom.java.LanguageLevel;
+import com.intellij.psi.*;
 import com.intellij.psi.codeStyle.CodeStyleSettings;
 import com.intellij.psi.codeStyle.CodeStyleSettings;
+import com.intellij.psi.javadoc.PsiDocComment;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -40,6 +42,8 @@ public class JDParser {
   private static final String P_START_TAG = "<p>";
   private static final String SELF_CLOSED_P_TAG = "<p/>";
 
   private static final String P_START_TAG = "<p>";
   private static final String SELF_CLOSED_P_TAG = "<p/>";
 
+  private static final char lineSeparator = '\n';
+
   private final CodeStyleSettings mySettings;
   private final LanguageLevel myLanguageLevel;
 
   private final CodeStyleSettings mySettings;
   private final LanguageLevel myLanguageLevel;
 
@@ -48,11 +52,74 @@ public class JDParser {
     myLanguageLevel = languageLevel;
   }
 
     myLanguageLevel = languageLevel;
   }
 
-  private static final char lineSeparator = '\n';
+  public void formatCommentText(@NotNull PsiElement element, @NotNull CommentFormatter formatter) {
+    CommentInfo info = getElementsCommentInfo(element);
+    JDComment comment = info != null ? parse(info, formatter) : null;
+    if (comment != null) {
+      String indent = formatter.getIndent(info.getCommentOwner());
+      String commentText = comment.generate(indent);
+      formatter.replaceCommentText(commentText, (PsiDocComment)info.psiComment);
+    }
+  }
+
+  private CommentInfo getElementsCommentInfo(@Nullable PsiElement psiElement) {
+    CommentInfo info = null;
+    if (psiElement instanceof PsiDocComment) {
+      final PsiDocComment docComment = (PsiDocComment)psiElement;
+      if (docComment.getOwner() == null && docComment.getParent() instanceof PsiJavaFile) {
+        info = CommentFormatter.getCommentInfo(docComment);
+        if (info != null) {
+          info.setCommentOwner(docComment);
+          info.setComment(docComment);
+        }
+      }
+      else {
+        return getElementsCommentInfo(psiElement.getParent());
+      }
+    }
+    else if (psiElement instanceof PsiDocCommentOwner) {
+      PsiDocCommentOwner owner = (PsiDocCommentOwner)psiElement;
+      info = CommentFormatter.getOrigCommentInfo(owner);
+      if (info != null) {
+        info.setCommentOwner(owner);
+        info.setComment(owner.getDocComment());
+      }
+    }
+    return info;
+  }
+
+  private JDComment parse(@NotNull CommentInfo info, @NotNull CommentFormatter formatter) {
+    PsiElement owner = info.getCommentOwner();
+    JDComment comment = createComment(owner, formatter);
+    if (comment == null) return null;
+
+    parse(info.comment, comment);
+    if (info.commentHeader != null) {
+      comment.setFirstCommentLine(info.commentHeader);
+    }
+    if (info.commentFooter != null) {
+      comment.setLastCommentLine(info.commentFooter);
+    }
+
+    return comment;
+  }
+
+  private JDComment createComment(@NotNull PsiElement psiElement, @NotNull CommentFormatter formatter) {
+    if (psiElement instanceof PsiClass) {
+      return new JDClassComment(formatter);
+    }
+    else if (psiElement instanceof PsiMethod) {
+      return new JDMethodComment(formatter);
+    }
+    else if (psiElement instanceof PsiField || psiElement instanceof PsiDocComment) {
+      return new JDComment(formatter);
+    }
+    return null;
+  }
 
   @NotNull
 
   @NotNull
-  public JDComment parse(@Nullable String text, @NotNull JDComment comment) {
-    if (text == null) return comment;
+  private void parse(@Nullable String text, @NotNull JDComment comment) {
+    if (text == null) return;
 
     List<Boolean> markers = new ArrayList<Boolean>();
     List<String> l = toArray(text, "\n", markers);
 
     List<Boolean> markers = new ArrayList<Boolean>();
     List<String> l = toArray(text, "\n", markers);
@@ -66,9 +133,9 @@ public class JDParser {
       comment.setMultiLine(true);
     }
 
       comment.setMultiLine(true);
     }
 
-    if (l == null) return comment;
+    if (l == null) return;
     int size = l.size();
     int size = l.size();
-    if (size == 0) return comment;
+    if (size == 0) return;
 
     // preprocess strings - removes first '*'
     for (int i = 0; i < size; i++) {
 
     // preprocess strings - removes first '*'
     for (int i = 0; i < size; i++) {
@@ -140,8 +207,6 @@ public class JDParser {
         }
       }
     }
         }
       }
     }
-
-    return comment;
   }
 
   /**
   }
 
   /**
@@ -544,4 +609,35 @@ public class JDParser {
 
     return sb;
   }
 
     return sb;
   }
+
+  public static class CommentInfo {
+    public final String commentHeader;
+    public final String comment;
+    public final String commentFooter;
+
+    private PsiComment psiComment;
+    private PsiElement myCommentOwner;
+
+    public CommentInfo(String commentHeader, String comment, String commentFooter) {
+      this.commentHeader = commentHeader;
+      this.comment = comment;
+      this.commentFooter = commentFooter;
+    }
+
+    public void setCommentOwner(PsiElement commentOwner) {
+      myCommentOwner = commentOwner;
+    }
+
+    public PsiElement getCommentOwner() {
+      return myCommentOwner;
+    }
+
+    public void setComment(PsiDocComment comment) {
+      psiComment = comment;
+    }
+
+    public PsiComment getComment() {
+      return psiComment;
+    }
+  }
 }
 }
index d38ec5553d5eb7a4ed57e6b2ef862caa9daa7e5b..f1a2f73609a6f7b8f9181eb36893412f71b951ed 100644 (file)
@@ -56,6 +56,61 @@ public class JavadocFormatterTest extends AbstractJavaFormatterTest {
     );
   }
 
     );
   }
 
+  public void test_do_wrap_on_asterisks() {
+    doTextTest(
+        "/***********\n" +
+        " *\n" +
+        " *********************/\n" +
+        "\n" +
+        "\n" +
+        "   public class Test {\n" +
+        "}\n",
+        "/***********\n" +
+        " *\n" +
+        " *********************/\n" +
+        "\n" +
+        "\n" +
+        "public class Test {\n" +
+        "}\n"
+    );
+  }
+
+  public void test_wrap_after_asterisks() {
+    doTextTest(
+        "/******* hollla la\n" +
+        " * I am javadoc comment\n" +
+        " * heey ***********/\n" +
+        "   class T {   }\n",
+        "/*******\n" +
+        " * hollla la\n" +
+        " * I am javadoc comment\n" +
+        " * heey\n" +
+        " ***********/\n" +
+        "class T {\n" +
+        "}\n"
+    );
+  }
+
+  public void test_strange_comment() {
+    doTextTest(
+        "/******F*****/\n" +
+        "public class T {\n" +
+        "}",
+        "/******\n" +
+        " * F\n" +
+        " *****/\n" +
+        "public class T {\n" +
+        "}"
+    );
+  }
+
+  public void test_incomplete_javadoc() {
+    doTextTest(
+        "/**\n",
+        "/**\n"
+    );
+  }
+
   public void testEA49739() throws Exception {
     getSettings().WRAP_LONG_LINES = true;
     getSettings().RIGHT_MARGIN = 35;
   public void testEA49739() throws Exception {
     getSettings().WRAP_LONG_LINES = true;
     getSettings().RIGHT_MARGIN = 35;