View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.meta;
21  
22  import java.util.Optional;
23  import java.util.regex.Matcher;
24  import java.util.regex.Pattern;
25  
26  import com.puppycrawl.tools.checkstyle.api.DetailNode;
27  import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
28  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
29  
30  /**
31   * Class for scraping module metadata from the corresponding class' class-level javadoc.
32   */
33  public final class JavadocMetadataScraperUtil {
34  
35      /** Regular expression for detecting ANTLR tokens(for e.g. CLASS_DEF). */
36      private static final Pattern TOKEN_TEXT_PATTERN = Pattern.compile("([A-Z_]{2,})+");
37  
38      /**
39       * Private utility constructor.
40       */
41      private JavadocMetadataScraperUtil() {
42      }
43  
44      /**
45       * Performs a depth-first traversal of the subtree starting at {@code startNode}
46       * and ending at {@code endNode}, and constructs the concatenated text of all nodes
47       * in that range, ignoring {@code JavadocToken} texts.
48       *
49       * @param startNode the node where traversal begins (inclusive)
50       * @param endNode the node where traversal ends (inclusive)
51       * @return the constructed text from the specified subtree range
52       */
53      public static String constructSubTreeText(DetailNode startNode,
54                                                 DetailNode endNode) {
55          DetailNode curNode = startNode;
56          final StringBuilder result = new StringBuilder(1024);
57  
58          while (curNode != null) {
59              if (isContentToWrite(curNode)) {
60                  String childText = curNode.getText();
61  
62                  if (isInsideCodeInlineTag(curNode)) {
63                      childText = adjustCodeInlineTagChildToHtml(curNode);
64                  }
65  
66                  result.append(childText);
67              }
68  
69              DetailNode toVisit = curNode.getFirstChild();
70              while (curNode != endNode && toVisit == null) {
71                  toVisit = curNode.getNextSibling();
72                  curNode = curNode.getParent();
73              }
74  
75              curNode = toVisit;
76          }
77          return result.toString().trim();
78      }
79  
80      /**
81       * Checks whether the given node is inside a {@code @code} Javadoc inline tag.
82       *
83       * @param node the node to check
84       * @return true if the node is inside a {@code @code} inline tag, false otherwise
85       */
86      private static boolean isInsideCodeInlineTag(DetailNode node) {
87          return node.getParent() != null
88                  && node.getParent().getType() == JavadocCommentsTokenTypes.CODE_INLINE_TAG;
89      }
90  
91      /**
92       * Checks whether selected Javadoc node is considered as something to write.
93       *
94       * @param detailNode javadoc node to check.
95       * @return whether javadoc node is something to write.
96       */
97      private static boolean isContentToWrite(DetailNode detailNode) {
98  
99          return detailNode.getType() != JavadocCommentsTokenTypes.LEADING_ASTERISK
100             && (detailNode.getType() == JavadocCommentsTokenTypes.TEXT
101             || !TOKEN_TEXT_PATTERN.matcher(detailNode.getText()).matches());
102     }
103 
104     /**
105      * Adjusts certain child of {@code @code} Javadoc inline tag to its analogous html format.
106      *
107      * @param codeChild {@code @code} child to convert.
108      * @return converted {@code @code} child element, otherwise just the original text.
109      */
110     public static String adjustCodeInlineTagChildToHtml(DetailNode codeChild) {
111 
112         return switch (codeChild.getType()) {
113             case JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_END -> "</code>";
114             case JavadocCommentsTokenTypes.TAG_NAME -> "";
115             case JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_START -> "<code>";
116             default -> codeChild.getText().trim();
117         };
118     }
119 
120     /**
121      * Returns the first child node of the given parent that matches the provided {@code tokenType}.
122      *
123      * @param node the parent node
124      * @param tokenType the token type to match
125      * @return an {@link Optional} containing the first matching child node,
126      *         or an empty {@link Optional} if none is found
127      */
128     private static Optional<DetailNode> getFirstChildOfType(DetailNode node, int tokenType) {
129         return JavadocUtil.getAllNodesOfType(node, tokenType).stream().findFirst();
130     }
131 
132     /**
133      * Checks whether the first child {@code JavadocTokenType.TEXT} node matches given pattern.
134      *
135      * @param ast parent javadoc node
136      * @param pattern pattern to match
137      * @return true if one of child text nodes matches pattern
138      */
139     public static boolean isChildNodeTextMatches(DetailNode ast, Pattern pattern) {
140         return getFirstChildOfType(ast, JavadocCommentsTokenTypes.TEXT)
141                 .map(DetailNode::getText)
142                 .map(pattern::matcher)
143                 .map(Matcher::matches)
144                 .orElse(Boolean.FALSE);
145     }
146 }