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 java.io.File; 023import java.util.regex.Pattern; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 027import com.puppycrawl.tools.checkstyle.api.FileText; 028 029/** 030 * <p> 031 * Checks that a specified pattern matches across multiple lines in any file type. 032 * </p> 033 * <p> 034 * Rationale: This check can be used to when the regular expression can be span multiple lines. 035 * </p> 036 * <ul> 037 * <li> 038 * Property {@code format} - Specify the format of the regular expression to match. 039 * Default value is {@code "$."}. 040 * </li> 041 * <li> 042 * Property {@code message} - Specify the message which is used to notify about 043 * violations, if empty then default (hard-coded) message is used. 044 * Default value is {@code null}. 045 * </li> 046 * <li> 047 * Property {@code ignoreCase} - Control whether to ignore case when searching. 048 * Default value is {@code false}. 049 * </li> 050 * <li> 051 * Property {@code minimum} - Specify the minimum number of matches required in each file. 052 * Default value is {@code 0}. 053 * </li> 054 * <li> 055 * Property {@code maximum} - Specify the maximum number of matches required in each file. 056 * Default value is {@code 0}. 057 * </li> 058 * <li> 059 * Property {@code matchAcrossLines} - Control whether to match expressions 060 * across multiple lines. 061 * Default value is {@code false}. 062 * </li> 063 * <li> 064 * Property {@code fileExtensions} - Specify the file type extension of files to process. 065 * Default value is {@code all files}. 066 * </li> 067 * </ul> 068 * <p> 069 * To configure the check to find calls to print to the console: 070 * </p> 071 * <pre> 072 * <module name="RegexpMultiline"> 073 * <property name="format" 074 * value="System\.(out)|(err)\.print(ln)?\("/> 075 * </module> 076 * </pre> 077 * <p> 078 * To configure the check to match text that spans multiple lines, 079 * like normal code in a Java file: 080 * </p> 081 * <pre> 082 * <module name="RegexpMultiline"> 083 * <property name="matchAcrossLines" value="true"/> 084 * <property name="format" value="System\.out.*print\("/> 085 * </module> 086 * </pre> 087 * <p> 088 * Example of violation from the above config: 089 * </p> 090 * <pre> 091 * void method() { 092 * System.out. // violation 093 * print("Example"); 094 * System.out. 095 * print("Example"); 096 * } 097 * </pre> 098 * <p> 099 * Note: Beware of the greedy regular expression used in the above example. 100 * {@code .*} will match as much as possible and not produce multiple violations 101 * in the file if multiple groups of lines could match the expression. To prevent 102 * an expression being too greedy, avoid overusing matching all text or allow it 103 * to be optional, like {@code .*?}. Changing the example expression to not be 104 * greedy will allow multiple violations in the example to be found in the same file. 105 * </p> 106 * 107 * <p> 108 * To configure the check to restrict an empty file: 109 * </p> 110 * <pre> 111 * <module name="RegexpMultiline"> 112 * <property name="format" value="^\s*$" /> 113 * <property name="matchAcrossLines" value="true" /> 114 * <property name="message" value="Empty file is not allowed" /> 115 * </module> 116 * </pre> 117 * <p> 118 * Example of violation from the above config: 119 * </p> 120 * <pre> 121 * /var/tmp$ cat -n Test.java 122 * 1 123 * 2 124 * 3 125 * 4 126 * </pre> 127 * <p>Result:</p> 128 * <pre> 129 * /var/tmp/Test.java // violation, a file must not be empty. 130 * </pre> 131 * 132 * @since 5.0 133 */ 134@StatelessCheck 135public class RegexpMultilineCheck extends AbstractFileSetCheck { 136 137 /** Specify the format of the regular expression to match. */ 138 private String format = "$."; 139 /** 140 * Specify the message which is used to notify about violations, 141 * if empty then default (hard-coded) message is used. 142 */ 143 private String message; 144 /** Specify the minimum number of matches required in each file. */ 145 private int minimum; 146 /** Specify the maximum number of matches required in each file. */ 147 private int maximum; 148 /** Control whether to ignore case when searching. */ 149 private boolean ignoreCase; 150 /** Control whether to match expressions across multiple lines. */ 151 private boolean matchAcrossLines; 152 153 /** The detector to use. */ 154 private MultilineDetector detector; 155 156 @Override 157 public void beginProcessing(String charset) { 158 final DetectorOptions options = DetectorOptions.newBuilder() 159 .reporter(this) 160 .compileFlags(getRegexCompileFlags()) 161 .format(format) 162 .message(message) 163 .minimum(minimum) 164 .maximum(maximum) 165 .ignoreCase(ignoreCase) 166 .build(); 167 detector = new MultilineDetector(options); 168 } 169 170 @Override 171 protected void processFiltered(File file, FileText fileText) { 172 detector.processLines(fileText); 173 } 174 175 /** 176 * Retrieves the compile flags for the regular expression being built based 177 * on {@code matchAcrossLines}. 178 * 179 * @return The compile flags. 180 */ 181 private int getRegexCompileFlags() { 182 final int result; 183 184 if (matchAcrossLines) { 185 result = Pattern.DOTALL; 186 } 187 else { 188 result = Pattern.MULTILINE; 189 } 190 191 return result; 192 } 193 194 /** 195 * Setter to specify the format of the regular expression to match. 196 * 197 * @param format the format of the regular expression to match. 198 */ 199 public void setFormat(String format) { 200 this.format = format; 201 } 202 203 /** 204 * Setter to specify the message which is used to notify about violations, 205 * if empty then default (hard-coded) message is used. 206 * 207 * @param message the message to report for a match. 208 */ 209 public void setMessage(String message) { 210 this.message = message; 211 } 212 213 /** 214 * Setter to specify the minimum number of matches required in each file. 215 * 216 * @param minimum the minimum number of matches required in each file. 217 */ 218 public void setMinimum(int minimum) { 219 this.minimum = minimum; 220 } 221 222 /** 223 * Setter to specify the maximum number of matches required in each file. 224 * 225 * @param maximum the maximum number of matches required in each file. 226 */ 227 public void setMaximum(int maximum) { 228 this.maximum = maximum; 229 } 230 231 /** 232 * Setter to control whether to ignore case when searching. 233 * 234 * @param ignoreCase whether to ignore case when searching. 235 */ 236 public void setIgnoreCase(boolean ignoreCase) { 237 this.ignoreCase = ignoreCase; 238 } 239 240 /** 241 * Setter to control whether to match expressions across multiple lines. 242 * 243 * @param matchAcrossLines whether to match expressions across multiple lines. 244 */ 245 public void setMatchAcrossLines(boolean matchAcrossLines) { 246 this.matchAcrossLines = matchAcrossLines; 247 } 248 249}