fix(JavaDoc): formatter, inspections

GitOrigin-RevId: 77686401cbf38ea31e93311a608bac7472ccc73c
This commit is contained in:
Mathias Boulay
2024-08-23 14:18:55 +02:00
committed by intellij-monorepo-bot
parent 0ae7ade582
commit d56883b763
30 changed files with 371 additions and 116 deletions

View File

@@ -2,6 +2,7 @@
package com.intellij.codeInsight.editorActions;
import com.intellij.application.options.CodeStyle;
import com.intellij.lang.Language;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RawText;
@@ -10,6 +11,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.util.PsiTreeUtil;
@@ -19,6 +21,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class JavadocCopyPastePreProcessor implements CopyPastePreProcessor {
private static final String MARKDOWN_LINE_PREFIX = "///";
private static final String LINE_COMMENT_PREFIX = "//";
@Override
public @Nullable String preprocessOnCopy(PsiFile file, int[] startOffsets, int[] endOffsets, String text) {
return null;
@@ -30,16 +36,24 @@ public final class JavadocCopyPastePreProcessor implements CopyPastePreProcessor
return text;
}
JavaCodeStyleSettings settings = CodeStyle.getCustomSettings(file, JavaCodeStyleSettings.class);
if (!settings.JD_LEADING_ASTERISKS_ARE_ENABLED) return text;
int offset = editor.getSelectionModel().getSelectionStart();
if (DocumentUtil.isAtLineEnd(offset, editor.getDocument()) && text.startsWith("\n")) return text;
PsiElement element = file.findElementAt(offset);
PsiElement element = file.findElementAt(offset - 1);
PsiDocComment docComment = PsiTreeUtil.getParentOfType(element, PsiDocComment.class, false);
if (docComment == null) return text;
JavaCodeStyleSettings settings = CodeStyle.getCustomSettings(file, JavaCodeStyleSettings.class);
if (!docComment.isMarkdownComment() && !settings.JD_LEADING_ASTERISKS_ARE_ENABLED) return text;
if (docComment.isMarkdownComment()) {
return preProcessMarkdownPaste(file, editor, offset, text);
}
return preProcessHtmlPaste(editor, offset, text);
}
private static String preProcessHtmlPaste(Editor editor, int offset, String text) {
Document document = editor.getDocument();
int lineStartOffset = DocumentUtil.getLineStartOffset(offset, document);
CharSequence chars = document.getImmutableCharSequence();
@@ -49,4 +63,34 @@ public final class JavadocCopyPastePreProcessor implements CopyPastePreProcessor
String lineStartReplacement = "\n" + chars.subSequence(lineStartOffset, firstNonWsLineOffset + 1) + " ";
return StringUtil.trimTrailing(text, '\n').replace("\n", lineStartReplacement);
}
/** @implNote Mostly a copy-paste from {@link LineCommentCopyPastePreProcessor#preprocessOnPaste(Project, PsiFile, Editor, String, RawText)} */
private static String preProcessMarkdownPaste(PsiFile file, Editor editor, int offset, String text) {
Language language = file.getLanguage();
Document document = editor.getDocument();
int lineStartOffset = DocumentUtil.getLineStartOffset(offset, document);
CharSequence chars = document.getImmutableCharSequence();
int firstNonWsLineOffset = CharArrayUtil.shiftForward(chars, lineStartOffset, " \t");
if (offset < (firstNonWsLineOffset + MARKDOWN_LINE_PREFIX.length()) ||
!CharArrayUtil.regionMatches(chars, firstNonWsLineOffset, MARKDOWN_LINE_PREFIX)) {
return text;
}
/*
This piece of code runs AFTER the LineCommentCopyPastePreProcessor.
Since there is not system in place to stop a preprocessor chain, we have to use whatever has been produced beforehand.
Meaning every line matches /^\s*?\/\/\s+?/
There will probably be edge cases with specially crafted input.
*/
CodeStyleSettings codeStyleSettings = CodeStyle.getSettings(file);
String lineStartToReplace = "\n" +
chars.subSequence(lineStartOffset, firstNonWsLineOffset) +
LINE_COMMENT_PREFIX +
(codeStyleSettings.getCommonSettings(language).LINE_COMMENT_ADD_SPACE ? " " : "");
String lineStartReplacement = "\n" + chars.subSequence(lineStartOffset, firstNonWsLineOffset) + MARKDOWN_LINE_PREFIX + " ";
return text.replace(lineStartToReplace, lineStartReplacement);
}
}

View File

@@ -173,7 +173,7 @@ public final class JavaDocReferenceInspection extends LocalInspectionTool {
int startOffsetInDocComment = refHolder.getTextOffset() - docComment.getTextOffset();
int endOffsetInDocComment =
refHolder.getTextOffset() + refText.length() + adjacent.getTextLength() - docComment.getTextOffset();
fix = LocalQuickFix.from(new UrlToHtmlFix(docComment, startOffsetInDocComment, endOffsetInDocComment));
fix = LocalQuickFix.from(new UrlToLinkFix(docComment, startOffsetInDocComment, endOffsetInDocComment));
}
}
}

View File

@@ -27,6 +27,8 @@ public final class JavadocLinkAsPlainTextInspection extends LocalInspectionTool
private static final Pattern END_TAG = Pattern.compile("^.*</\\w+>", Pattern.DOTALL);
private static final Pattern TAG_BEFORE_ATTRIBUTE = Pattern.compile("<\\w+\\s.*\\w+\\s*=\\s*[\"']?\\s*$", Pattern.DOTALL);
private static final Pattern TAG_AFTER_ATTRIBUTE = Pattern.compile("^\\s*[\"']?.*>", Pattern.DOTALL);
private static final Pattern TAG_MARKDOWN_BEFORE_ATTRIBUTE = Pattern.compile("]\\(\\s*", Pattern.DOTALL);
private static final Pattern TAG_MARKDOWN_AFTER_ATTRIBUTE = Pattern.compile("\\s*\\)", Pattern.DOTALL);
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
@@ -54,9 +56,10 @@ public final class JavadocLinkAsPlainTextInspection extends LocalInspectionTool
}
String prefix = commentText.substring(0, start);
String suffix = commentText.substring(end);
if (isContentOfATag(prefix, suffix) || isHtmlTagAttribute(prefix, suffix)) continue;
if (isContentOfATag(prefix, suffix) || isHtmlTagAttribute(prefix, suffix) || isMarkdownLinkAttribute(prefix, suffix)) continue;
holder.problem(comment, JavaBundle.message("inspection.javadoc.link.as.plain.text.message"))
.range(range).fix(new UrlToHtmlFix(comment, start, end)).register();
.range(range).fix(new UrlToLinkFix(comment, start, end)).register();
}
}
@@ -67,6 +70,10 @@ public final class JavadocLinkAsPlainTextInspection extends LocalInspectionTool
private static boolean isHtmlTagAttribute(String prefix, String suffix) {
return TAG_BEFORE_ATTRIBUTE.matcher(prefix).find() && TAG_AFTER_ATTRIBUTE.matcher(suffix).find();
}
private static boolean isMarkdownLinkAttribute(String prefix, String suffix) {
return TAG_MARKDOWN_BEFORE_ATTRIBUTE.matcher(prefix).find() && TAG_MARKDOWN_AFTER_ATTRIBUTE.matcher(suffix).find();
}
};
}
}

View File

@@ -11,7 +11,7 @@ import com.intellij.psi.javadoc.PsiDocComment;
import com.siyeh.ig.psiutils.CommentTracker;
import org.jetbrains.annotations.NotNull;
class UrlToHtmlFix extends PsiUpdateModCommandAction<PsiDocComment> {
class UrlToLinkFix extends PsiUpdateModCommandAction<PsiDocComment> {
private final int myStartOffsetInDocComment;
private final int myEndOffsetInDocComment;
@@ -19,10 +19,10 @@ class UrlToHtmlFix extends PsiUpdateModCommandAction<PsiDocComment> {
/**
* @param comment target Javadoc comment to fix
* @param startOffsetInDocComment the start offset of the URL in Javadoc comment
* @param endOffsetInDocComment if you want to use "..." as the HTML link text, use the end URL offset in a JavaDoc comment;
* if you want to use a substring after the URL as the HTML link text, use the end of that substring
* @param endOffsetInDocComment if you want to use "..." as the link text, use the end URL offset in a JavaDoc comment;
* if you want to use a substring after the URL as the link text, use the end of that substring
*/
UrlToHtmlFix(@NotNull PsiDocComment comment, int startOffsetInDocComment, int endOffsetInDocComment) {
UrlToLinkFix(@NotNull PsiDocComment comment, int startOffsetInDocComment, int endOffsetInDocComment) {
super(comment);
myStartOffsetInDocComment = startOffsetInDocComment;
myEndOffsetInDocComment = endOffsetInDocComment;
@@ -30,7 +30,7 @@ class UrlToHtmlFix extends PsiUpdateModCommandAction<PsiDocComment> {
@Override
public @NotNull String getFamilyName() {
return JavaBundle.message("quickfix.text.replace.url.with.html");
return JavaBundle.message("quickfix.text.replace.url.with.link");
}
@Override
@@ -48,10 +48,16 @@ class UrlToHtmlFix extends PsiUpdateModCommandAction<PsiDocComment> {
else {
text = "...";
}
String wrappedLink = "<a href=\"" + urlAndMaybeText + "\">" + text + "</a>";
CommentTracker ct = new CommentTracker();
String wrappedLink = element.isMarkdownComment()
? "[%s](%s)".formatted(text, urlAndMaybeText)
: "<a href=\"" + urlAndMaybeText + "\">" + text + "</a>";
PsiElement replacement = ct.replace(element, prefix + wrappedLink + suffix);
int start = replacement.getTextRange().getStartOffset() + prefix.length() + urlAndMaybeText.length() + 11;
int start = replacement.getTextRange().getStartOffset() + prefix.length();
start += element.isMarkdownComment() ? 1 : urlAndMaybeText.length() + 11;
updater.select(TextRange.from(start, text.length()));
}
}

View File

@@ -8,6 +8,7 @@ import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.util.PsiTreeUtil;
@@ -26,13 +27,19 @@ public class JavaDocMarkdownEnterHandler extends EnterHandlerDelegateAdapter {
PsiElement caretElement = file.findElementAt(caretOffset.get());
if (caretElement == null) return Result.Continue;
PsiElement currentElement = caretElement.getPrevSibling();
// EOL whitespace is not useful, we only need the tokens behind it
if (currentElement instanceof PsiWhiteSpace) {
currentElement = currentElement.getPrevSibling();
if (caretElement instanceof PsiWhiteSpace) {
// In multiline whitespaces, check cursor position to check whether the handler should trigger
String whitespaces = caretElement.getText();
int end = caretOffset.get() - caretElement.getTextOffset();
if (StringUtil.countChars(whitespaces, '\n', 0, end, false) > 0) {
return Result.Continue;
}
caretElement = caretElement.getPrevSibling();
}
if (!shouldInsertLeadingTokens(currentElement)) {
if (!shouldInsertLeadingTokens(caretElement)) {
return Result.Continue;
}
Document document = editor.getDocument();

View File

@@ -22,6 +22,7 @@ import com.intellij.psi.impl.source.tree.java.ClassElement;
import com.intellij.psi.jsp.JspClassLevelDeclarationStatementType;
import com.intellij.psi.jsp.JspCodeBlockType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.SmartList;
@@ -303,7 +304,12 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo
}
if (childNodeType == JavaDocElementType.DOC_TAG) return Indent.getNoneIndent();
if (childNodeType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) return Indent.getSpaceIndent(1);
if (childNodeType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) {
if(PsiUtil.isInMarkdownDocComment(child.getPsi())) {
return Indent.getNoneIndent();
}
return Indent.getSpaceIndent(1);
}
if (child.getPsi() instanceof PsiFile) return Indent.getNoneIndent();
if (parent != null) {
final Indent defaultChildIndent = getChildIndent(parent, indentOptions);

View File

@@ -16,8 +16,8 @@ public class JDClassComment extends JDParamListOwnerComment {
private List<String> myAuthorsList;
private String myVersion;
public JDClassComment(@NotNull CommentFormatter formatter) {
super(formatter);
public JDClassComment(@NotNull CommentFormatter formatter, boolean isMarkdown) {
super(formatter, isMarkdown);
}
@Override
@@ -29,14 +29,16 @@ public class JDClassComment extends JDParamListOwnerComment {
for (String author : myAuthorsList) {
sb.append(myFormatter.getParser().formatJDTagDescription(author,
prefix + tag.getWithEndWhitespace(),
continuationPrefix));
continuationPrefix,
getIsMarkdown()));
}
}
if (!isNull(myVersion)) {
JDTag tag = JDTag.VERSION;
sb.append(myFormatter.getParser().formatJDTagDescription(myVersion,
prefix + tag.getWithEndWhitespace(),
continuationPrefix));
continuationPrefix,
getIsMarkdown()));
}
}

View File

@@ -24,11 +24,20 @@ public class JDComment {
private List<String> mySinceList;
private String myDeprecated;
private boolean myMultiLineComment;
private String myFirstLine = "/**";
private String myEndLine = "*/";
public JDComment(@NotNull CommentFormatter formatter) {
private final boolean myMarkdown;
private String myFirstLine;
private String myEndLine;
private final String myLeadingLine;
public JDComment(@NotNull CommentFormatter formatter, boolean isMarkdown) {
myFormatter = formatter;
myMarkdown = isMarkdown;
myFirstLine = myMarkdown ? "///" : "/**";
myEndLine = myMarkdown ? "" : "*/";
myLeadingLine = myMarkdown ? "/// " : " * ";
}
protected static boolean isNull(@Nullable String s) {
@@ -57,8 +66,8 @@ public class JDComment {
public @Nullable String generate(@NotNull String indent) {
final String prefix;
if (myFormatter.getSettings().JD_LEADING_ASTERISKS_ARE_ENABLED) {
prefix = indent + " * ";
if (myMarkdown || myFormatter.getSettings().JD_LEADING_ASTERISKS_ARE_ENABLED) {
prefix = indent + myLeadingLine;
} else {
prefix = indent;
}
@@ -66,7 +75,7 @@ public class JDComment {
StringBuilder sb = new StringBuilder();
if (!isNull(myDescription)) {
sb.append(myFormatter.getParser().formatJDTagDescription(myDescription, prefix));
sb.append(myFormatter.getParser().formatJDTagDescription(myDescription, prefix, getIsMarkdown()));
if (myFormatter.getSettings().JD_ADD_BLANK_AFTER_DESCRIPTION) {
sb.append(prefix);
@@ -80,7 +89,7 @@ public class JDComment {
if (!isNull(myUnknownList) && myFormatter.getSettings().JD_KEEP_INVALID_TAGS) {
for (String aUnknownList : myUnknownList) {
sb.append(myFormatter.getParser().formatJDTagDescription(aUnknownList, prefix, continuationPrefix));
sb.append(myFormatter.getParser().formatJDTagDescription(aUnknownList, prefix, continuationPrefix, getIsMarkdown()));
}
}
@@ -88,7 +97,7 @@ public class JDComment {
JDTag tag = JDTag.SEE;
for (String aSeeAlsoList : mySeeAlsoList) {
StringBuilder tagDescription = myFormatter.getParser()
.formatJDTagDescription(aSeeAlsoList, prefix + tag.getWithEndWhitespace(), continuationPrefix);
.formatJDTagDescription(aSeeAlsoList, prefix + tag.getWithEndWhitespace(), continuationPrefix, getIsMarkdown());
sb.append(tagDescription);
}
}
@@ -97,7 +106,7 @@ public class JDComment {
JDTag tag = JDTag.SINCE;
for (String since : mySinceList) {
StringBuilder tagDescription = myFormatter.getParser()
.formatJDTagDescription(since, prefix + tag.getWithEndWhitespace(), continuationPrefix);
.formatJDTagDescription(since, prefix + tag.getWithEndWhitespace(), continuationPrefix, getIsMarkdown());
sb.append(tagDescription);
}
}
@@ -105,7 +114,7 @@ public class JDComment {
if (myDeprecated != null) {
JDTag tag = JDTag.DEPRECATED;
StringBuilder tagDescription = myFormatter.getParser()
.formatJDTagDescription(myDeprecated, prefix + tag.getWithEndWhitespace(), continuationPrefix);
.formatJDTagDescription(myDeprecated, prefix + tag.getWithEndWhitespace(), continuationPrefix, getIsMarkdown());
sb.append(tagDescription);
}
@@ -120,9 +129,10 @@ public class JDComment {
sb.append(prefix).append('\n');
}
if (myMultiLineComment && myFormatter.getSettings().JD_DO_NOT_WRAP_ONE_LINE_COMMENTS
if (!myMarkdown && (myMultiLineComment &&
myFormatter.getSettings().JD_DO_NOT_WRAP_ONE_LINE_COMMENTS
|| !myFormatter.getSettings().JD_DO_NOT_WRAP_ONE_LINE_COMMENTS
|| sb.indexOf("\n") != sb.length() - 1) // If comment has become multiline after formatting - it must be shown as multiline.
|| 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, myFirstLine + '\n');
@@ -162,6 +172,10 @@ public class JDComment {
mySinceList.add(since);
}
public boolean getIsMarkdown() {
return myMarkdown;
}
public void setDeprecated(@Nullable String deprecated) {
this.myDeprecated = deprecated;
}

View File

@@ -14,8 +14,8 @@ class JDMethodComment extends JDParamListOwnerComment {
private final List<String> myReturnTags = new ArrayList<>(); // In erroneous cases multiple return tags are possible (see IDEA-186041)
private List<TagDescription> myThrowsList;
JDMethodComment(@NotNull CommentFormatter formatter) {
super(formatter);
JDMethodComment(@NotNull CommentFormatter formatter,boolean isMarkdown) {
super(formatter, isMarkdown);
}
@Override
@@ -27,7 +27,8 @@ class JDMethodComment extends JDParamListOwnerComment {
JDTag tag = JDTag.RETURN;
sb.append(myFormatter.getParser().formatJDTagDescription(returnTag,
prefix + tag.getWithEndWhitespace(),
prefix + javadocContinuationIndent()));
prefix + javadocContinuationIndent(),
getIsMarkdown()));
if (myFormatter.getSettings().JD_ADD_BLANK_AFTER_RETURN) {
sb.append(prefix);

View File

@@ -12,8 +12,8 @@ import java.util.List;
public class JDParamListOwnerComment extends JDComment {
protected List<TagDescription> myParamsList;
public JDParamListOwnerComment(@NotNull CommentFormatter formatter) {
super(formatter);
public JDParamListOwnerComment(@NotNull CommentFormatter formatter, boolean isMarkdown) {
super(formatter, isMarkdown);
}
@Override
@@ -117,7 +117,7 @@ public class JDParamListOwnerComment extends JDComment {
private StringBuilder formatJDTagDescription(@Nullable String description,
@NotNull CharSequence firstLinePrefix,
@NotNull CharSequence continuationPrefix) {
return myFormatter.getParser().formatJDTagDescription(description, firstLinePrefix, continuationPrefix);
return myFormatter.getParser().formatJDTagDescription(description, firstLinePrefix, continuationPrefix, getIsMarkdown());
}
private StringBuilder formatJDTagDescription(@Nullable String description, @NotNull CharSequence prefix) {

View File

@@ -5,6 +5,7 @@ import com.intellij.ide.todo.TodoConfiguration;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.Strings;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
@@ -63,7 +64,7 @@ public class JDParser {
}
private static boolean isJavadoc(CommentInfo info) {
return JAVADOC_HEADER.equals(info.commentHeader);
return info.docComment.isMarkdownComment() || JAVADOC_HEADER.equals(info.commentHeader);
}
private static CommentInfo getElementsCommentInfo(@Nullable PsiElement psiElement) {
@@ -94,44 +95,31 @@ public class JDParser {
String commentFooter = null;
StringBuilder sb = new StringBuilder();
boolean first = true;
PsiElement e = docComment;
while (true) {
if (e instanceof PsiDocComment cm) {
String text = cm.getText();
if (text.startsWith("//")) {
if (!first) sb.append('\n');
sb.append(text.substring(2).trim());
}
else if (text.startsWith("/*")) {
int commentHeaderEndOffset = CharArrayUtil.shiftForward(text, 1, "*");
int commentFooterStartOffset = CharArrayUtil.shiftBackward(text, text.length() - 2, "*");
String text = docComment.getText();
if (text.startsWith("///")){
sb.append(text);
} else if (text.startsWith("/*")) {
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 {
commentHeader = text.substring(0, commentHeaderEndOffset);
text = "";
commentFooter = "";
}
sb.append(text);
}
if (commentHeaderEndOffset <= commentFooterStartOffset) {
commentHeader = text.substring(0, commentHeaderEndOffset);
commentFooter = text.substring(commentFooterStartOffset + 1);
text = text.substring(commentHeaderEndOffset, commentFooterStartOffset + 1);
}
else if (!(e instanceof PsiWhiteSpace || e instanceof PsiComment)) {
break;
else {
commentHeader = text.substring(0, commentHeaderEndOffset);
text = "";
commentFooter = "";
}
first = false;
e = e.getNextSibling();
sb.append(text);
}
return new CommentInfo(docComment, owner, commentHeader, sb.toString(), commentFooter);
}
private @NotNull JDComment parse(@NotNull CommentInfo info, @NotNull CommentFormatter formatter) {
JDComment comment = createComment(info.commentOwner, formatter);
JDComment comment = createComment(info.commentOwner, formatter, info.docComment.isMarkdownComment());
parse(info.comment, comment);
if (info.commentHeader != null) {
comment.setFirstCommentLine(info.commentHeader);
@@ -142,15 +130,15 @@ public class JDParser {
return comment;
}
private static JDComment createComment(@NotNull PsiElement commentOwner, @NotNull CommentFormatter formatter) {
private static JDComment createComment(@NotNull PsiElement commentOwner, @NotNull CommentFormatter formatter, boolean isMarkdown) {
if (commentOwner instanceof PsiClass) {
return new JDClassComment(formatter);
return new JDClassComment(formatter, isMarkdown);
}
else if (commentOwner instanceof PsiMethod) {
return new JDMethodComment(formatter);
return new JDMethodComment(formatter, isMarkdown);
}
else {
return new JDComment(formatter);
return new JDComment(formatter, isMarkdown);
}
}
@@ -158,7 +146,7 @@ public class JDParser {
if (text == null) return;
List<Boolean> markers = new ArrayList<>();
List<String> l = toArray(text, markers);
List<String> l = toArray(text, markers, comment.getIsMarkdown());
//if it is - we are dealing with multiline comment:
// /**
@@ -173,23 +161,31 @@ public class JDParser {
int size = l.size();
if (size == 0) return;
// preprocess strings - removes first '*'
// preprocess strings - removes leading token
for (int i = 0; i < size; i++) {
String line = l.get(i);
line = line.trim();
if (!line.isEmpty()) {
if (line.charAt(0) == '*') {
if ((markers.get(i)).booleanValue()) {
if (line.length() > 1 && line.charAt(1) == ' ') {
line = line.substring(2);
if(!comment.getIsMarkdown()){
if (line.charAt(0) == '*') {
if ((markers.get(i)).booleanValue()) {
if (line.length() > 1 && line.charAt(1) == ' ') {
line = line.substring(2);
}
else {
line = line.substring(1);
}
}
else {
line = line.substring(1);
line = line.substring(1).trim();
}
}
else {
line = line.substring(1).trim();
} else {
String newLine = StringUtil.trimStart(line, "/// ");
if (Strings.areSameInstance(newLine, line)) {
newLine = StringUtil.trimStart(line, "///");
}
line = newLine;
}
}
l.set(i, line);
@@ -262,7 +258,7 @@ public class JDParser {
* false if it is outside
* @return list of strings (lines)
*/
private @Nullable List<String> toArray(@Nullable String s, @Nullable List<Boolean> markers) {
private @Nullable List<String> toArray(@Nullable String s, @Nullable List<Boolean> markers, boolean markdownComment) {
if (s == null) return null;
s = s.trim();
if (s.isEmpty()) return null;
@@ -282,7 +278,7 @@ public class JDParser {
String token = st.nextToken();
curPos += token.length();
String lineWithoutAsterisk = getLineWithoutAsterisk(token);
String lineWithoutAsterisk = getLineWithoutLeadingTokens(token, markdownComment);
if (!isInMultilineTodo) {
if (isMultilineTodoStart(lineWithoutAsterisk)) {
isInMultilineTodo = true;
@@ -313,7 +309,10 @@ public class JDParser {
markers.add(preCount > 0 || firstLineToKeepIndents >= 0);
continue;
}
if (preCount == 0 && firstLineToKeepIndents < 0 && !isInMultilineTodo && snippetBraceBalance == 0) token = token.trim();
if (!markdownComment && preCount == 0 && firstLineToKeepIndents < 0 && !isInMultilineTodo && snippetBraceBalance == 0) {
token = token.trim();
}
list.add(token);
@@ -378,9 +377,10 @@ public class JDParser {
return Integer.MAX_VALUE;
}
private static String getLineWithoutAsterisk(@NotNull String line) {
int asteriskPos = line.indexOf('*');
return asteriskPos >= 0 ? line.substring(asteriskPos + 1) : line;
private static String getLineWithoutLeadingTokens(@NotNull String line, boolean markdownComment) {
String leadingToken = markdownComment ? "///" : "*";
int asteriskPos = line.indexOf(leadingToken);
return asteriskPos >= 0 ? line.substring(asteriskPos + leadingToken.length()) : line;
}
private static boolean isParaTag(String token) {
@@ -420,10 +420,10 @@ public class JDParser {
* @param width width of the wrapped text
* @return list of strings (lines)
*/
@Contract("null, _ -> null")
private List<String> toArrayWrapping(@Nullable String s, int width) {
@Contract("null, _, _ -> null")
private List<String> toArrayWrapping(@Nullable String s, int width, boolean markdownComment) {
List<String> list = new ArrayList<>();
List<Pair<String, Boolean>> pairs = splitToParagraphs(s);
List<Pair<String, Boolean>> pairs = splitToParagraphs(s, markdownComment);
if (pairs == null) {
return null;
}
@@ -532,8 +532,8 @@ public class JDParser {
* @param s string to process
* @return processing result
*/
@Contract("null -> null")
private List<Pair<String, Boolean>> splitToParagraphs(@Nullable String s) {
@Contract("null, _ -> null")
private List<Pair<String, Boolean>> splitToParagraphs(@Nullable String s, boolean markdownComment) {
if (s == null) return null;
s = s.trim();
if (s.isEmpty()) return null;
@@ -542,7 +542,7 @@ public class JDParser {
StringBuilder sb = new StringBuilder();
List<Boolean> markers = new ArrayList<>();
List<String> list = toArray(s, markers);
List<String> list = toArray(s, markers, markdownComment);
Boolean[] marks = markers.toArray(new Boolean[0]);
markers.clear();
assert list != null;
@@ -723,8 +723,8 @@ public class JDParser {
return count;
}
protected @NotNull StringBuilder formatJDTagDescription(@Nullable String str, @NotNull CharSequence prefix) {
return formatJDTagDescription(str, prefix, prefix);
protected @NotNull StringBuilder formatJDTagDescription(@Nullable String str, @NotNull CharSequence prefix, boolean markdownComment) {
return formatJDTagDescription(str, prefix, prefix, markdownComment);
}
/**
@@ -740,7 +740,8 @@ public class JDParser {
*/
protected @NotNull StringBuilder formatJDTagDescription(@Nullable String str,
@NotNull CharSequence firstLinePrefix,
@NotNull CharSequence continuationPrefix) {
@NotNull CharSequence continuationPrefix,
boolean markdownComment) {
final int rightMargin = myCommonSettings.getRootSettings().getRightMargin(JavaLanguage.INSTANCE);
final int maxCommentLength = rightMargin - continuationPrefix.length();
final int firstLinePrefixLength = firstLinePrefix.length();
@@ -753,7 +754,7 @@ public class JDParser {
//If wrap comments selected, comments should be wrapped by the right margin
if (myCommonSettings.WRAP_COMMENTS && canWrap) {
list = toArrayWrapping(str, maxCommentLength);
list = toArrayWrapping(str, maxCommentLength, markdownComment);
if (firstLineShorter
&& list != null && !list.isEmpty()
@@ -761,7 +762,7 @@ public class JDParser {
{
list = new ArrayList<>();
//want the first line to be shorter, according to it's prefix
String firstLine = toArrayWrapping(str, rightMargin - firstLinePrefixLength).get(0);
String firstLine = toArrayWrapping(str, rightMargin - firstLinePrefixLength, markdownComment).get(0);
//so now first line is exactly same width we need
list.add(firstLine);
str = str.substring(firstLine.length());
@@ -772,7 +773,7 @@ public class JDParser {
}
//getting all another lines according to their prefix
List<String> subList = toArrayWrapping(str, maxCommentLength);
List<String> subList = toArrayWrapping(str, maxCommentLength, markdownComment);
//removing pre tag
if (unclosedPreTag && subList != null && !subList.isEmpty()) {
@@ -783,7 +784,7 @@ public class JDParser {
}
}
else {
list = toArray(str, new ArrayList<>());
list = toArray(str, new ArrayList<>(), markdownComment);
}
if (list == null) {
@@ -796,7 +797,7 @@ public class JDParser {
String line = list.get(i);
if (line.isEmpty() && !mySettings.JD_KEEP_EMPTY_LINES) continue;
if (i != 0) sb.append(continuationPrefix);
if (line.isEmpty() && mySettings.JD_P_AT_EMPTY_LINES && !insidePreTag && !isFollowedByTagLine(list, i) && snippetBraceBalance == 0) {
if (!markdownComment && line.isEmpty() && mySettings.JD_P_AT_EMPTY_LINES && !insidePreTag && !isFollowedByTagLine(list, i) && snippetBraceBalance == 0) {
sb.append(P_START_TAG);
}
else {

View File

@@ -27,6 +27,8 @@ import java.util.regex.Pattern;
public class PsiDocCommentImpl extends LazyParseablePsiElement implements PsiDocComment, JavaTokenType, Constants {
private static final Logger LOG = Logger.getInstance(PsiDocCommentImpl.class);
private static final String LEADING_TOKEN = "*";
private static final String LEADING_TOKEN_MARKDOWN = "///";
private static final TokenSet TAG_BIT_SET = TokenSet.create(DOC_TAG);
private static final ArrayFactory<PsiDocTag> ARRAY_FACTORY = count -> count == 0 ? PsiDocTag.EMPTY_ARRAY : new PsiDocTag[count];
@@ -121,7 +123,7 @@ public class PsiDocCommentImpl extends LazyParseablePsiElement implements PsiDoc
return WS_PATTERN.matcher(docCommentData.getText()).matches();
}
private static void addNewLineToTag(CompositeElement tag, PsiFile psiFile, PsiManager manager) {
private void addNewLineToTag(CompositeElement tag, PsiFile psiFile, PsiManager manager) {
LOG.assertTrue(tag != null && tag.getElementType() == DOC_TAG);
ASTNode current = tag.getLastChildNode();
while (current != null && current.getElementType() == DOC_COMMENT_DATA && isWhitespaceCommentData(current)) {
@@ -130,9 +132,9 @@ public class PsiDocCommentImpl extends LazyParseablePsiElement implements PsiDoc
if (current != null && current.getElementType() == DOC_COMMENT_LEADING_ASTERISKS) return;
CharTable charTable = SharedImplUtil.findCharTableByTree(tag);
if (JavaFileCodeStyleFacade.forContext(psiFile).isJavaDocLeadingAsterisksEnabled()) {
if (JavaFileCodeStyleFacade.forContext(psiFile).isJavaDocLeadingAsterisksEnabled() || isMarkdownComment()) {
tag.addChild(Factory.createSingleLeafElement(TokenType.WHITE_SPACE, "\n ", charTable, manager));
tag.addChild(Factory.createSingleLeafElement(DOC_COMMENT_LEADING_ASTERISKS, "*", charTable, manager));
tag.addChild(Factory.createSingleLeafElement(DOC_COMMENT_LEADING_ASTERISKS, getLeadingToken(), charTable, manager));
tag.addChild(Factory.createSingleLeafElement(DOC_COMMENT_DATA, " ", charTable, manager));
}
else {
@@ -164,9 +166,9 @@ public class PsiDocCommentImpl extends LazyParseablePsiElement implements PsiDoc
CharTable charTable = SharedImplUtil.findCharTableByTree(this);
PsiManager psiManager = getManager();
if (JavaFileCodeStyleFacade.forContext(getContainingFile()).isJavaDocLeadingAsterisksEnabled()) {
if (JavaFileCodeStyleFacade.forContext(getContainingFile()).isJavaDocLeadingAsterisksEnabled() || isMarkdownComment()) {
TreeElement newLine = Factory.createSingleLeafElement(TokenType.WHITE_SPACE, "\n ", charTable, psiManager);
TreeElement leadingAsterisk = Factory.createSingleLeafElement(DOC_COMMENT_LEADING_ASTERISKS, "*", charTable, psiManager);
TreeElement leadingAsterisk = Factory.createSingleLeafElement(DOC_COMMENT_LEADING_ASTERISKS, getLeadingToken(), charTable, psiManager);
TreeElement commentData = Factory.createSingleLeafElement(DOC_COMMENT_DATA, " ", charTable, psiManager);
newLine.getTreeParent().addChild(leadingAsterisk);
newLine.getTreeParent().addChild(commentData);
@@ -267,6 +269,10 @@ public class PsiDocCommentImpl extends LazyParseablePsiElement implements PsiDoc
return true;
}
private String getLeadingToken() {
return isMarkdownComment() ? LEADING_TOKEN_MARKDOWN : LEADING_TOKEN;
}
@Override
public void deleteChildInternal(@NotNull ASTNode child) {
if (child.getElementType() == DOC_TAG) {

View File

@@ -1,4 +1,4 @@
// "Replace URL with HTML link" "true-preview"
// "Replace URL with link" "true-preview"
/**
* abc <a href="https://en.wikipedia.org/"><caret><selection>...</selection></a> def

View File

@@ -0,0 +1,6 @@
// "Replace URL with link" "true-preview"
///
/// abc [<caret><selection>...</selection>](https://en.wikipedia.org/) def
///
class Main { }

View File

@@ -1,4 +1,4 @@
// "Replace URL with HTML link" "true-preview"
// "Replace URL with link" "true-preview"
/**
* abc https://en.wikipedia.org/<caret> def

View File

@@ -0,0 +1,6 @@
// "Replace URL with link" "true-preview"
///
/// abc https://en.wikipedia.org/<caret> def
///
class Main { }

View File

@@ -1,4 +1,4 @@
// "Replace URL with HTML link" "true-preview"
// "Replace URL with link" "true-preview"
/**
* @see <a href="https://www.nowhere.net"><caret><selection>here you go</selection></a>

View File

@@ -1,4 +1,4 @@
// "Replace URL with HTML link" "true-preview"
// "Replace URL with link" "true-preview"
/**
* @see <a href="https://www.nowhere.net"><caret><selection>...</selection></a>

View File

@@ -1,4 +1,4 @@
// "Replace URL with HTML link" "true-preview"
// "Replace URL with link" "true-preview"
/**
* @see <caret>https://www.nowhere.net here you go

View File

@@ -1,4 +1,4 @@
// "Replace URL with HTML link" "true-preview"
// "Replace URL with link" "true-preview"
/**
* @see <caret>https://www.nowhere.net

View File

@@ -0,0 +1,6 @@
// "_ignore" "true"
/// ```
/// <caret>
/// ```
class C{}

View File

@@ -0,0 +1,10 @@
// "_ignore" "true"
/// - [the `java.util` package][java.util]
/// - [a class][String]
/// - [a field][String#CASE_INSENSITIVE_ORDER]
/// - [a method][String#chars()]
<caret>
class C{}

View File

@@ -0,0 +1,8 @@
// "_ignore" "true"
/// - [the `java.util` package][java.util]
/// - [a class][String]
/// - [a field][String#CASE_INSENSITIVE_ORDER]
/// - [a method][String#chars()]
class C{}

View File

@@ -0,0 +1,5 @@
// "_ignore" "true"
/// ```<caret>
/// ```
class C{}

View File

@@ -0,0 +1,9 @@
// "_ignore" "true"
/// - [the `java.util` package][java.util]
/// - [a class][String]
/// - [a field][String#CASE_INSENSITIVE_ORDER]
/// - [a method][String#chars()]
<caret>
class C{}

View File

@@ -0,0 +1,7 @@
// "_ignore" "true"
/// - [the `java.util` package][java.util]
/// - [a class][String]
/// - [a field][String#CASE_INSENSITIVE_ORDER]
/// - [a method][String#chars()]
<caret>class C{}

View File

@@ -0,0 +1,6 @@
/// [Beep Boop](https://www.wikipedia.org/)
class MarkdownLinks {}
/// <warning descr="Link specified as plain text">https://www.wikipedia.org/</warning>
class NoMarkdownLinks {}

View File

@@ -45,4 +45,8 @@ public class JavadocLinkAsPlainTextInspectionTest extends LightDaemonAnalyzerTes
configuration.setLinks(oldLinks);
}
}
public void testMarkdownLinks() {
doTest();
}
}

View File

@@ -1838,4 +1838,98 @@ public class Test {
""".trimIndent()
)
}
fun testMarkdownAlignment(){
doTextTest("""
/// Oh hello there
/// I'm misaligned on purpose
/// Think you can fix me ?
public class Main {}
""".trimIndent(), """
/// Oh hello there
/// I'm misaligned on purpose
/// Think you can fix me ?
public class Main {
}
""".trimIndent())
}
fun testMarkdownSpacing(){
doTextTest("""
///I'm stuck to the leading slashes !
public class Main {}
""".trimIndent(), """
/// I'm stuck to the leading slashes !
public class Main {
}
""".trimIndent())
}
fun testMarkdownSpacing2() {
doTextTest("""
///
/// Purposefully misaaligned stuff
public class Main {}
""".trimIndent(), """
/// Purposefully misaaligned stuff
public class Main {
}
""".trimIndent())
}
fun testMarkdownNoTagInsert() {
doTextTest("""
/// Method blabla bla
///
/// @param toto imagine
///
/// @param titi imagine 2
/// @return The processed value.
/// @throws RuntimeException yeah catch this
/// @see RuntimeException
public class Main {
}
""".trimIndent(),
"""
/// Method blabla bla
///
/// @param toto imagine
/// @param titi imagine 2
/// @return The processed value.
/// @throws RuntimeException yeah catch this
/// @see RuntimeException
public class Main {
}
""".trimIndent())
}
fun testMarkdownRemoveSpaces() {
doTextTest("""
public class Main {
/// Method blabla bla
///
/// @param toto imagine
/// @param titi imagine 2
/// @return The processed value.
/// @throws RuntimeException yeah catch this
/// @see RuntimeException
boolean toto(int toto, int titi) {
return true;
}
}
""".trimIndent(), """
public class Main {
/// Method blabla bla
///
/// @param toto imagine
/// @param titi imagine 2
/// @return The processed value.
/// @throws RuntimeException yeah catch this
/// @see RuntimeException
boolean toto(int toto, int titi) {
return true;
}
}
""".trimIndent())
}
}

View File

@@ -1230,7 +1230,7 @@ quickfix.text.replace.filter.0.is.present.with.any.match=Replace ''filter().{0}(
quickfix.text.replace.stream.0.with.1.2=Replace ''stream().{0}()'' with ''{1}()''{2}
quickfix.text.suffix.may.change.semantics=\ (may change semantics)
quickfix.text.wrap.0.with.1=Wrap ''{0}'' with ''{1}''
quickfix.text.replace.url.with.html=Replace URL with HTML link
quickfix.text.replace.url.with.link=Replace URL with link
radio.button.subclass.of.0=Subclass of ''{0}''
radio.button.unused.declaration.unused.option=unused
radio.button.unused.declaration.used.option=used