feat(javadoc): early markdown support part 2

GitOrigin-RevId: 74093daa489fda535b3951828d6617519e5d293f
This commit is contained in:
Mathias Boulay
2024-08-14 02:10:36 +02:00
committed by intellij-monorepo-bot
parent 0ccfb8e55f
commit 6ebdc205ef
26 changed files with 324 additions and 150 deletions

View File

@@ -12,10 +12,8 @@ import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.psi.templateLanguages.TemplateLanguageUtil;
import com.intellij.psi.util.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
@@ -421,4 +419,19 @@ public final class JavaDocUtil {
public static boolean isInsidePackageInfo(@Nullable PsiDocComment containingComment) {
return containingComment != null && containingComment.getOwner() == null && containingComment.getParent() instanceof PsiJavaFile;
}
public static boolean isDanglingDocComment(@NotNull PsiDocComment comment, boolean ignoreCopyright) {
if (comment.getOwner() != null || TemplateLanguageUtil.isInsideTemplateFile(comment)) {
return false;
}
if (isInsidePackageInfo(comment) &&
PsiTreeUtil.skipWhitespacesAndCommentsForward(comment) instanceof PsiPackageStatement &&
"package-info.java".equals(comment.getContainingFile().getName())) {
return false;
}
if (ignoreCopyright && comment.getPrevSibling() == null && comment.getParent() instanceof PsiFile) {
return false;
}
return true;
}
}

View File

@@ -2,14 +2,17 @@
package com.intellij.psi.impl.source.javadoc;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.psi.JavaDocTokenType;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.javadoc.PsiMarkdownCodeBlock;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PsiMarkdownCodeBlockImpl extends CompositePsiElement implements PsiMarkdownCodeBlock {
public PsiMarkdownCodeBlockImpl() {
@@ -18,7 +21,7 @@ public class PsiMarkdownCodeBlockImpl extends CompositePsiElement implements Psi
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if(visitor instanceof JavaElementVisitor){
if (visitor instanceof JavaElementVisitor) {
((JavaElementVisitor)visitor).visitMarkdownCodeBlock(this);
return;
}
@@ -30,15 +33,43 @@ public class PsiMarkdownCodeBlockImpl extends CompositePsiElement implements Psi
return "PsiMarkdownCodeBlock:";
}
@Override
public @Nullable Language getCodeLanguage() {
String languageInfo = getLanguageInfo();
if (languageInfo == null) return getLanguage();
return Language.findLanguageByID(languageInfo);
}
@Override
public @NotNull String getCodeText() {
StringBuilder builder = new StringBuilder();
for (ASTNode child = getFirstChildNode(); child != null; child = child.getTreeNext()) {
ASTNode child = getFirstChildNode().getTreeNext();
if (hasLanguageInfo() && child != null) {
child = child.getTreeNext();
}
while (child != null) {
IElementType i = child.getElementType();
if (i != JavaDocTokenType.DOC_CODE_FENCE && i != JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) {
builder.append(child.getText());
}
child = child.getTreeNext();
}
return builder.toString();
}
private @Nullable String getLanguageInfo() {
if (!hasLanguageInfo()) return null;
return getChildren()[1].getText().trim();
}
/** @return True if the block has language info. It doesn't always translate to a {@link com.intellij.lang.Language} */
private boolean hasLanguageInfo() {
PsiElement[] children = getChildren();
if (children.length < 2) return false;
PsiElement languageInfoChild = children[1];
if (languageInfoChild.getNode().getElementType() != JavaDocTokenType.DOC_COMMENT_DATA) return false;
return !languageInfoChild.getText().trim().isEmpty();
}
}

View File

@@ -12,6 +12,7 @@ import com.intellij.psi.javadoc.PsiSnippetDocTag;
import com.intellij.psi.javadoc.PsiSnippetDocTagBody;
import com.intellij.psi.javadoc.PsiSnippetDocTagValue;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.Contract;
@@ -85,6 +86,7 @@ public class PsiSnippetDocTagImpl extends CompositePsiElement implements PsiSnip
@Contract(pure = true)
public @NotNull List<@NotNull TextRange> getContentRanges() {
boolean isMarkdown = PsiUtil.isInMarkdownDocComment(this);
final PsiSnippetDocTagValue valueElement = getValueElement();
if (valueElement == null) return Collections.emptyList();
@@ -104,15 +106,15 @@ public class PsiSnippetDocTagImpl extends CompositePsiElement implements PsiSnip
final String[] lines = snippetBodyTextRangeRelativeToSnippetTag.substring(getText()).split("\n");
if (lines.length == 0) return Collections.singletonList(snippetBodyTextRangeRelativeToSnippetTag);
return getRanges(snippetBodyTextRangeRelativeToSnippetTag, lines);
return getRanges(snippetBodyTextRangeRelativeToSnippetTag, lines, isMarkdown);
}
@Contract(pure = true)
private static @NotNull List<@NotNull TextRange> getRanges(@NotNull TextRange snippetBodyTextRangeRelativeToSnippet, String@NotNull [] lines) {
final int firstLine = getFirstNonEmptyLine(lines);
final int lastLine = getLastNonEmptyLine(lines);
private static @NotNull List<@NotNull TextRange> getRanges(@NotNull TextRange snippetBodyTextRangeRelativeToSnippet, String@NotNull [] lines, boolean isMarkdown) {
final int firstLine = getFirstNonEmptyLine(lines, isMarkdown);
final int lastLine = getLastNonEmptyLine(lines, isMarkdown);
int totalMinIndent = getIndent(lines, firstLine, lastLine);
int totalMinIndent = getIndent(lines, firstLine, lastLine, isMarkdown);
int startOffset = getStartOffsetOfFirstNonEmptyLine(snippetBodyTextRangeRelativeToSnippet, lines, firstLine);
@@ -166,16 +168,16 @@ public class PsiSnippetDocTagImpl extends CompositePsiElement implements PsiSnip
}
@Contract(pure = true)
private static @Range(from = 0, to = Integer.MAX_VALUE) int getIndent(String@NotNull [] lines, int firstLine, int lastLine) {
private static @Range(from = 0, to = Integer.MAX_VALUE) int getIndent(String@NotNull [] lines, int firstLine, int lastLine, boolean isMarkdown) {
int minIndent = Integer.MAX_VALUE;
for (int i = firstLine; i <= lastLine && i < lines.length; i++) {
String line = lines[i];
final int indentLength;
if (isEmptyOrSpacesWithLeadingAsterisksOnly(line)) {
if (isEmptyOrSpacesWithLeadingAsterisksOnly(line, isMarkdown)) {
indentLength = line.length();
}
else {
indentLength = calculateIndent(line);
indentLength = calculateIndent(line, isMarkdown);
}
if (minIndent > indentLength) minIndent = indentLength;
}
@@ -184,33 +186,33 @@ public class PsiSnippetDocTagImpl extends CompositePsiElement implements PsiSnip
}
@Contract(pure = true)
private static @Range(from = 0, to = Integer.MAX_VALUE) int getLastNonEmptyLine(String@NotNull[] lines) {
private static @Range(from = 0, to = Integer.MAX_VALUE) int getLastNonEmptyLine(String@NotNull[] lines, boolean isMarkdown) {
int lastLine = lines.length - 1;
while (lastLine > 0 && isEmptyOrSpacesWithLeadingAsterisksOnly(lines[lastLine])) {
while (lastLine > 0 && isEmptyOrSpacesWithLeadingAsterisksOnly(lines[lastLine], isMarkdown)) {
lastLine --;
}
return lastLine;
}
@Contract(pure = true)
private static @Range(from = 0, to = Integer.MAX_VALUE) int getFirstNonEmptyLine(String@NotNull[] lines) {
private static @Range(from = 0, to = Integer.MAX_VALUE) int getFirstNonEmptyLine(String@NotNull[] lines, boolean isMarkdown) {
int firstLine = 0;
while (firstLine < lines.length && isEmptyOrSpacesWithLeadingAsterisksOnly(lines[firstLine])) {
while (firstLine < lines.length && isEmptyOrSpacesWithLeadingAsterisksOnly(lines[firstLine], isMarkdown)) {
firstLine ++;
}
return firstLine;
}
@Contract(pure = true)
private static boolean isEmptyOrSpacesWithLeadingAsterisksOnly(@NotNull String lines) {
private static boolean isEmptyOrSpacesWithLeadingAsterisksOnly(@NotNull String lines, boolean isMarkdown) {
if (lines.isEmpty()) return true;
return lines.matches("^\\s*\\**\\s*$");
return lines.matches(isMarkdown ? "^\\s*///\\s*$" : "^\\s*\\**\\s*$");
}
@Contract(pure = true)
private static @Range(from = 0, to = Integer.MAX_VALUE) int calculateIndent(@NotNull String content) {
private static @Range(from = 0, to = Integer.MAX_VALUE) int calculateIndent(@NotNull String content, boolean isMarkdown) {
if (content.isEmpty()) return 0;
final String noIndent = content.replaceAll("^\\s*\\*\\s*", "");
final String noIndent = content.replaceAll(isMarkdown ? "^\\s*///\\s*" : "^\\s*\\*\\s*", "");
return content.length() - noIndent.length();
}