001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.whitespace; 021 022import java.util.Locale; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029 030/** 031 * <p> 032 * Checks line wrapping with separators. 033 * </p> 034 * <ul> 035 * <li> 036 * Property {@code option} - Specify policy on how to wrap lines. 037 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption}. 038 * Default value is {@code eol}. 039 * </li> 040 * <li> 041 * Property {@code tokens} - tokens to check 042 * Type is {@code int[]}. 043 * Default value is: 044 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT"> 045 * DOT</a>, 046 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA"> 047 * COMMA</a>. 048 * </li> 049 * </ul> 050 * <p> 051 * To configure the check: 052 * </p> 053 * <pre> 054 * <module name="SeparatorWrap"/> 055 * </pre> 056 * <p> 057 * Example: 058 * </p> 059 * <pre> 060 * import java.io. 061 * IOException; // OK 062 * 063 * class Test { 064 * 065 * String s; 066 * 067 * public void foo(int a, 068 * int b) { // OK 069 * } 070 * 071 * public void bar(int p 072 * , int q) { // violation, separator comma on new line 073 * if (s 074 * .isEmpty()) { // violation, separator dot on new line 075 * } 076 * } 077 * 078 * } 079 * </pre> 080 * <p> 081 * To configure the check for 082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_REF"> 083 * METHOD_REF</a> at new line: 084 * </p> 085 * <pre> 086 * <module name="SeparatorWrap"> 087 * <property name="tokens" value="METHOD_REF"/> 088 * <property name="option" value="nl"/> 089 * </module> 090 * </pre> 091 * <p> 092 * Example: 093 * </p> 094 * <pre> 095 * import java.util.Arrays; 096 * 097 * class Test2 { 098 * 099 * String[] stringArray = {"foo", "bar"}; 100 * 101 * void fun() { 102 * Arrays.sort(stringArray, String:: 103 * compareToIgnoreCase); // violation, separator method reference on same line 104 * Arrays.sort(stringArray, String 105 * ::compareTo); // OK 106 * } 107 * 108 * } 109 * </pre> 110 * <p> 111 * To configure the check for comma at the new line: 112 * </p> 113 * <pre> 114 * <module name="SeparatorWrap"> 115 * <property name="tokens" value="COMMA"/> 116 * <property name="option" value="nl"/> 117 * </module> 118 * </pre> 119 * <p> 120 * Example: 121 * </p> 122 * <pre> 123 * class Test3 { 124 * 125 * String s; 126 * 127 * int a, 128 * b; // violation, separator comma on same line 129 * 130 * public void foo(int a, 131 * int b) { // violation, separator comma on the same line 132 * int r 133 * , t; // OK 134 * } 135 * 136 * public void bar(int p 137 * , int q) { // OK 138 * } 139 * 140 * } 141 * </pre> 142 * <p> 143 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 144 * </p> 145 * <p> 146 * Violation Message Keys: 147 * </p> 148 * <ul> 149 * <li> 150 * {@code line.new} 151 * </li> 152 * <li> 153 * {@code line.previous} 154 * </li> 155 * </ul> 156 * 157 * @since 5.8 158 */ 159@StatelessCheck 160public class SeparatorWrapCheck 161 extends AbstractCheck { 162 163 /** 164 * A key is pointing to the warning message text in "messages.properties" 165 * file. 166 */ 167 public static final String MSG_LINE_PREVIOUS = "line.previous"; 168 169 /** 170 * A key is pointing to the warning message text in "messages.properties" 171 * file. 172 */ 173 public static final String MSG_LINE_NEW = "line.new"; 174 175 /** Specify policy on how to wrap lines. */ 176 private WrapOption option = WrapOption.EOL; 177 178 /** 179 * Setter to specify policy on how to wrap lines. 180 * 181 * @param optionStr string to decode option from 182 * @throws IllegalArgumentException if unable to decode 183 */ 184 public void setOption(String optionStr) { 185 option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 186 } 187 188 @Override 189 public int[] getDefaultTokens() { 190 return new int[] { 191 TokenTypes.DOT, 192 TokenTypes.COMMA, 193 }; 194 } 195 196 @Override 197 public int[] getAcceptableTokens() { 198 return new int[] { 199 TokenTypes.DOT, 200 TokenTypes.COMMA, 201 TokenTypes.SEMI, 202 TokenTypes.ELLIPSIS, 203 TokenTypes.AT, 204 TokenTypes.LPAREN, 205 TokenTypes.RPAREN, 206 TokenTypes.ARRAY_DECLARATOR, 207 TokenTypes.RBRACK, 208 TokenTypes.METHOD_REF, 209 }; 210 } 211 212 @Override 213 public int[] getRequiredTokens() { 214 return CommonUtil.EMPTY_INT_ARRAY; 215 } 216 217 @Override 218 public void visitToken(DetailAST ast) { 219 final String text = ast.getText(); 220 final int colNo = ast.getColumnNo(); 221 final int lineNo = ast.getLineNo(); 222 final String currentLine = getLines()[lineNo - 1]; 223 final String substringAfterToken = 224 currentLine.substring(colNo + text.length()).trim(); 225 final String substringBeforeToken = 226 currentLine.substring(0, colNo).trim(); 227 228 if (option == WrapOption.EOL 229 && substringBeforeToken.isEmpty()) { 230 log(ast, MSG_LINE_PREVIOUS, text); 231 } 232 else if (option == WrapOption.NL 233 && substringAfterToken.isEmpty()) { 234 log(ast, MSG_LINE_NEW, text); 235 } 236 } 237 238}