001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.indentation; 021 022import org.apache.commons.lang3.ArrayUtils; 023 024import com.puppycrawl.tools.checkstyle.api.Check; 025import com.puppycrawl.tools.checkstyle.api.DetailAST; 026import com.puppycrawl.tools.checkstyle.api.TokenTypes; 027import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 028 029/** 030 * This Check controls the indentation between comments and surrounding code. 031 * Comments are indented at the same level as the surrounding code. 032 * Detailed info about such convention can be found 033 * <a href= 034 * "http://checkstyle.sourceforge.net/reports/google-java-style.html#s4.8.6.1-block-comment-style"> 035 * here</a> 036 * <p> 037 * Examples: 038 * </p> 039 * <p> 040 * To configure the Check: 041 * </p> 042 * 043 * <pre> 044 * {@code 045 * <module name="CommentsIndentation"/module> 046 * } 047 * {@code 048 * /* 049 * * comment 050 * * some comment 051 * */ 052 * boolean bool = true; - such comment indentation is ok 053 * /* 054 * * comment 055 * * some comment 056 * */ 057 * double d = 3.14; - Block Comment has incorrect indentation level 7, expected 4. 058 * // some comment - comment is ok 059 * String str = ""; 060 * // some comment Comment has incorrect indentation level 8, expected 4. 061 * String str1 = ""; 062 * } 063 * </pre> 064 * 065 * 066 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 067 * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a> 068 * 069 */ 070public class CommentsIndentationCheck extends Check { 071 072 /** 073 * A key is pointing to the warning message text in "messages.properties" 074 * file. 075 */ 076 public static final String MSG_KEY_SINGLE = "comments.indentation.single"; 077 078 /** 079 * A key is pointing to the warning message text in "messages.properties" 080 * file. 081 */ 082 public static final String MSG_KEY_BLOCK = "comments.indentation.block"; 083 084 @Override 085 public int[] getDefaultTokens() { 086 return new int[] { 087 TokenTypes.SINGLE_LINE_COMMENT, 088 TokenTypes.BLOCK_COMMENT_BEGIN, 089 }; 090 } 091 092 @Override 093 public int[] getAcceptableTokens() { 094 return new int[] { 095 TokenTypes.SINGLE_LINE_COMMENT, 096 TokenTypes.BLOCK_COMMENT_BEGIN, 097 }; 098 } 099 100 @Override 101 public int[] getRequiredTokens() { 102 return ArrayUtils.EMPTY_INT_ARRAY; 103 } 104 105 @Override 106 public boolean isCommentNodesRequired() { 107 return true; 108 } 109 110 @Override 111 public void visitToken(DetailAST commentAst) { 112 switch (commentAst.getType()) { 113 case TokenTypes.SINGLE_LINE_COMMENT: 114 visitSingleLineComment(commentAst); 115 break; 116 case TokenTypes.BLOCK_COMMENT_BEGIN: 117 visitBlockComment(commentAst); 118 break; 119 default: 120 final String exceptionMsg = "Unexpected token type: " + commentAst.getText(); 121 throw new IllegalArgumentException(exceptionMsg); 122 } 123 } 124 125 /** 126 * Checks single line comment indentations over surrounding code, e.g.: 127 * <p> 128 * {@code 129 * // some comment - this is ok 130 * double d = 3.14; 131 * // some comment - this is <b>not</b> ok. 132 * double d1 = 5.0; 133 * } 134 * </p> 135 * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}. 136 */ 137 private void visitSingleLineComment(DetailAST singleLineComment) { 138 final DetailAST nextStatement = singleLineComment.getNextSibling(); 139 final DetailAST prevStatement = getPrevStatementFromSwitchBlock(singleLineComment); 140 141 if (nextStatement != null 142 && nextStatement.getType() != TokenTypes.RCURLY 143 && !isTrailingSingleLineComment(singleLineComment) 144 && !areSameLevelIndented(singleLineComment, prevStatement, nextStatement)) { 145 146 log(singleLineComment.getLineNo(), MSG_KEY_SINGLE, nextStatement.getLineNo(), 147 singleLineComment.getColumnNo(), nextStatement.getColumnNo()); 148 } 149 } 150 151 /** 152 * Gets comment's previous statement from switch block. 153 * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single-line comment}. 154 * @return comment's previous statement or null if previous statement is absent. 155 */ 156 private static DetailAST getPrevStatementFromSwitchBlock(DetailAST comment) { 157 DetailAST prevStmt = null; 158 final DetailAST parentStatement = comment.getParent(); 159 if (parentStatement != null) { 160 if (parentStatement.getType() == TokenTypes.CASE_GROUP) { 161 prevStmt = getPrevStatementWhenCommentIsUnderCase(parentStatement); 162 } 163 else { 164 prevStmt = getPrevCaseToken(parentStatement); 165 } 166 } 167 return prevStmt; 168 } 169 170 /** 171 * Gets previous statement for comment which is placed immediately under case. 172 * @param parentStatement comment's parent statement. 173 * @return comment's previous statement or null if previous statement is absent. 174 */ 175 private static DetailAST getPrevStatementWhenCommentIsUnderCase(DetailAST parentStatement) { 176 DetailAST prevStmt = null; 177 final DetailAST prevBlock = parentStatement.getPreviousSibling(); 178 if (prevBlock.getLastChild() != null) { 179 DetailAST blockBody = prevBlock.getLastChild().getLastChild(); 180 if (blockBody.getPreviousSibling() != null) { 181 blockBody = blockBody.getPreviousSibling(); 182 } 183 if (blockBody.getType() == TokenTypes.EXPR) { 184 prevStmt = blockBody.getFirstChild().getFirstChild(); 185 } 186 else { 187 prevStmt = blockBody; 188 } 189 } 190 return prevStmt; 191 } 192 193 /** 194 * Gets previous case-token for comment. 195 * @param parentStatement comment's parent statement. 196 * @return previous case-token or null if previous case-token is absent. 197 */ 198 private static DetailAST getPrevCaseToken(DetailAST parentStatement) { 199 final DetailAST prevCaseToken; 200 final DetailAST parentBlock = parentStatement.getParent(); 201 if (parentBlock != null && parentBlock.getParent() != null 202 && parentBlock.getParent().getPreviousSibling() != null 203 && parentBlock.getParent().getPreviousSibling() 204 .getType() == TokenTypes.LITERAL_CASE) { 205 206 prevCaseToken = parentBlock.getParent().getPreviousSibling(); 207 } 208 else { 209 prevCaseToken = null; 210 } 211 return prevCaseToken; 212 } 213 214 /** 215 * Checks if comment and next code statement 216 * (or previous code stmt like <b>case</b> in switch block) are indented at the same level, 217 * e.g.: 218 * <p> 219 * <pre> 220 * {@code 221 * // some comment - same indentation level 222 * int x = 10; 223 * // some comment - different indentation level 224 * int x1 = 5; 225 * /* 226 * * 227 * */ 228 * boolean bool = true; - same indentation level 229 * } 230 * </pre> 231 * </p> 232 * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}. 233 * @param prevStmt previous code statement. 234 * @param nextStmt next code statement. 235 * @return true if comment and next code statement are indented at the same level. 236 */ 237 private static boolean areSameLevelIndented(DetailAST singleLineComment, 238 DetailAST prevStmt, DetailAST nextStmt) { 239 boolean result; 240 if (prevStmt == null) { 241 result = singleLineComment.getColumnNo() == nextStmt.getColumnNo(); 242 } 243 else { 244 result = singleLineComment.getColumnNo() == nextStmt.getColumnNo() 245 || singleLineComment.getColumnNo() == prevStmt.getColumnNo(); 246 } 247 return result; 248 } 249 250 /** 251 * Checks if current single line comment is trailing comment, e.g.: 252 * <p> 253 * {@code 254 * double d = 3.14; // some comment 255 * } 256 * </p> 257 * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}. 258 * @return true if current single line comment is trailing comment. 259 */ 260 private boolean isTrailingSingleLineComment(DetailAST singleLineComment) { 261 final String targetSourceLine = getLine(singleLineComment.getLineNo() - 1); 262 final int commentColumnNo = singleLineComment.getColumnNo(); 263 return !CommonUtils.hasWhitespaceBefore(commentColumnNo, targetSourceLine); 264 } 265 266 /** 267 * Checks comment block indentations over surrounding code, e.g.: 268 * <p> 269 * {@code 270 * /* some comment */ - this is ok 271 * double d = 3.14; 272 * /* some comment */ - this is <b>not</b> ok. 273 * double d1 = 5.0; 274 * } 275 * </p> 276 * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}. 277 */ 278 private void visitBlockComment(DetailAST blockComment) { 279 final DetailAST nextStatement = blockComment.getNextSibling(); 280 final DetailAST prevStatement = getPrevStatementFromSwitchBlock(blockComment); 281 282 if (nextStatement != null 283 && nextStatement.getType() != TokenTypes.RCURLY 284 && !isTrailingBlockComment(blockComment) 285 && !areSameLevelIndented(blockComment, prevStatement, nextStatement)) { 286 287 log(blockComment.getLineNo(), MSG_KEY_BLOCK, nextStatement.getLineNo(), 288 blockComment.getColumnNo(), nextStatement.getColumnNo()); 289 } 290 } 291 292 /** 293 * Checks if current comment block is trailing comment, e.g.: 294 * <p> 295 * {@code 296 * double d = 3.14; /* some comment */ 297 * /* some comment */ double d = 18.5; 298 * } 299 * </p> 300 * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}. 301 * @return true if current comment block is trailing comment. 302 */ 303 private boolean isTrailingBlockComment(DetailAST blockComment) { 304 final String commentLine = getLine(blockComment.getLineNo() - 1); 305 final int commentColumnNo = blockComment.getColumnNo(); 306 return !CommonUtils.hasWhitespaceBefore(commentColumnNo, commentLine) 307 || blockComment.getNextSibling().getLineNo() == blockComment.getLineNo(); 308 } 309}