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