Skip to content

Commit

Permalink
Completion for Javadoc tags
Browse files Browse the repository at this point in the history
- Both block and inline tags
- Take into account which ones are applicable to the declaration being
  Javadocumented

Fixes #948

Signed-off-by: David Thompson <[email protected]>
  • Loading branch information
datho7561 committed Nov 15, 2024
1 parent a12a2d7 commit fff29f4
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class DOMCompletionContext extends CompletionContext {
private final Supplier<Stream<IBinding>> bindingsAcquirer;
private final ExpectedTypes expectedTypes;

private boolean inJavadoc = false;

DOMCompletionContext(int offset, char[] token, IJavaElement enclosingElement,
Supplier<Stream<IBinding>> bindingHaver, ExpectedTypes expectedTypes) {
Expand All @@ -52,6 +53,15 @@ public char[] getToken() {
return this.token;
}

@Override
public boolean isInJavadoc() {
return this.inJavadoc;
}

public void setInJavadoc(boolean inJavadoc) {
this.inJavadoc = inJavadoc;
}

@Override
public IJavaElement getEnclosingElement() {
return this.enclosingElement;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,17 @@ public void run() {
if (simpleName.getParent() instanceof SimpleType simpleType && (simpleType.getParent() instanceof ClassInstanceCreation)) {
context = simpleName.getParent().getParent();
}
} else if (this.toComplete instanceof SimpleType simpleType) {
} else if (this.toComplete instanceof TextElement textElement) {
int charCount = this.offset - textElement.getStartPosition();
completeAfter = textElement.getText().substring(0, textElement.getText().length() <= charCount ? textElement.getText().length() : charCount);
context = textElement.getParent();
} else if (this.toComplete instanceof TagElement tagElement) {
completeAfter = tagElement.getTagName();
int atIndex = completeAfter.indexOf('@');
if (atIndex >= 0) {
completeAfter = completeAfter.substring(atIndex + 1);
}
} if (this.toComplete instanceof SimpleType simpleType) {
if (FAKE_IDENTIFIER.equals(simpleType.getName().toString())) {
context = this.toComplete.getParent();
} else if (simpleType.getName() instanceof QualifiedName qualifiedName) {
Expand Down Expand Up @@ -621,6 +631,12 @@ public void run() {
}
suggestDefaultCompletions = false;
}
if (context instanceof TagElement tagElement) {
completionContext.setInJavadoc(true);
completeJavadocBlockTags(tagElement);
completeJavadocInlineTags(tagElement);
suggestDefaultCompletions = false;
}

// check for accessible bindings to potentially turn into completions.
// currently, this is always run, even when not using the default completion,
Expand Down Expand Up @@ -739,6 +755,28 @@ private void statementLikeKeywords() {
}
}

private void completeJavadocBlockTags(TagElement tagNode) {
if (this.requestor.isIgnored(CompletionProposal.JAVADOC_BLOCK_TAG)) {
return;
}
for (char[] blockTag : DOMCompletionEngineJavadocUtil.getJavadocBlockTags(this.modelUnit.getJavaProject(), tagNode)) {
if (!isFailedMatch(this.prefix.toCharArray(), blockTag)) {
this.requestor.accept(toJavadocBlockTagProposal(blockTag));
}
}
}

private void completeJavadocInlineTags(TagElement tagNode) {
if (this.requestor.isIgnored(CompletionProposal.JAVADOC_INLINE_TAG)) {
return;
}
for (char[] blockTag : DOMCompletionEngineJavadocUtil.getJavadocInlineTags(this.modelUnit.getJavaProject(), tagNode)) {
if (!isFailedMatch(this.prefix.toCharArray(), blockTag)) {
this.requestor.accept(toJavadocInlineTagProposal(blockTag));
}
}
}

private void completeThrowsClause(MethodDeclaration methodDeclaration, Bindings scope) {
if (methodDeclaration.thrownExceptionTypes().size() == 0) {
if (!isFailedMatch(this.prefix.toCharArray(), Keywords.THROWS)) {
Expand Down Expand Up @@ -1646,6 +1684,42 @@ private CompletionProposal toAnnotationAttributeRefProposal(IMethodBinding metho
return proposal;
}

private CompletionProposal toJavadocBlockTagProposal(char[] blockTag) {
InternalCompletionProposal res = createProposal(CompletionProposal.JAVADOC_BLOCK_TAG);
res.setName(blockTag);
StringBuilder completion = new StringBuilder();
completion.append('@');
completion.append(blockTag);
res.setCompletion(completion.toString().toCharArray());
setRange(res);
ASTNode replaceNode = this.toComplete;
if (replaceNode instanceof TextElement) {
replaceNode = replaceNode.getParent();
}
res.setReplaceRange(replaceNode.getStartPosition(), replaceNode.getStartPosition() + replaceNode.getLength());
res.setRelevance(RelevanceConstants.R_DEFAULT + RelevanceConstants.R_INTERESTING + RelevanceConstants.R_NON_RESTRICTED);
return res;
}

private CompletionProposal toJavadocInlineTagProposal(char[] inlineTag) {
InternalCompletionProposal res = createProposal(CompletionProposal.JAVADOC_INLINE_TAG);
res.setName(inlineTag);
StringBuilder completion = new StringBuilder();
completion.append('{');
completion.append('@');
completion.append(inlineTag);
completion.append('}');
res.setCompletion(completion.toString().toCharArray());
setRange(res);
ASTNode replaceNode = this.toComplete;
if (replaceNode instanceof TextElement) {
replaceNode = replaceNode.getParent();
}
res.setReplaceRange(replaceNode.getStartPosition(), replaceNode.getStartPosition() + replaceNode.getLength());
res.setRelevance(RelevanceConstants.R_DEFAULT + RelevanceConstants.R_INTERESTING + RelevanceConstants.R_NON_RESTRICTED);
return res;
}

private void configureProposal(InternalCompletionProposal proposal, ASTNode completing) {
proposal.setReplaceRange(completing.getStartPosition(), this.offset);
proposal.completionEngine = this.nestedEngine;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc. and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.jdt.internal.codeassist;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.internal.compiler.parser.JavadocTagConstants;

class DOMCompletionEngineJavadocUtil {

// block tags

private static final List<char[]> JAVA_8_BLOCK_TAGS;
static {
JAVA_8_BLOCK_TAGS = new ArrayList<>();
for (int i = 0 ; i < 4 ; i++) {
for (char[] entry : JavadocTagConstants.BLOCK_TAGS_RAW[i].tags()) {
JAVA_8_BLOCK_TAGS.add(entry);
}
}
}
private static final List<char[]> JAVA_9_BLOCK_TAGS;
static {
JAVA_9_BLOCK_TAGS = new ArrayList<>();
JAVA_9_BLOCK_TAGS.addAll(JAVA_8_BLOCK_TAGS);
for (char[] entry : JavadocTagConstants.BLOCK_TAGS_RAW[4].tags()) {
JAVA_9_BLOCK_TAGS.add(entry);
}
}

// inline tags

private static final List<char[]> JAVA_8_INLINE_TAGS;
static {
JAVA_8_INLINE_TAGS = new ArrayList<>();
for (int i = 0 ; i < 4 ; i++) {
for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[i].tags()) {
JAVA_8_INLINE_TAGS.add(entry);
}
}
}

private static final List<char[]> JAVA_9_INLINE_TAGS;
static {
JAVA_9_INLINE_TAGS = new ArrayList<>();
JAVA_9_INLINE_TAGS.addAll(JAVA_8_INLINE_TAGS);
for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[4].tags()) {
JAVA_9_INLINE_TAGS.add(entry);
}
}

private static final List<char[]> JAVA_10_INLINE_TAGS;
static {
JAVA_10_INLINE_TAGS = new ArrayList<>();
JAVA_10_INLINE_TAGS.addAll(JAVA_9_INLINE_TAGS);
for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[5].tags()) {
JAVA_10_INLINE_TAGS.add(entry);
}
}

private static final List<char[]> JAVA_12_INLINE_TAGS;
static {
JAVA_12_INLINE_TAGS = new ArrayList<>();
JAVA_12_INLINE_TAGS.addAll(JAVA_10_INLINE_TAGS);
for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[6].tags()) {
JAVA_12_INLINE_TAGS.add(entry);
}
}

private static final List<char[]> JAVA_16_INLINE_TAGS;
static {
JAVA_16_INLINE_TAGS = new ArrayList<>();
JAVA_16_INLINE_TAGS.addAll(JAVA_12_INLINE_TAGS);
for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[7].tags()) {
JAVA_16_INLINE_TAGS.add(entry);
}
}

private static final List<char[]> JAVA_18_INLINE_TAGS;
static {
JAVA_18_INLINE_TAGS = new ArrayList<>();
JAVA_18_INLINE_TAGS.addAll(JAVA_16_INLINE_TAGS);
for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[8].tags()) {
JAVA_18_INLINE_TAGS.add(entry);
}
}

public static List<char[]> getJavadocBlockTags(IJavaProject project, TagElement tagNode) {
List<char[]> tagsForVersion;
String projectVersion = project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
if (projectVersion.contains(".") || Integer.parseInt(projectVersion) < 9) { //$NON-NLS-1$
tagsForVersion = JAVA_8_BLOCK_TAGS;
} else {
tagsForVersion = JAVA_9_BLOCK_TAGS;
}

return tagsForNode(tagsForVersion, tagNode);
}

public static List<char[]> getJavadocInlineTags(IJavaProject project, TagElement tagNode) {
List<char[]> tagsForVersion;
String projectVersion = project.getOption(JavaCore.COMPILER_COMPLIANCE, true);
if (projectVersion.contains(".")) { //$NON-NLS-1$
tagsForVersion = JAVA_8_INLINE_TAGS;
} else {
int versionNumber = Integer.parseInt(projectVersion);
if (versionNumber < 9) {
tagsForVersion = JAVA_8_INLINE_TAGS;
} else if (versionNumber < 10) {
tagsForVersion = JAVA_9_INLINE_TAGS;
} else if (versionNumber < 12) {
tagsForVersion = JAVA_10_INLINE_TAGS;
} else if (versionNumber < 16) {
tagsForVersion = JAVA_12_INLINE_TAGS;
} else if (versionNumber < 18) {
tagsForVersion = JAVA_16_INLINE_TAGS;
} else {
tagsForVersion = JAVA_18_INLINE_TAGS;
}
}
return tagsForNode(tagsForVersion, tagNode);
}

private static List<char[]> tagsForNode(List<char[]> tagsForVersion, TagElement tagNode) {
boolean isField = findParent(tagNode, new int[]{ ASTNode.FIELD_DECLARATION }) != null;
if (isField) {
return Stream.of(JavadocTagConstants.FIELD_TAGS) //
.filter(tag -> tagsForVersion.contains(tag)) //
.toList();
}

ASTNode astNode = findParent(tagNode, new int[] {
ASTNode.METHOD_DECLARATION,
ASTNode.TYPE_DECLARATION,
ASTNode.ENUM_DECLARATION,
ASTNode.RECORD_DECLARATION,
ASTNode.ANNOTATION_TYPE_DECLARATION,
ASTNode.TYPE_DECLARATION_STATEMENT});

boolean isMethod = astNode != null && astNode.getNodeType() == ASTNode.METHOD_DECLARATION;
if (isMethod) {
return Stream.of(JavadocTagConstants.METHOD_TAGS) //
.filter(tag -> tagsForVersion.contains(tag)) //
.toList();
}

boolean isType = astNode != null;
if (isType) {
return Stream.of(JavadocTagConstants.CLASS_TAGS) //
.filter(tag -> tagsForVersion.contains(tag)) //
.toList();
}

boolean isPackage = findParent(tagNode, new int[] {ASTNode.PACKAGE_DECLARATION}) != null;
if (isPackage) {
return Stream.of(JavadocTagConstants.PACKAGE_TAGS) //
.filter(tag -> tagsForVersion.contains(tag)) //
.toList();
}

throw new IllegalStateException("I was expecting one of the above nodes to be documented"); //$NON-NLS-1$
}

private static ASTNode findParent(ASTNode nodeToSearch, int[] kindsToFind) {
ASTNode cursor = nodeToSearch;
while (cursor != null) {
for (int kindToFind : kindsToFind) {
if (cursor.getNodeType() == kindToFind) {
return cursor;
}
}
cursor = cursor.getParent();
}
return null;
}

}

0 comments on commit fff29f4

Please sign in to comment.