001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.regexp; 021 022import com.puppycrawl.tools.checkstyle.PropertyType; 023import com.puppycrawl.tools.checkstyle.StatelessCheck; 024import com.puppycrawl.tools.checkstyle.XdocsPropertyType; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 028 029/** 030 * <p> 031 * Checks that a specified pattern matches a single line in Java files. 032 * </p> 033 * <p> 034 * This class is variation on 035 * <a href="https://checkstyle.org/config_regexp.html#RegexpSingleline">RegexpSingleline</a> 036 * for detecting single lines that match a supplied regular expression in Java files. 037 * It supports suppressing matches in Java comments. 038 * </p> 039 * <ul> 040 * <li> 041 * Property {@code format} - Specify the format of the regular expression to match. 042 * Type is {@code java.util.regex.Pattern}. 043 * Default value is {@code "$."}. 044 * </li> 045 * <li> 046 * Property {@code message} - Specify the message which is used to notify about 047 * violations, if empty then default (hard-coded) message is used. 048 * Type is {@code java.lang.String}. 049 * Default value is {@code null}. 050 * </li> 051 * <li> 052 * Property {@code ignoreCase} - Control whether to ignore case when searching. 053 * Type is {@code boolean}. 054 * Default value is {@code false}. 055 * </li> 056 * <li> 057 * Property {@code minimum} - Specify the minimum number of matches required in each file. 058 * Type is {@code int}. 059 * Default value is {@code 0}. 060 * </li> 061 * <li> 062 * Property {@code maximum} - Specify the maximum number of matches required in each file. 063 * Type is {@code int}. 064 * Default value is {@code 0}. 065 * </li> 066 * <li> 067 * Property {@code ignoreComments} - Control whether to ignore text in comments when searching. 068 * Type is {@code boolean}. 069 * Default value is {@code false}. 070 * </li> 071 * </ul> 072 * <p> 073 * To configure the default check: 074 * </p> 075 * <pre> 076 * <module name="RegexpSinglelineJava"/> 077 * </pre> 078 * <p> 079 * This configuration does not match to anything, 080 * so we do not provide any code example for it 081 * as no violation will ever be reported. 082 * </p> 083 * <p> 084 * To configure the check for calls to {@code System.out.println}, except in comments: 085 * </p> 086 * <pre> 087 * <module name="RegexpSinglelineJava"> 088 * <!-- . matches any character, so we need to 089 * escape it and use \. to match dots. --> 090 * <property name="format" value="System\.out\.println"/> 091 * <property name="ignoreComments" value="true"/> 092 * </module> 093 * </pre> 094 * <p>Example:</p> 095 * <pre> 096 * System.out.println(""); // violation, instruction matches illegal pattern 097 * System.out. 098 * println(""); // OK 099 * /* System.out.println */ // OK, comments are ignored 100 * </pre> 101 * <p> 102 * To configure the check to find case-insensitive occurrences of "debug": 103 * </p> 104 * <pre> 105 * <module name="RegexpSinglelineJava"> 106 * <property name="format" value="debug"/> 107 * <property name="ignoreCase" value="true"/> 108 * </module> 109 * </pre> 110 * <p>Example:</p> 111 * <pre> 112 * int debug = 0; // violation, variable name matches illegal pattern 113 * public class Debug { // violation, class name matches illegal pattern 114 * /* this is for de 115 * bug only; */ // OK 116 * </pre> 117 * <p> 118 * To configure the check to find occurrences of 119 * "\.read(.*)|\.write(.*)" 120 * and display "IO found" for each violation. 121 * </p> 122 * <pre> 123 * <module name="RegexpSinglelineJava"> 124 * <property name="format" value="\.read(.*)|\.write(.*)"/> 125 * <property name="message" value="IO found"/> 126 * </module> 127 * </pre> 128 * <p>Example:</p> 129 * <pre> 130 * FileReader in = new FileReader("path/to/input"); 131 * int ch = in.read(); // violation 132 * while(ch != -1) { 133 * System.out.print((char)ch); 134 * ch = in.read(); // violation 135 * } 136 * 137 * FileWriter out = new FileWriter("path/to/output"); 138 * out.write("something"); // violation 139 * </pre> 140 * <p> 141 * To configure the check to find occurrences of 142 * "\.log(.*)". We want to allow a maximum of 2 occurrences. 143 * </p> 144 * <pre> 145 * <module name="RegexpSinglelineJava"> 146 * <property name="format" value="\.log(.*)"/> 147 * <property name="maximum" value="2"/> 148 * </module> 149 * </pre> 150 * <p>Example:</p> 151 * <pre> 152 * public class Foo{ 153 * public void bar(){ 154 * Logger.log("first"); // OK, first occurrence is allowed 155 * Logger.log("second"); // OK, second occurrence is allowed 156 * Logger.log("third"); // violation 157 * System.out.println("fourth"); 158 * Logger.log("fifth"); // violation 159 * } 160 * } 161 * </pre> 162 * <p> 163 * To configure the check to find all occurrences of 164 * "public". We want to ignore comments, 165 * display "public member found" for each violation 166 * and say if less than 2 occurrences. 167 * </p> 168 * <pre> 169 * <module name="RegexpSinglelineJava"> 170 * <property name="format" value="public"/> 171 * <property name="minimum" value="2"/> 172 * <property name="message" value="public member found"/> 173 * <property name="ignoreComments" value="true"/> 174 * </module> 175 * </pre> 176 * <p>Example:</p> 177 * <pre> 178 * class Foo{ // violation, file contains less than 2 occurrences of "public" 179 * private int a; 180 * /* public comment */ // OK, comment is ignored 181 * private void bar1() {} 182 * public void bar2() {} // violation 183 * } 184 * </pre> 185 * <p>Example:</p> 186 * <pre> 187 * class Foo{ 188 * private int a; 189 * /* public comment */ // OK, comment is ignored 190 * public void bar1() {} // violation 191 * public void bar2() {} // violation 192 * } 193 * </pre> 194 * <p> 195 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 196 * </p> 197 * <p> 198 * Violation Message Keys: 199 * </p> 200 * <ul> 201 * <li> 202 * {@code regexp.exceeded} 203 * </li> 204 * <li> 205 * {@code regexp.minimum} 206 * </li> 207 * </ul> 208 * 209 * @since 6.0 210 */ 211@StatelessCheck 212public class RegexpSinglelineJavaCheck extends AbstractCheck { 213 214 /** Specify the format of the regular expression to match. */ 215 @XdocsPropertyType(PropertyType.PATTERN) 216 private String format = "$."; 217 /** 218 * Specify the message which is used to notify about violations, 219 * if empty then default (hard-coded) message is used. 220 */ 221 private String message; 222 /** Specify the minimum number of matches required in each file. */ 223 private int minimum; 224 /** Specify the maximum number of matches required in each file. */ 225 private int maximum; 226 /** Control whether to ignore case when searching. */ 227 private boolean ignoreCase; 228 /** Control whether to ignore text in comments when searching. */ 229 private boolean ignoreComments; 230 231 @Override 232 public int[] getDefaultTokens() { 233 return getRequiredTokens(); 234 } 235 236 @Override 237 public int[] getAcceptableTokens() { 238 return getRequiredTokens(); 239 } 240 241 @Override 242 public int[] getRequiredTokens() { 243 return CommonUtil.EMPTY_INT_ARRAY; 244 } 245 246 // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166 247 @SuppressWarnings("deprecation") 248 @Override 249 public void beginTree(DetailAST rootAST) { 250 MatchSuppressor suppressor = null; 251 if (ignoreComments) { 252 suppressor = new CommentSuppressor(getFileContents()); 253 } 254 255 final DetectorOptions options = DetectorOptions.newBuilder() 256 .reporter(this) 257 .compileFlags(0) 258 .suppressor(suppressor) 259 .format(format) 260 .message(message) 261 .minimum(minimum) 262 .maximum(maximum) 263 .ignoreCase(ignoreCase) 264 .build(); 265 final SinglelineDetector detector = new SinglelineDetector(options); 266 detector.processLines(getFileContents().getText()); 267 } 268 269 /** 270 * Setter to specify the format of the regular expression to match. 271 * 272 * @param format the format of the regular expression to match. 273 */ 274 public void setFormat(String format) { 275 this.format = format; 276 } 277 278 /** 279 * Setter to specify the message which is used to notify about violations, 280 * if empty then default (hard-coded) message is used. 281 * 282 * @param message the message to report for a match. 283 */ 284 public void setMessage(String message) { 285 this.message = message; 286 } 287 288 /** 289 * Setter to specify the minimum number of matches required in each file. 290 * 291 * @param minimum the minimum number of matches required in each file. 292 */ 293 public void setMinimum(int minimum) { 294 this.minimum = minimum; 295 } 296 297 /** 298 * Setter to specify the maximum number of matches required in each file. 299 * 300 * @param maximum the maximum number of matches required in each file. 301 */ 302 public void setMaximum(int maximum) { 303 this.maximum = maximum; 304 } 305 306 /** 307 * Setter to control whether to ignore case when searching. 308 * 309 * @param ignoreCase whether to ignore case when searching. 310 */ 311 public void setIgnoreCase(boolean ignoreCase) { 312 this.ignoreCase = ignoreCase; 313 } 314 315 /** 316 * Setter to control whether to ignore text in comments when searching. 317 * 318 * @param ignore whether to ignore text in comments when searching. 319 */ 320 public void setIgnoreComments(boolean ignore) { 321 ignoreComments = ignore; 322 } 323 324}