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

@@ -3,9 +3,9 @@ package com.siyeh.ig.javadoc;
import com.intellij.codeInsight.javadoc.JavaDocUtil;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
import com.intellij.codeInspection.options.OptPane;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocComment;
@@ -13,6 +13,7 @@ import com.intellij.psi.javadoc.PsiDocToken;
import com.intellij.psi.templateLanguages.TemplateLanguageUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
@@ -75,6 +76,9 @@ public final class DanglingJavadocInspection extends BaseInspection {
else if (!JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS.equals(tokenType)) {
newCommentText.append(child.getText());
}
else if (PsiUtil.isInMarkdownDocComment(docToken)) {
newCommentText.append("//");
}
}
else {
newCommentText.append(child.getText());
@@ -111,18 +115,9 @@ public final class DanglingJavadocInspection extends BaseInspection {
@Override
public void visitDocComment(@NotNull PsiDocComment comment) {
super.visitDocComment(comment);
if (comment.getOwner() != null || TemplateLanguageUtil.isInsideTemplateFile(comment)) {
return;
if (JavaDocUtil.isDanglingDocComment(comment, ignoreCopyright)) {
registerError(comment.getFirstChild());
}
if (JavaDocUtil.isInsidePackageInfo(comment) &&
PsiTreeUtil.skipWhitespacesAndCommentsForward(comment) instanceof PsiPackageStatement &&
"package-info.java".equals(comment.getContainingFile().getName())) {
return;
}
if (ignoreCopyright && comment.getPrevSibling() == null && comment.getParent() instanceof PsiFile) {
return;
}
registerError(comment.getFirstChild());
}
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.lexer;
import com.intellij.psi.tree.IElementType;
/**
* Describes JDoc comment specific to the Java Plugin (not part of the core-api)
*/
public interface JavaDocCommentTokenTypes extends DocCommentTokenTypes {
default IElementType codeFence() {
return commentData();
}
default IElementType rightBracket() {
return commentData();
}
default IElementType leftBracket() {
return commentData();
}
default IElementType leftParenthesis() {
return commentData();
}
default IElementType rightParenthesis() {
return commentData();
}
default IElementType sharp() {
return commentData();
}
}

View File

@@ -5,8 +5,8 @@ import com.intellij.psi.JavaDocTokenType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
public final class JavaDocTokenTypes implements DocCommentTokenTypes {
public static final DocCommentTokenTypes INSTANCE = new JavaDocTokenTypes();
public final class JavaDocTokenTypes implements JavaDocCommentTokenTypes {
public static final JavaDocCommentTokenTypes INSTANCE = new JavaDocTokenTypes();
private final TokenSet mySpaceCommentsSet = TokenSet.create(JavaDocTokenType.DOC_SPACE, JavaDocTokenType.DOC_COMMENT_DATA);
private JavaDocTokenTypes() { }

View File

@@ -2,13 +2,13 @@
// source: _JavaDocLexer.flex
/* It's an automatically generated code. Do not modify it. */
package com.intellij.lang.java.lexer;
package com.intellij.lang.java.lexer;
import com.intellij.lexer.DocCommentTokenTypes;
import com.intellij.lexer.FlexLexer;
import com.intellij.psi.tree.IElementType;
import com.intellij.lexer.JavaDocCommentTokenTypes;
import com.intellij.lexer.FlexLexer;
import com.intellij.psi.tree.IElementType;
@SuppressWarnings("ALL")
@SuppressWarnings("ALL")
class _JavaDocLexer implements FlexLexer {
@@ -565,31 +565,31 @@ class _JavaDocLexer implements FlexLexer {
/* user code: */
private boolean myJdk15Enabled;
private DocCommentTokenTypes myTokenTypes;
private int mySnippetBracesLevel = 0;
/* Enable markdown support for java 23 */
private boolean myMarkdownMode = false;
private JavaDocCommentTokenTypes myTokenTypes;
private int mySnippetBracesLevel = 0;
/* Enable markdown support for java 23 */
private boolean myMarkdownMode = false;
public _JavaDocLexer(boolean isJdk15Enabled, DocCommentTokenTypes tokenTypes) {
this((java.io.Reader)null);
myJdk15Enabled = isJdk15Enabled;
myTokenTypes = tokenTypes;
}
public _JavaDocLexer(boolean isJdk15Enabled, JavaDocCommentTokenTypes tokenTypes) {
this((java.io.Reader)null);
myJdk15Enabled = isJdk15Enabled;
myTokenTypes = tokenTypes;
}
/** Should be called right after a reset */
public void setMarkdownMode(boolean isEnabled) {
myMarkdownMode = isEnabled;
}
/** Should be called right after a reset */
public void setMarkdownMode(boolean isEnabled) {
myMarkdownMode = isEnabled;
}
public boolean checkAhead(char c) {
if (zzMarkedPos >= zzBuffer.length()) return false;
return zzBuffer.charAt(zzMarkedPos) == c;
}
public boolean checkAhead(char c) {
if (zzMarkedPos >= zzBuffer.length()) return false;
return zzBuffer.charAt(zzMarkedPos) == c;
}
public void goTo(int offset) {
zzCurrentPos = zzMarkedPos = zzStartRead = offset;
zzAtEOF = false;
}
public void goTo(int offset) {
zzCurrentPos = zzMarkedPos = zzStartRead = offset;
zzAtEOF = false;
}
/**

View File

@@ -1,10 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.lang.java.lexer;
import com.intellij.lexer.DocCommentTokenTypes;
import com.intellij.lexer.JavaDocTokenTypes;
import com.intellij.lexer.LexerBase;
import com.intellij.lexer.MergingLexerAdapter;
import com.intellij.lexer.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.tree.IElementType;
@@ -18,7 +15,7 @@ public class JavaDocLexer extends MergingLexerAdapter {
this(JavaDocTokenTypes.INSTANCE, level.isAtLeast(LanguageLevel.JDK_1_5));
}
private JavaDocLexer(DocCommentTokenTypes tokenTypes, boolean isJdk15Enabled) {
private JavaDocLexer(JavaDocCommentTokenTypes tokenTypes, boolean isJdk15Enabled) {
super(new AsteriskStripperLexer(new _JavaDocLexer(isJdk15Enabled, tokenTypes), tokenTypes), tokenTypes.spaceCommentsTokenSet());
}

View File

@@ -1,40 +1,40 @@
/* It's an automatically generated code. Do not modify it. */
package com.intellij.lang.java.lexer;
package com.intellij.lang.java.lexer;
import com.intellij.lexer.DocCommentTokenTypes;
import com.intellij.lexer.FlexLexer;
import com.intellij.psi.tree.IElementType;
import com.intellij.lexer.JavaDocCommentTokenTypes;
import com.intellij.lexer.FlexLexer;
import com.intellij.psi.tree.IElementType;
@SuppressWarnings("ALL")
@SuppressWarnings("ALL")
%%
%{
private boolean myJdk15Enabled;
private DocCommentTokenTypes myTokenTypes;
private int mySnippetBracesLevel = 0;
/* Enable markdown support for java 23 */
private boolean myMarkdownMode = false;
private JavaDocCommentTokenTypes myTokenTypes;
private int mySnippetBracesLevel = 0;
/* Enable markdown support for java 23 */
private boolean myMarkdownMode = false;
public _JavaDocLexer(boolean isJdk15Enabled, DocCommentTokenTypes tokenTypes) {
this((java.io.Reader)null);
myJdk15Enabled = isJdk15Enabled;
myTokenTypes = tokenTypes;
}
public _JavaDocLexer(boolean isJdk15Enabled, JavaDocCommentTokenTypes tokenTypes) {
this((java.io.Reader)null);
myJdk15Enabled = isJdk15Enabled;
myTokenTypes = tokenTypes;
}
/** Should be called right after a reset */
public void setMarkdownMode(boolean isEnabled) {
myMarkdownMode = isEnabled;
}
/** Should be called right after a reset */
public void setMarkdownMode(boolean isEnabled) {
myMarkdownMode = isEnabled;
}
public boolean checkAhead(char c) {
if (zzMarkedPos >= zzBuffer.length()) return false;
return zzBuffer.charAt(zzMarkedPos) == c;
}
public boolean checkAhead(char c) {
if (zzMarkedPos >= zzBuffer.length()) return false;
return zzBuffer.charAt(zzMarkedPos) == c;
}
public void goTo(int offset) {
zzCurrentPos = zzMarkedPos = zzStartRead = offset;
zzAtEOF = false;
}
public void goTo(int offset) {
zzCurrentPos = zzMarkedPos = zzStartRead = offset;
zzAtEOF = false;
}
%}
%class _JavaDocLexer

View File

@@ -1150,6 +1150,7 @@
<filePasteProvider implementation="com.intellij.ide.JavaFilePasteProvider" order="before fileList"/>
<enterHandlerDelegate implementation="com.intellij.javadoc.EnterInJavadocParamDescriptionHandler"/>
<enterHandlerDelegate implementation="com.intellij.javadoc.JavadocSnippetEnterHandler" order="before EnterBetweenBracesHandler"/>
<enterHandlerDelegate implementation="com.intellij.javadoc.JavaDocMarkdownEnterHandler" order="after EnterInLineCommentHandler" />
<editorNavigation implementation="com.intellij.javadoc.JavadocNavigationDelegate"/>
<methodNavigationOffsetProvider implementation="com.intellij.codeInsight.navigation.JavaMethodNavigationOffsetProvider"/>
<dom.customAnnotationChecker implementation="com.intellij.util.xml.impl.ExtendsClassChecker"/>

View File

@@ -7,6 +7,7 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.documentation.DocumentationManagerProtocol;
import com.intellij.codeInsight.documentation.DocumentationManagerUtil;
import com.intellij.codeInsight.javadoc.markdown.JavaDocMarkdownFlavourDescriptor;
import com.intellij.ide.highlighter.HtmlFileType;
import com.intellij.java.JavaBundle;
import com.intellij.javadoc.JavadocGeneratorRunProfile;
@@ -1807,16 +1808,14 @@ public class JavaDocInfoGenerator {
int startIndex,
InheritDocProvider<PsiElement[]> provider) {
int predictOffset = startIndex < elements.length ? elements[startIndex].getTextOffset() + elements[startIndex].getText().length() : 0;
boolean wasMarkdown = false, isMarkdown = false;
// Secondary buffer to flush at each switch between (non-)markdown content
boolean isMarkdown = startIndex < elements.length && PsiUtil.isInMarkdownDocComment(elements[startIndex]);
StringBuilder subBuffer = new StringBuilder();
StringBuilder htmlCodeBlockContents = null;
Pair<String, String> htmlCodeBlockDelimiters = null;
for (int i = startIndex; i < elements.length; i++) {
isMarkdown = PsiUtil.isInMarkdownDocComment(elements[i]);
if (elements[i].getTextOffset() > predictOffset) {
if (htmlCodeBlockContents != null) {
htmlCodeBlockContents.append(' ');
@@ -1853,10 +1852,8 @@ public class JavaDocInfoGenerator {
if (provider == null) continue;
Pair<PsiElement[], InheritDocProvider<PsiElement[]>> inheritInfo = provider.getInheritDoc();
if (inheritInfo != null) {
isMarkdown = PsiUtil.isInMarkdownDocComment(inheritInfo.first[0]);
flushIfNecessary(buffer, subBuffer, wasMarkdown, isMarkdown);
wasMarkdown = isMarkdown;
generateValue(subBuffer, inheritInfo.first, inheritInfo.second);
flushSubBuffer(buffer, subBuffer, isMarkdown);
generateValue(buffer, inheritInfo.first, inheritInfo.second);
}
}
case DOC_ROOT_TAG -> subBuffer.append(getDocRoot());
@@ -1867,9 +1864,11 @@ public class JavaDocInfoGenerator {
case RETURN_TAG -> generateInlineReturnValue(subBuffer, tag, provider);
default -> generateUnknownInlineTagValue(subBuffer, tag);
}
} else if (element instanceof PsiMarkdownCodeBlock) {
appendStyledCodeBlock(subBuffer, element.getProject(), element.getLanguage(), ((PsiMarkdownCodeBlock)element).getCodeText());
} else if (element instanceof PsiMarkdownReferenceLink) {
}
else if (element instanceof PsiMarkdownCodeBlock markdownCodeBlock) {
appendStyledCodeBlock(subBuffer, element.getProject(), markdownCodeBlock.getCodeLanguage(), markdownCodeBlock.getCodeText());
}
else if (element instanceof PsiMarkdownReferenceLink) {
generateMarkdownLinkValue((PsiMarkdownReferenceLink)element, subBuffer);
}
else {
@@ -1907,24 +1906,19 @@ public class JavaDocInfoGenerator {
}
}
}
flushIfNecessary(buffer, subBuffer, wasMarkdown, isMarkdown);
wasMarkdown = isMarkdown;
}
if (htmlCodeBlockContents != null) {
subBuffer.append(htmlCodeBlockDelimiters.first);
appendPlainText(subBuffer, htmlCodeBlockContents.toString());
}
buffer.append(isMarkdown ? markdownToHtml(subBuffer.toString()) : subBuffer );
flushSubBuffer(buffer, subBuffer, isMarkdown);
}
@Contract(mutates = "param1, param2")
private static void flushIfNecessary(StringBuilder buffer, StringBuilder subBuffer, boolean wasMarkdown, boolean isMarkdown){
if(wasMarkdown != isMarkdown) {
buffer.append(wasMarkdown ? markdownToHtml(subBuffer.toString()) : subBuffer );
subBuffer.setLength(0);
}
private static void flushSubBuffer(StringBuilder buffer, StringBuilder subBuffer, boolean flushAsMarkdown) {
buffer.append(flushAsMarkdown ? markdownToHtml(subBuffer.toString()) : subBuffer);
subBuffer.setLength(0);
}
private @Nullable StringBuilder appendHtmlCodeBlockContents(@NotNull String text, @NotNull StringBuilder buffer,

View File

@@ -1,5 +1,5 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.javadoc;
package com.intellij.codeInsight.javadoc.markdown;
import org.intellij.markdown.IElementType;
import org.intellij.markdown.MarkdownElementTypes;
@@ -10,6 +10,7 @@ import org.intellij.markdown.html.GeneratingProvider;
import org.intellij.markdown.html.HtmlGenerator;
import org.intellij.markdown.html.TrimmingInlineHolderProvider;
import org.intellij.markdown.parser.LinkMap;
import org.intellij.markdown.parser.MarkerProcessorFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -29,9 +30,9 @@ public class JavaDocMarkdownFlavourDescriptor extends GFMFlavourDescriptor {
@NotNull
@Override
public Map<IElementType, GeneratingProvider> createHtmlGeneratingProviders(@NotNull LinkMap linkMap, @Nullable URI baseURI) {
Map<IElementType, GeneratingProvider> result = new HashMap<>(super.createHtmlGeneratingProviders(linkMap, baseURI));
Map<IElementType, GeneratingProvider> result = new HashMap<>(super.createHtmlGeneratingProviders(linkMap, baseURI));
// We don't generate a body
// We don't generate a body
result.remove(MarkdownElementTypes.MARKDOWN_FILE);
// Paragraphs may need to be inlined
@@ -40,12 +41,18 @@ public class JavaDocMarkdownFlavourDescriptor extends GFMFlavourDescriptor {
return result;
}
@NotNull
@Override
public MarkerProcessorFactory getMarkerProcessorFactory() {
return new JavaDocMarkerProcessorFactory();
}
private static class JavaDocParagraphProvider extends TrimmingInlineHolderProvider {
private static final CharSequence[] EMPTY_ARRAY = new CharSequence[0];
/** @return Whether or no the paragraph is small and thus should be rendered inline */
private static boolean isSmallParagraph(@NotNull ASTNode node) {
for(ASTNode child : node.getChildren()) {
for (ASTNode child : node.getChildren()) {
if (child.getType() == MarkdownTokenTypes.EOL) {
return false;
}
@@ -56,7 +63,7 @@ public class JavaDocMarkdownFlavourDescriptor extends GFMFlavourDescriptor {
// Check if the next thing is an EOL
List<ASTNode> siblings = parent.getChildren();
ASTNode nextSibling = siblings.get(Math.min(siblings.size() - 1, siblings.indexOf(node) + 1));
if(nextSibling != null && nextSibling.getType() == MarkdownTokenTypes.EOL) {
if (nextSibling != null && nextSibling.getType() == MarkdownTokenTypes.EOL) {
return false;
}
}

View File

@@ -0,0 +1,46 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.javadoc.markdown;
import org.intellij.markdown.flavours.commonmark.CommonMarkMarkerProcessor;
import org.intellij.markdown.flavours.gfm.table.GitHubTableMarkerProvider;
import org.intellij.markdown.parser.MarkerProcessor;
import org.intellij.markdown.parser.MarkerProcessorFactory;
import org.intellij.markdown.parser.ProductionHolder;
import org.intellij.markdown.parser.constraints.CommonMarkdownConstraints;
import org.intellij.markdown.parser.markerblocks.MarkerBlockProvider;
import org.intellij.markdown.parser.markerblocks.providers.*;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
public class JavaDocMarkerProcessorFactory implements MarkerProcessorFactory {
@NotNull
@Override
public MarkerProcessor<?> createMarkerProcessor(@NotNull ProductionHolder holder) {
return new JavaDocMarkerProcessor(holder);
}
/**
* Marker processor taking account of changes brought to Common Markdown by the JEP-467
* Namely: no indented code blocks, tables from Github Markdown
*/
public static class JavaDocMarkerProcessor extends CommonMarkMarkerProcessor {
public JavaDocMarkerProcessor(@NotNull ProductionHolder productionHolder) {
super(productionHolder, CommonMarkdownConstraints.Companion.getBASE());
}
@NotNull
@Override
protected List<MarkerBlockProvider<StateInfo>> getMarkerBlockProviders() {
return Arrays.asList(new HorizontalRuleProvider(),
new CodeFenceProvider(),
new SetextHeaderProvider(),
new BlockQuoteProvider(),
new ListMarkerProvider(),
new AtxHeaderProvider(),
new HtmlBlockProvider(),
new GitHubTableMarkerProvider());
}
}
}

View File

@@ -17,6 +17,7 @@ import com.intellij.psi.javadoc.PsiDocToken;
import com.intellij.psi.javadoc.PsiInlineDocTag;
import com.intellij.psi.javadoc.PsiSnippetDocTagBody;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.xml.util.HtmlUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -38,7 +39,9 @@ public final class JavadocBlankLinesInspection extends LocalInspectionTool {
nextWhitespace instanceof PsiWhiteSpace &&
!isAfterParagraphOrBlockTag(prevWhitespace) &&
!isBeforeParagraphOrBlockTag(nextWhitespace) &&
!isAfterPreTag(token)) {
!isAfterPreTag(token) &&
!PsiUtil.isInMarkdownDocComment(token)
) {
holder.problem(token, JavaBundle.message("inspection.javadoc.blank.lines.message"))
.fix(new InsertParagraphTagFix(token)).register();
}

View File

@@ -0,0 +1,61 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.javadoc;
import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter;
import com.intellij.codeInsight.javadoc.JavaDocUtil;
import com.intellij.openapi.actionSystem.DataContext;
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.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMember;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocToken;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
public class JavaDocMarkdownEnterHandler extends EnterHandlerDelegateAdapter {
@Override
public Result preprocessEnter(@NotNull PsiFile file,
@NotNull Editor editor,
@NotNull Ref<Integer> caretOffset,
@NotNull Ref<Integer> caretAdvance,
@NotNull DataContext dataContext,
EditorActionHandler originalHandler) {
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 (!((currentElement instanceof PsiDocToken token) && shouldInsertLeadingTokens(token))) {
return Result.Continue;
}
Document document = editor.getDocument();
document.insertString(caretOffset.get(), "///");
caretAdvance.set(3);
return Result.DefaultForceIndent;
}
/**
* Verifies whether we should automatically add the leading slashes
*
* @param element a doc element found at the caret offset
* @return If the javadoc is tied to a method/a class it should return true otherwise false
*/
private static boolean shouldInsertLeadingTokens(PsiDocToken element) {
PsiDocComment docComment = PsiTreeUtil.getParentOfType(element, PsiDocComment.class, false, PsiMember.class);
if (docComment == null || !docComment.isMarkdownComment()) return false;
return !JavaDocUtil.isDanglingDocComment(docComment, true);
}
}

View File

@@ -19,6 +19,7 @@ import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.impl.source.javadoc.PsiSnippetDocTagImpl;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.javadoc.PsiSnippetDocTag;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.DocumentUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.text.CharArrayUtil;
@@ -39,7 +40,7 @@ public final class JavadocSnippetEnterHandler extends EnterHandlerDelegateAdapte
final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(file.getProject());
final PsiSnippetDocTag host = ObjectUtils.tryCast(injectedLanguageManager.getInjectionHost(file), PsiSnippetDocTag.class);
return host == null ? Result.Continue : Result.Default;
return (host == null || PsiUtil.isInMarkdownDocComment(host)) ? Result.Continue : Result.Default;
}
@Override

View File

@@ -1,12 +1,18 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.javadoc;
import com.intellij.lang.Language;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/** Describes a markdown code block */
public interface PsiMarkdownCodeBlock extends PsiElement {
/** @return The text of the code block, without superfluous elements */
@NotNull String getCodeText();
@NotNull
String getCodeText();
/** @return The language for this code block */
@Nullable Language getCodeLanguage();
}

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();
}

View File

@@ -1 +1 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">MarkdownFeatures</span></pre></div><div class='content'> <h1>Title1</h1><h2>Title2</h2><h3>Title 3</h3><p><em>italic</em> <strong>strong</strong> <code>code</code></p>Separate paragraph</div><table class='sections'><p></table>
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">MarkdownFeatures</span></pre></div><div class='content'><h1>Title1</h1><h2>Title2</h2><h3>Title 3</h3><p><em>italic</em> <strong>strong</strong> <code>code</code></p>Separate paragraph</div><table class='sections'><p></table>

View File

@@ -1,4 +1,3 @@
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class">&nbsp;<a href="psi_element://MarkdownInheritedDoc"><code><span style="color:#000000;">MarkdownInheritedDoc</span></code></a></div><div class='definition'><pre><span style="color:#000080;font-weight:bold;">void</span>&nbsp;<span style="color:#000000;">foo</span><span style="">(</span><span style="">)</span></pre></div><div class='content'> <p>Markdown variant
</p> I am legacy javadoc, I hope no one disturbs my _underlines_ and **astrerisks**
<p>More <em>markdown</em>
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class">&nbsp;<a href="psi_element://MarkdownInheritedDoc"><code><span style="color:#000000;">MarkdownInheritedDoc</span></code></a></div><div class='definition'><pre><span style="color:#000080;font-weight:bold;">void</span>&nbsp;<span style="color:#000000;">foo</span><span style="">(</span><span style="">)</span></pre></div><div class='content'><p>Markdown variant
</p> I am legacy javadoc, I hope no one disturbs my _underlines_ and **astrerisks** <p>More <em>markdown</em>
syntax</p></div><table class='sections'><tr><td valign='top' class='section'><p>Overrides:</td><td valign='top'><p><a href="psi_element://MarkdownInheritDoc#foo()"><code><span style="color:#000000;">foo</span></code></a> in class <a href="psi_element://MarkdownInheritDoc"><code><span style="color:#000000;">MarkdownInheritDoc</span></code></a></td></table>

View File

@@ -0,0 +1 @@
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class">&nbsp;<a href="psi_element://InlineWithTagsMarkdown"><code><span style="color:#000000;">InlineWithTagsMarkdown</span></code></a></div><div class='definition'><pre><span style="color:#000080;font-weight:bold;">boolean</span>&nbsp;<span style="color:#000000;">foo</span><span style="">(</span><br> <span style="color:#000080;font-weight:bold;">int</span>&nbsp;<span style="color:#000000;">bar</span><br><span style="">)</span></pre></div><table class='sections'><p><tr><td valign='top' class='section'><p>Params:</td><td valign='top'><code><span style="color:#000000;">bar</span></code> &ndash; an int, set to <code>-1</code></td><tr><td valign='top' class='section'><p>Returns:</td><td valign='top'><p><em>Some value</em></td></table>

View File

@@ -0,0 +1,7 @@
class InlineWithTagsMarkdown {
/// @param bar an int, set to `-1`
/// @return _Some value_
boolean foo(int bar) {
return true;
}
}

View File

@@ -1 +1 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">MarkdownCodeBlock</span></pre></div><div class='content'> <p><a href="psi_element://java.util.HashMap"><code><span style="color:#0000ff;">java.util.HashMap</span></code></a></p><a href="psi_element://java.util.Collections#EMPTY_LIST"><span style="color:#660e7a;">The default empty collection</span></a></div><table class='sections'><p></table>
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">MarkdownCodeBlock</span></pre></div><div class='content'><p><a href="psi_element://java.util.HashMap"><code><span style="color:#0000ff;">java.util.HashMap</span></code></a></p><a href="psi_element://java.util.Collections#EMPTY_LIST"><span style="color:#660e7a;">The default empty collection</span></a></div><table class='sections'><p></table>

View File

@@ -191,6 +191,11 @@ public class JavaDocInfoGeneratorTest extends JavaCodeInsightTestCase {
PsiClass outerClass = ((PsiJavaFile) myFile).getClasses()[1];
verifyJavaDoc(outerClass.getMethods()[0]);
}
public void testMarkdownInlineWithTags(){
configureByFile();
PsiClass outerClass = ((PsiJavaFile) myFile).getClasses()[0];
verifyJavaDoc(outerClass.getMethods()[0]);
}
public void testRepeatableAnnotations() {
useJava8();

View File

@@ -905,18 +905,12 @@ c:com.intellij.lexer.DelegateLexer
- start(java.lang.CharSequence,I,I,I):V
com.intellij.lexer.DocCommentTokenTypes
- a:badCharacter():com.intellij.psi.tree.IElementType
- codeFence():com.intellij.psi.tree.IElementType
- a:commentData():com.intellij.psi.tree.IElementType
- a:commentEnd():com.intellij.psi.tree.IElementType
- a:commentLeadingAsterisks():com.intellij.psi.tree.IElementType
- a:commentStart():com.intellij.psi.tree.IElementType
- a:inlineTagEnd():com.intellij.psi.tree.IElementType
- a:inlineTagStart():com.intellij.psi.tree.IElementType
- leftBracket():com.intellij.psi.tree.IElementType
- leftParenthesis():com.intellij.psi.tree.IElementType
- rightBracket():com.intellij.psi.tree.IElementType
- rightParenthesis():com.intellij.psi.tree.IElementType
- sharp():com.intellij.psi.tree.IElementType
- a:space():com.intellij.psi.tree.IElementType
- a:spaceCommentsTokenSet():com.intellij.psi.tree.TokenSet
- a:tagName():com.intellij.psi.tree.IElementType

View File

@@ -26,24 +26,6 @@ public interface DocCommentTokenTypes {
IElementType inlineTagEnd();
IElementType badCharacter();
IElementType commentLeadingAsterisks();
default IElementType codeFence() {
return commentData();
}
default IElementType rightBracket() {
return commentData();
}
default IElementType leftBracket() {
return commentData();
}
default IElementType leftParenthesis() {
return commentData();
}
default IElementType rightParenthesis() {
return commentData();
}
default IElementType sharp() {
return commentData();
}
default IElementType tagValueQuote() {
return commentData();
}

View File

@@ -808,7 +808,7 @@
<statementUpDownMover implementation="com.intellij.codeInsight.editorActions.moveUpDown.LineMover" id="line" order="last"/>
<enterHandlerDelegate implementation="com.intellij.codeInsight.editorActions.enter.EnterInStringLiteralHandler"/>
<enterHandlerDelegate implementation="com.intellij.codeInsight.editorActions.enter.EnterInLineCommentHandler"/>
<enterHandlerDelegate implementation="com.intellij.codeInsight.editorActions.enter.EnterInLineCommentHandler" id="EnterInLineCommentHandler"/>
<enterHandlerDelegate implementation="com.intellij.codeInsight.editorActions.enter.EnterInBlockCommentHandler" id="blockComment"
order="last"/>
<enterHandlerDelegate implementation="com.intellij.codeInsight.editorActions.enter.EnterAfterUnmatchedBraceHandler"