Skip to content

Commit

Permalink
feat: display Beta, Obsolete, and Internal status and descriptions (#217
Browse files Browse the repository at this point in the history
)

* deps: use libraries-bom for dependency management

* feat: add annotation descriptions in doc comments

* feat: use devsite notices

* chore: format

* deps: update libraries-bom 26.27.0

* fix: remove unused status values

* fix: goldens

* fix: ClassLookupTest

* fix: formatting
  • Loading branch information
burkedavison authored Nov 20, 2023
1 parent e3e8c35 commit 489aca9
Show file tree
Hide file tree
Showing 25 changed files with 446 additions and 173 deletions.
14 changes: 12 additions & 2 deletions third_party/docfx-doclet-143274/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@
</plugins>
</build>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.27.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
Expand Down Expand Up @@ -182,13 +194,11 @@
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-vision</artifactId>
<version>2.0.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-speech</artifactId>
<version>4.23.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.sun.source.doctree.LinkTree;
import com.sun.source.doctree.LiteralTree;
import com.sun.source.doctree.SeeTree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -22,6 +23,8 @@
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import jdk.javadoc.doclet.DocletEnvironment;
Expand Down Expand Up @@ -170,19 +173,17 @@ public String extractOverridden(T key) {
return resolve(key).getOverridden();
}

public String extractStatus(T element) {
Optional<DocCommentTree> docCommentTree = getDocCommentTree(element);
if (docCommentTree.isPresent()) {
boolean isDeprecated =
docCommentTree.get().getBlockTags().stream()
.filter(docTree -> docTree.getKind().equals(DocTree.Kind.DEPRECATED))
.findFirst()
.isPresent();
if (isDeprecated) {
return DocTree.Kind.DEPRECATED.name().toLowerCase();
}
}
return null;
protected Optional<DocCommentTree> getDocCommentTree(T element) {
return Optional.ofNullable(environment.getDocTrees().getDocCommentTree(element));
}

private boolean hasDeprecatedJavadocTag(T element) {
List<? extends DocTree> javadocTags =
getDocCommentTree(element)
.map(DocCommentTree::getBlockTags)
.orElse(Collections.emptyList());

return javadocTags.stream().map(DocTree::getKind).anyMatch(DocTree.Kind.DEPRECATED::equals);
}

protected String determineType(T element) {
Expand All @@ -194,16 +195,33 @@ protected String determinePackageName(T element) {
}

protected String determineComment(T element) {
Optional<DocCommentTree> docCommentTree = getDocCommentTree(element);
if (docCommentTree.isPresent()) {
String comment =
docCommentTree
.map(DocCommentTree::getFullBody)
.map(this::replaceLinksAndCodes)
.orElse(null);
return replaceBlockTags(docCommentTree.get(), comment);
String statusComment = getStatusComment(element);
String javadocComment = getJavadocComment(element);
return joinNullable(statusComment, javadocComment);
}

private String getJavadocComment(T element) {
return getDocCommentTree(element)
.map(
tree -> {
String commentWithBlockTags = replaceLinksAndCodes(tree.getFullBody());
return replaceBlockTags(tree, commentWithBlockTags);
})
.orElse(null);
}

/** Safely combine two nullable strings with a newline delimiter */
String joinNullable(@Nullable String top, @Nullable String bottom) {
String a = top == null || top.isEmpty() ? null : top;
String b = bottom == null || bottom.isEmpty() ? null : bottom;

if (a != null && b != null) {
return a + "\n\n" + b;
} else if (a != null) {
return a;
} else {
return b;
}
return null;
}

/** Provides support for deprecated and see tags */
Expand Down Expand Up @@ -277,10 +295,6 @@ String expandLiteralBody(LiteralTree bodyItem) {
return String.valueOf(StringEscapeUtils.unescapeJava(bodyItem.getBody().toString()));
}

protected Optional<DocCommentTree> getDocCommentTree(T element) {
return Optional.ofNullable(environment.getDocTrees().getDocCommentTree(element));
}

/**
* We make type shortening in assumption that package name doesn't contain uppercase characters
*/
Expand Down Expand Up @@ -313,4 +327,103 @@ private String getSeeTagRef(SeeTree seeTree) {
return String.format(
"<xref uid=\"%1$s\" data-throw-if-not-resolved=\"false\">%1$s</xref>", ref);
}

public String extractStatus(T element) {
List<String> annotationNames =
element.getAnnotationMirrors().stream()
.map(mirror -> mirror.getAnnotationType().asElement().getSimpleName().toString())
.collect(Collectors.toList());

if (annotationNames.stream().anyMatch("Deprecated"::equals)
|| hasDeprecatedJavadocTag(element)) {
return "deprecated";
}
if (annotationNames.stream().anyMatch("BetaApi"::equals)) {
return "beta";
}
return null;
}

public String getStatusComment(T element) {
Map<String, Optional<String>> annotationComments = getAnnotationComments(element);

// Deprecated comments are determined by the Javadoc @deprecated block tag.
// See this#replaceBlockTags

List<String> comments = new ArrayList<>();
if (annotationComments.containsKey("InternalApi")) {
comments.add(createInternalOnlyNotice(annotationComments.get("InternalApi")));
}
if (annotationComments.containsKey("InternalExtensionOnly")) {
comments.add(
createInternalExtensionOnlyNotice(annotationComments.get("InternalExtensionOnly")));
}
if (annotationComments.containsKey("ObsoleteApi")) {
comments.add(createObsoleteNotice(annotationComments.get("ObsoleteApi")));
}
if (annotationComments.containsKey("BetaApi")) {
comments.add(createBetaNotice(annotationComments.get("BetaApi")));
}

if (comments.isEmpty()) {
return null;
}
return String.join("\n\n", comments);
}

private String createBetaNotice(Optional<String> customComment) {
return "<aside class=\"beta\">\n"
+ "<p><strong>Beta</strong></p>\n"
+ customComment.map(comment -> "<p><em>" + comment + "</em></p>\n").orElse("")
+ "<p>This feature is covered by the <a href=\"/terms/service-terms#1\">Pre-GA Offerings "
+ "Terms</a> of the Terms of Service. Pre-GA libraries might have limited support, and "
+ "changes to pre-GA libraries might not be compatible with other pre-GA versions. For "
+ "more information, see the launch stage descriptions.</p>\n"
+ "</aside>\n";
}

private String createObsoleteNotice(Optional<String> customComment) {
return "<aside class=\"deprecated\">\n"
+ "<p><strong>Obsolete</strong></p>\n"
+ customComment.map(comment -> "<p><em>" + comment + "</em></p>\n").orElse("")
+ "<p>This feature is stable for usage in this major version, but may be deprecated in a "
+ "future release.</p>\n"
+ "</aside>\n";
}

private String createInternalExtensionOnlyNotice(Optional<String> customComment) {
return "<aside class=\"special\">\n"
+ "<p><strong>Internal Extension Only</strong>: This feature is stable for usage, but is "
+ "not intended for extension or implementation.</p>\n"
+ customComment.map(comment -> "<p><em>" + comment + "</em></p>\n").orElse("")
+ "</aside>\n";
}

private String createInternalOnlyNotice(Optional<String> customComment) {
return "<aside class=\"warning\">\n"
+ "<p><strong>Internal Only</strong>: This feature is not stable for application use.</p>\n"
+ customComment.map(comment -> "<p><em>" + comment + "</em></p>\n").orElse("")
+ "</aside>\n";
}

/**
* @return all annotations on the element and their associated comment, if it exists
*/
public Map<String, Optional<String>> getAnnotationComments(T element) {
Map<String, Optional<String>> annotationComments = new HashMap<>();

for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
String name = annotation.getAnnotationType().asElement().getSimpleName().toString();
Optional<String> value =
annotation.getElementValues().entrySet().stream()
.filter(entry -> entry.getKey().getSimpleName().toString().equals("value"))
.map(Map.Entry::getValue)
.map(annotationValue -> annotationValue.getValue().toString())
.findFirst();

annotationComments.put(name, value);
}

return annotationComments;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ private String determineComment(ExecutableElement methodElement) {
String inheritedInlineComment = getInheritedInlineCommentString(methodElement);
Optional<DocCommentTree> docCommentTree = getDocCommentTree(methodElement);
if (docCommentTree.isPresent()) {
return replaceBlockTags(docCommentTree.get(), inheritedInlineComment);
inheritedInlineComment = replaceBlockTags(docCommentTree.get(), inheritedInlineComment);
}
return inheritedInlineComment;
return joinNullable(getStatusComment(methodElement), inheritedInlineComment);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ List<String> determineInheritedMembers(List<ExtendedMetadataFileItem> inheritedM
return new ArrayList<>();
}

@Override
public String getStatusComment(TypeElement element) {
// Don't provide status comments for classes.
return null;
}

public String extractJavaType(TypeElement element) {
String superClass = determineSuperclass(element);
if (superClass != null && superClass.contains("Exception")) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.microsoft.lookup;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.google.testing.compile.CompilationRule;
Expand All @@ -16,7 +16,6 @@
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.util.DocTrees;
import java.util.Arrays;
import java.util.List;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
Expand All @@ -42,6 +41,7 @@ public class ClassLookupTest {
private DeprecatedTree deprecatedTree;
private TextTree textTree;
private TypeMirror typeMirror;
private ClassItemsLookup classItemsLookup;

@Before
public void setup() {
Expand All @@ -54,6 +54,9 @@ public void setup() {
deprecatedTree = Mockito.mock(DeprecatedTree.class);
textTree = Mockito.mock(TextTree.class);
typeMirror = Mockito.mock(TypeMirror.class);
classItemsLookup = new ClassItemsLookup(environment, Mockito.mock(ElementUtil.class));

when(environment.getDocTrees()).thenReturn(docTrees);
}

@Test
Expand Down Expand Up @@ -189,41 +192,24 @@ public void determineTypeForClass() {
}

@Test
public void extractStatusDeprecated() {
public void extractStatus_deprecated() {
TypeElement element =
elements.getTypeElement(
"com.microsoft.samples.agreements.AgreementDetailsCollectionOperations");

when(environment.getDocTrees()).thenReturn(docTrees);
when(docTrees.getDocCommentTree(element)).thenReturn(docCommentTree);
doReturn(Arrays.asList(deprecatedTree)).when(docCommentTree).getBlockTags();
when(deprecatedTree.getKind()).thenReturn(DocTree.Kind.DEPRECATED);

String result = classLookup.extractStatus(element);

verify(environment).getDocTrees();
verify(docTrees).getDocCommentTree(element);
verify(docCommentTree).getBlockTags();
verify(deprecatedTree).getKind();
assertEquals("Wrong description", result, Status.DEPRECATED.toString());
}

@Test
public void extractStatusNotDeprecated() {
public void extractStatus_notDeprecated() {
TypeElement element =
elements.getTypeElement(
"com.microsoft.samples.agreements.AgreementDetailsCollectionOperations");

when(environment.getDocTrees()).thenReturn(docTrees);
when(docTrees.getDocCommentTree(element)).thenReturn(docCommentTree);
doReturn(Arrays.asList()).when(docCommentTree).getBlockTags();
elements.getTypeElement("com.microsoft.samples.agreements.AgreementMetaData");

String result = classLookup.extractStatus(element);

verify(environment).getDocTrees();
verify(docTrees).getDocCommentTree(element);
verify(docCommentTree).getBlockTags();
assertEquals("Wrong description", result, null);
assertNull("Wrong description", result);
}

@Test
Expand All @@ -233,12 +219,18 @@ public void testExtractJavaType() {
assertEquals("Wrong javaType", classLookup.extractJavaType(typeElement), "exception");

typeElement = elements.getTypeElement("com.microsoft.samples.google.RecognitionAudio");
assertEquals("Wrong javaType", classLookup.extractJavaType(typeElement), null);
assertNull("Wrong javaType", classLookup.extractJavaType(typeElement));

typeElement = elements.getTypeElement("com.microsoft.samples.google.BetaApi");
assertEquals("Wrong javaType", classLookup.extractJavaType(typeElement), "annotationtype");

typeElement = elements.getTypeElement("com.microsoft.samples.IPartner");
assertEquals("Wrong javaType", classLookup.extractJavaType(typeElement), null);
assertNull("Wrong javaType", classLookup.extractJavaType(typeElement));
}

@Test
public void testExtractStatus_class_beta() {
TypeElement betaApi = elements.getTypeElement("com.microsoft.samples.google.BetaApi");
assertThat(classLookup.extractStatus(betaApi)).isEqualTo("beta");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.google.api.core.ApiFunction;
import com.google.api.core.BetaApi;
import com.google.api.core.InternalExtensionOnly;
import com.google.api.gax.core.GoogleCredentialsProvider;
import com.google.api.gax.core.InstantiatingExecutorProvider;
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
Expand Down Expand Up @@ -63,6 +64,7 @@
* }</pre>
*/
@BetaApi
@InternalExtensionOnly
@Generated("by gapic-generator-java")
@SuppressWarnings("unchecked")
public class SpeechSettings extends ClientSettings<SpeechSettings> {
Expand Down
Loading

0 comments on commit 489aca9

Please sign in to comment.