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