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.annotation; 021 022import java.util.ArrayList; 023import java.util.List; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 030import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 031 032/** 033 * <div> 034 * Verifies that annotations are properly placed by 035 * <a href="https://cr.openjdk.org/~alundblad/styleguide/index-v6.html#toc-annotations"> 036 * OpenJDK Style</a>. 037 * Declaration annotations must either reside entirely on a single line or 038 * have each annotation placed on its own separate line. Annotations should 039 * not share a line with the target declaration, except for single-line methods and fields. 040 * </div> 041 * 042 * <p> 043 * Attention: Checkstyle ignores annotations placed among modifiers due to a technical limitation. 044 * The parser cannot distinguish whether an annotation applies to the method itself 045 * or to its return type. 046 * </p> 047 * 048 * @since 13.9.0 049 */ 050@StatelessCheck 051public class OpenjdkAnnotationLocationCheck extends AbstractCheck { 052 053 /** 054 * A key is pointing to the warning message text in "messages.properties" 055 * file. 056 */ 057 public static final String MSG_KEY_ANNOTATION_ALONE_OR_SAME = "annotation.alone.or.same"; 058 059 /** 060 * A key is pointing to the warning message text in "messages.properties" 061 * file. 062 */ 063 public static final String MSG_KEY_ANNOTATION_ON_TARGET_LINE = "annotation.on.target.line"; 064 065 /** 066 * Creates a new {@code OpenjdkAnnotationLocationCheck} instance. 067 */ 068 public OpenjdkAnnotationLocationCheck() { 069 // no code by default 070 } 071 072 @Override 073 public int[] getDefaultTokens() { 074 return getAcceptableTokens(); 075 } 076 077 @Override 078 public int[] getAcceptableTokens() { 079 return new int[] { 080 TokenTypes.CLASS_DEF, 081 TokenTypes.INTERFACE_DEF, 082 TokenTypes.PACKAGE_DEF, 083 TokenTypes.ENUM_CONSTANT_DEF, 084 TokenTypes.ENUM_DEF, 085 TokenTypes.METHOD_DEF, 086 TokenTypes.CTOR_DEF, 087 TokenTypes.VARIABLE_DEF, 088 TokenTypes.ANNOTATION_DEF, 089 TokenTypes.ANNOTATION_FIELD_DEF, 090 TokenTypes.RECORD_DEF, 091 TokenTypes.COMPACT_CTOR_DEF, 092 }; 093 } 094 095 @Override 096 public int[] getRequiredTokens() { 097 return CommonUtil.EMPTY_INT_ARRAY; 098 } 099 100 @Override 101 public void visitToken(DetailAST ast) { 102 if (!isLocalVariable(ast)) { 103 final DetailAST annotationParentNode = getAnnotationsNode(ast); 104 final DetailAST startOfTargetNode = getStartingAst(annotationParentNode); 105 final List<DetailAST> annotationList = getAnnotations(annotationParentNode); 106 107 if (!areAllOnSameLine(annotationList) && !areAllOnSeparateLines(annotationList)) { 108 log(startOfTargetNode, MSG_KEY_ANNOTATION_ALONE_OR_SAME, getTargetName(ast)); 109 } 110 if (isAnyOnTargetLine(annotationList, startOfTargetNode) 111 && !isSingleLineMethodOrField(ast)) { 112 log(startOfTargetNode, MSG_KEY_ANNOTATION_ON_TARGET_LINE, getTargetName(ast)); 113 } 114 } 115 } 116 117 /** 118 * Checks whether the variable is local or not. 119 * 120 * @param ast variable. 121 * @return true if local variable. 122 */ 123 private static boolean isLocalVariable(DetailAST ast) { 124 return ast.getType() == TokenTypes.VARIABLE_DEF 125 && ast.getParent().getType() != TokenTypes.OBJBLOCK 126 && ast.getParent().getType() != TokenTypes.COMPACT_COMPILATION_UNIT; 127 } 128 129 /** 130 * Finds the first node other than the annotation node in target ast. 131 * 132 * @param targetNode target node. 133 * @return the ast of the starting point 134 */ 135 private static DetailAST getStartingAst(DetailAST targetNode) { 136 DetailAST annotation = targetNode.getFirstChild(); 137 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 138 annotation = annotation.getNextSibling(); 139 } 140 141 final DetailAST startingAst; 142 if (annotation != null) { 143 startingAst = annotation; 144 } 145 else { 146 startingAst = targetNode.getNextSibling(); 147 } 148 return startingAst; 149 } 150 151 /** 152 * Gets the parent node of annotations. 153 * 154 * @param ast token. 155 * @return the parent of annotations. 156 */ 157 private static DetailAST getAnnotationsNode(DetailAST ast) { 158 DetailAST annotationParentNode = ast.findFirstToken(TokenTypes.MODIFIERS); 159 if (annotationParentNode == null) { 160 annotationParentNode = ast.findFirstToken(TokenTypes.ANNOTATIONS); 161 } 162 return annotationParentNode; 163 } 164 165 /** 166 * Gets all annotations of a target node. 167 * 168 * @param annotationParentNode parent node of annotations. 169 * @return the list of annotations. 170 */ 171 private static List<DetailAST> getAnnotations(DetailAST annotationParentNode) { 172 final List<DetailAST> annotationList = new ArrayList<>(); 173 DetailAST annotation = annotationParentNode.getFirstChild(); 174 while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) { 175 annotationList.add(annotation); 176 annotation = annotation.getNextSibling(); 177 } 178 return annotationList; 179 } 180 181 /** 182 * Checks whether all annotations are on the same line. 183 * 184 * @param annotationList list of annotations. 185 * @return true if all annotations are on the same line. 186 */ 187 private static boolean areAllOnSameLine(List<DetailAST> annotationList) { 188 return annotationList.isEmpty() 189 || annotationList.getFirst().getLineNo() == annotationList.getLast().getLineNo(); 190 } 191 192 /** 193 * Checks whether all annotations are on a separate line. 194 * 195 * @param annotationList list of annotations. 196 * @return true if all annotations are on separate lines. 197 */ 198 private static boolean areAllOnSeparateLines(List<DetailAST> annotationList) { 199 boolean areOnSeparateLine = true; 200 for (int index = 0; index < annotationList.size() - 1; index++) { 201 if (annotationList.get(index).getLineNo() 202 == annotationList.get(index + 1).getLineNo()) { 203 areOnSeparateLine = false; 204 } 205 } 206 return areOnSeparateLine; 207 } 208 209 /** 210 * Checks whether an annotation is on the target line. 211 * 212 * @param annotationList list of annotations. 213 * @param startOfTargetNode ast of starting point of target node. 214 * @return true if an annotation is on the target line. 215 */ 216 private static boolean isAnyOnTargetLine(Iterable<DetailAST> annotationList, 217 DetailAST startOfTargetNode) { 218 boolean isOnTargetLine = false; 219 for (final DetailAST annotation : annotationList) { 220 if (TokenUtil.areOnSameLine(annotation, startOfTargetNode)) { 221 isOnTargetLine = true; 222 } 223 } 224 return isOnTargetLine; 225 } 226 227 /** 228 * Checks whether a method or field is single line or not. 229 * 230 * @param targetNode ast of target node 231 * @return true if the method or field is single line. 232 */ 233 private static boolean isSingleLineMethodOrField(DetailAST targetNode) { 234 boolean result = false; 235 if (targetNode.getType() == TokenTypes.METHOD_DEF 236 || targetNode.getType() == TokenTypes.ANNOTATION_FIELD_DEF 237 || targetNode.getType() == TokenTypes.VARIABLE_DEF) { 238 final DetailAST lastToken = targetNode.getLastChild(); 239 if (lastToken.getType() == TokenTypes.SLIST) { 240 final DetailAST rightCurly = lastToken.getLastChild(); 241 result = TokenUtil.areOnSameLine(targetNode, rightCurly); 242 } 243 else { 244 result = TokenUtil.areOnSameLine(targetNode, lastToken); 245 } 246 } 247 248 return result; 249 } 250 251 /** 252 * Returns the name of the given target node. 253 * 254 * @param targetNode target node. 255 * @return target name. 256 */ 257 private static String getTargetName(DetailAST targetNode) { 258 DetailAST identNode = targetNode.findFirstToken(TokenTypes.IDENT); 259 if (identNode == null) { 260 identNode = targetNode.findFirstToken(TokenTypes.DOT).findFirstToken(TokenTypes.IDENT); 261 } 262 return identNode.getText(); 263 } 264 265}