001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027 028import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.DetailNode; 031import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 034import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 035import com.puppycrawl.tools.checkstyle.utils.NullUtil; 036import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 037 038/** 039 * <div> 040 * Checks that {@code @param} tags in Javadoc comments are in the same order as the 041 * parameters in the declaration. 042 * </div> 043 * 044 * <p> 045 * Type parameters must come before regular parameters. For record declarations, record 046 * components are treated as regular parameters and must be documented after type parameters. 047 * For compact constructors, the expected parameter order is the order of the record components 048 * in the record declaration. 049 * </p> 050 * 051 * <p> 052 * The check does not validate missing, extra, or duplicate {@code @param} tags. It reports only 053 * tags that move backward in the declaration order. 054 * </p> 055 * 056 * @since 14.1.0 057 */ 058@FileStatefulCheck 059public class JavadocParamOrderCheck extends AbstractJavadocCheck { 060 061 /** 062 * A key is pointing to the warning message text in "messages.properties" file. 063 */ 064 public static final String MSG_KEY = "javadoc.param.order"; 065 066 /** Html element start symbol. */ 067 private static final String ELEMENT_START = "<"; 068 069 /** Html element end symbol. */ 070 private static final String ELEMENT_END = ">"; 071 072 /** Javadoc param tag names, mapped by their corresponding Javadoc node. */ 073 private final Map<DetailNode, String> javadocTags = new LinkedHashMap<>(); 074 075 /** 076 * Creates a new {@code JavadocParamOrderCheck} instance. 077 */ 078 public JavadocParamOrderCheck() { 079 // no code by default 080 } 081 082 @Override 083 public int[] getDefaultTokens() { 084 return getRequiredTokens(); 085 } 086 087 @Override 088 public final int[] getRequiredTokens() { 089 return new int[] { 090 TokenTypes.METHOD_DEF, 091 TokenTypes.CTOR_DEF, 092 TokenTypes.CLASS_DEF, 093 TokenTypes.INTERFACE_DEF, 094 TokenTypes.COMPACT_CTOR_DEF, 095 TokenTypes.RECORD_DEF, 096 }; 097 } 098 099 @Override 100 public int[] getDefaultJavadocTokens() { 101 return getRequiredJavadocTokens(); 102 } 103 104 @Override 105 public int[] getRequiredJavadocTokens() { 106 return new int[] { 107 JavadocCommentsTokenTypes.PARAM_BLOCK_TAG, 108 }; 109 } 110 111 @Override 112 public final void visitToken(final DetailAST ast) { 113 final DetailAST blockCommentNode = JavadocUtil.getAttachedJavadocComment(ast); 114 if (blockCommentNode != null) { 115 javadocTags.clear(); 116 super.visitToken(blockCommentNode); 117 for (Map.Entry<DetailNode, String> javadocTag : getMisorderedParamTags(ast)) { 118 log(javadocTag.getKey(), MSG_KEY, javadocTag.getValue()); 119 } 120 } 121 } 122 123 @Override 124 public void visitJavadocToken(final DetailNode ast) { 125 collectParam(ast); 126 } 127 128 /** 129 * Collects a param tag. 130 * 131 * @param ast the param tag node 132 */ 133 private void collectParam(final DetailNode ast) { 134 final DetailNode parameterName = JavadocUtil.findFirstToken( 135 ast, JavadocCommentsTokenTypes.PARAMETER_NAME); 136 if (parameterName != null) { 137 javadocTags.put(ast, parameterName.getText()); 138 } 139 } 140 141 /** 142 * Gets collected Javadoc param tags that violate the expected declaration order. 143 * 144 * @param ast Java AST node whose Javadoc is being checked 145 * @return collected Javadoc param tags that violate the expected declaration order 146 */ 147 private List<Map.Entry<DetailNode, String>> getMisorderedParamTags(final DetailAST ast) { 148 final List<String> expectedParamOrder = getExpectedParamOrder(ast); 149 final List<Map.Entry<DetailNode, String>> misorderedTags = new ArrayList<>(); 150 151 int maxIndexOfPreviousParam = -1; 152 for (Map.Entry<DetailNode, String> javadocTag : javadocTags.entrySet()) { 153 final int currentIndex = expectedParamOrder.indexOf(javadocTag.getValue()); 154 155 if (currentIndex >= 0) { 156 if (currentIndex < maxIndexOfPreviousParam) { 157 misorderedTags.add(javadocTag); 158 } 159 else { 160 maxIndexOfPreviousParam = currentIndex; 161 } 162 } 163 } 164 return misorderedTags; 165 } 166 167 /** 168 * Gets expected param tag order for the current AST node. 169 * 170 * @param ast Java AST node whose Javadoc is being checked 171 * @return expected param tag order 172 */ 173 private static List<String> getExpectedParamOrder(final DetailAST ast) { 174 final List<String> expectedParamOrder = new ArrayList<>(); 175 176 addTypeParameterNames(expectedParamOrder, ast); 177 178 switch (ast.getType()) { 179 case TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF -> 180 addParameterNames(expectedParamOrder, ast); 181 182 case TokenTypes.RECORD_DEF -> 183 addRecordComponentNames(expectedParamOrder, ast); 184 185 case TokenTypes.COMPACT_CTOR_DEF -> 186 addRecordComponentNames(expectedParamOrder, getRecordDef(ast)); 187 188 default -> { 189 // No formal parameters for type definitions other than records. 190 } 191 } 192 193 return expectedParamOrder; 194 } 195 196 /** 197 * Adds type parameter names from the supplied AST node. 198 * 199 * @param paramNames destination list 200 * @param ast node to inspect 201 */ 202 private static void addTypeParameterNames(final Collection<String> paramNames, 203 final DetailAST ast) { 204 for (String typeParamName : CheckUtil.getTypeParameterNames(ast)) { 205 paramNames.add(ELEMENT_START + typeParamName + ELEMENT_END); 206 } 207 } 208 209 /** 210 * Adds parameter names from the supplied method or constructor AST node. 211 * 212 * @param paramNames destination list 213 * @param ast node to inspect 214 */ 215 private static void addParameterNames(final Collection<String> paramNames, 216 final DetailAST ast) { 217 final DetailAST parameters = NullUtil.notNull(ast.findFirstToken(TokenTypes.PARAMETERS)); 218 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, 219 paramDef -> addParameterName(paramNames, paramDef)); 220 } 221 222 /** 223 * Adds a parameter name from the supplied parameter definition AST node. 224 * 225 * @param paramNames destination list 226 * @param paramDef parameter definition node 227 */ 228 private static void addParameterName(final Collection<String> paramNames, 229 final DetailAST paramDef) { 230 if (!CheckUtil.isReceiverParameter(paramDef)) { 231 final DetailAST ident = NullUtil.notNull(paramDef.findFirstToken(TokenTypes.IDENT)); 232 paramNames.add(ident.getText()); 233 } 234 } 235 236 /** 237 * Adds record component names from the supplied record AST node. 238 * 239 * @param paramNames destination list 240 * @param recordDef record definition node 241 */ 242 private static void addRecordComponentNames(final Collection<String> paramNames, 243 final DetailAST recordDef) { 244 for (DetailAST component : getRecordComponents(recordDef)) { 245 paramNames.add(component.getText()); 246 } 247 } 248 249 /** 250 * Finds the nearest ancestor record definition node for the given AST node. 251 * 252 * @param ast the AST node to start searching from 253 * @return the nearest {@code RECORD_DEF} AST node 254 */ 255 private static DetailAST getRecordDef(final DetailAST ast) { 256 DetailAST current = ast; 257 while (current.getType() != TokenTypes.RECORD_DEF) { 258 current = current.getParent(); 259 } 260 return current; 261 } 262 263 /** 264 * Gets record component identifier nodes from a record definition. 265 * 266 * @param recordDef record definition node 267 * @return record component identifier nodes 268 */ 269 private static List<DetailAST> getRecordComponents(final DetailAST recordDef) { 270 final List<DetailAST> components = new ArrayList<>(); 271 final DetailAST recordDecl = NullUtil.notNull( 272 recordDef.findFirstToken(TokenTypes.RECORD_COMPONENTS)); 273 274 DetailAST child = recordDecl.getFirstChild(); 275 while (child != null) { 276 if (child.getType() == TokenTypes.RECORD_COMPONENT_DEF) { 277 components.add(NullUtil.notNull(child.findFirstToken(TokenTypes.IDENT))); 278 } 279 child = child.getNextSibling(); 280 } 281 return components; 282 } 283 284}