Skip to content

Refine newly added reuse of metadata and xdoc to simplify Javadocs #17746

@SteLeo1602

Description

@SteLeo1602

Discovered in #17675

  • 1.
    Consider the following methods:
    public static String getFirstParagraphFromJavadoc(DetailNode javadoc) {
    final Deque<DetailNode> stack = new ArrayDeque<>();
    final List<DetailNode> firstParagraphNodes = getFirstJavadocParagraphNodes(javadoc);
    Lists.reverse(firstParagraphNodes).forEach(stack::push);
    final StringBuilder result = new StringBuilder(1024);
    while (!stack.isEmpty()) {
    DetailNode detailNode = stack.pop();
    Lists.reverse(Arrays.asList(detailNode.getChildren())).forEach(stack::push);
    String childText = detailNode.getText();
    if (detailNode.getParent().getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
    childText = JavadocMetadataScraper.adjustCodeInlineTagChildToHtml(detailNode);
    }
    // Regular expression for detecting ANTLR tokens(for e.g. CLASS_DEF).
    final Pattern tokenTextPattern = Pattern.compile("([A-Z_]{2,})+");
    if (detailNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
    && !tokenTextPattern.matcher(childText).matches()) {
    result.append(childText);
    }
    }
    return result.toString().trim();
    }

    private static String getDescriptionFromJavadocForXdoc(DetailNode javadoc, String moduleName)
    throws MacroExecutionException {
    boolean isInCodeLiteral = false;
    boolean isInHtmlElement = false;
    boolean isInHrefAttribute = false;
    final StringBuilder description = new StringBuilder(128);
    final Deque<DetailNode> queue = new ArrayDeque<>();
    final List<DetailNode> descriptionNodes = getFirstJavadocParagraphNodes(javadoc);
    Lists.reverse(descriptionNodes).forEach(queue::push);
    // Perform DFS traversal on description nodes
    while (!queue.isEmpty()) {
    final DetailNode node = queue.pop();
    Lists.reverse(Arrays.asList(node.getChildren())).forEach(queue::push);
    if (node.getType() == JavadocTokenTypes.HTML_TAG_NAME
    && "href".equals(node.getText())) {
    isInHrefAttribute = true;
    }
    if (isInHrefAttribute && node.getType() == JavadocTokenTypes.ATTR_VALUE) {
    final String href = node.getText();
    if (href.contains(CHECKSTYLE_ORG_URL)) {
    DescriptionExtractor.handleInternalLink(description, moduleName, href);
    }
    else {
    description.append(href);
    }
    isInHrefAttribute = false;
    continue;
    }
    if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) {
    isInHtmlElement = true;
    }
    if (node.getType() == JavadocTokenTypes.END
    && node.getParent().getType() == JavadocTokenTypes.HTML_ELEMENT_END) {
    description.append(node.getText());
    isInHtmlElement = false;
    }
    if (node.getType() == JavadocTokenTypes.TEXT
    // If a node has children, its text is not part of the description
    || isInHtmlElement && node.getChildren().length == 0
    // Some HTML elements span multiple lines, so we avoid the asterisk
    && node.getType() != JavadocTokenTypes.LEADING_ASTERISK) {
    description.append(node.getText());
    }
    if (node.getType() == JavadocTokenTypes.CODE_LITERAL) {
    isInCodeLiteral = true;
    description.append("<code>");
    }
    if (isInCodeLiteral
    && node.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG_END) {
    isInCodeLiteral = false;
    description.append("</code>");
    }
    }
    return description.toString().trim();
    }

These two methods essentially do the same thing, except getDescriptionFromJavadocForXdoc also processes extracted description for Xdoc.

Reusing getFirstParagraphFromJavadoc for getDescriptionFromJavadocForXdoc just with addition of xdoc wrapping would make functionality easier.


  • 2.
    As result of new implementation, the following error pops up that is temporarily suppressed:

Class Data Abstraction Coupling is 8 (max allowed is 7) classes [ArrayDeque, Checker, DefaultConfiguration, DetailNode, LinkedHashMap, MacroExecutionException, PackageObjectFactory, Pattern]. [ClassDataAbstractionCoupling]

Fixing it, potentially with the side-effect of integratinggetFirstParagraphFromJavadoc to getDescriptionFromJavadocForXdoc would also make functionality easier and project better.


  • 3.
    Suppressions

Remove coverage suppressions for MetadataGeneratorUtil and XmlMetaWriter, most of which, if not all, will be removed once migration of simplified javadocs to all modules is complete

XmlMetaWriter coverage used to be 0.77

<minimum>0.75</minimum>

Complains about coverage in

if (defaultValue != null && !"null".equals(defaultValue)) {


Currently, MetadataGeneratorUtil has 0.95 of true coverage.

Complains about coverage for such catch statements:

catch (TransformerException | ParserConfigurationException example) {
throw new CheckstyleException(
"Failed to write metadata into XML file for module: "
+ moduleDetails.getName(), example);
}

catch (MacroExecutionException macroException) {
throw new CheckstyleException(macroException.getMessage(), macroException);
}

Decided to keep the same coverage for XmlMetaWriter because without complained part, the xml files are going to be rewritten significantly with newly added `default-value = 'null'`` statement

Meanwhile, stopped fixing coverage for MetadataGeneratorUtil at 0.95 because what is left is those catch statements shown above which could be useful in the future, but are very problematic to be tested to fix the coverage.


  • 4. Also figure out what to do with CheckTagEmptyBody inspection which was caused by new implementation's decision to get rid of quotations around property values in Xdoc

Current idea inspection suppression:

<!-- Until #17746 -->
<inspection_tool class="CheckTagEmptyBody" enabled="false" level="ERROR"
enabled_by_default="true"/>

Example inspection error message from ci/circleci: run-inspections:

<problem>
<file>file://$PROJECT_DIR$/src/site/xdoc/checks/coding/matchxpath.xml</file>
<line>51</line>
<module>project</module>
<entry_point TYPE="file" FQNAME="file://$PROJECT_DIR$/src/site/xdoc/checks/coding/matchxpath.xml"/>
<problem_class id="CheckTagEmptyBody" severity="ERROR" attribute_key="ERRORS_ATTRIBUTES">Empty element content</problem_class>
<description>XML tag has empty body</description>
<highlighted_element><code></code></highlighted_element>
<language>XML</language>
<offset>18</offset>
<length>13</length>
</problem>

  • 5.

Revise the implementation of finding end of description so it will be more universal and reliable, and won't depend solely on @since ending tag. Probably nail it to the last line separator in the module's javadoc.

Consider following code for getDescriptionEndNode:

/**
* Gets the end node of the description.
*
* @param moduleJavadoc javadoc of module.
* @return the end index.
*/
public static DetailNode getDescriptionEndNode(DetailNode moduleJavadoc) {
final DetailNode descriptionEndNode;
final DetailNode notesStartingNode =
getNotesSectionStartNode(moduleJavadoc);
if (notesStartingNode != null) {
descriptionEndNode = notesStartingNode.getPreviousSibling();
}
else {
descriptionEndNode = getModuleSinceVersionTagStartNode(
moduleJavadoc).getPreviousSibling();
}
return descriptionEndNode;
}

and consider following Example:

* @since 3.4
* @noinspection HtmlTagCanBeJavadocTag
* @noinspectionreason HtmlTagCanBeJavadocTag - encoded symbols were not decoded
* when replaced with Javadoc tag

As far as I recall, getDescriptionEndNode would take both @noinspection tags if @since tag would be placed at the most bottom. I believe we have to make it more flexible so @since tag could be placed in any order, but not necessarily first to avoid extra tags

  • 6.

/**
* Checks whether property is to contain tokens.
*
* @param propertyField property field.
* @return true if property is to contain tokens, false otherwise.
*/
public static boolean isPropertySpecialTokenProp(Field propertyField) {
boolean result = false;
if (propertyField != null) {
final XdocsPropertyType fieldXdocAnnotation =
propertyField.getAnnotation(XdocsPropertyType.class);
result = fieldXdocAnnotation != null
&& fieldXdocAnnotation.value() == PropertyType.TOKEN_ARRAY;
}
return result;
}
}

Move methods like isPropertySpecialTokenProp to more belonging locations

UPDATE: this step was decided to be left with as is, since it is not being used that much and moving it out would potentially create cyclical dependency.

Image

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions