View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.regexp;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck.MSG_EMPTY;
24  import static com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck.MSG_REGEXP_EXCEEDED;
25  import static com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck.MSG_REGEXP_MINIMUM;
26  import static com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck.MSG_STACKOVERFLOW;
27  
28  import java.io.File;
29  import java.nio.charset.StandardCharsets;
30  import java.nio.file.Files;
31  
32  import org.junit.jupiter.api.Test;
33  import org.junit.jupiter.api.io.TempDir;
34  
35  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
36  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
37  import com.puppycrawl.tools.checkstyle.api.FileText;
38  import com.puppycrawl.tools.checkstyle.internal.testmodules.TestLoggingReporter;
39  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
40  
41  public class RegexpMultilineCheckTest extends AbstractModuleTestSupport {
42  
43      @TempDir
44      public File temporaryFolder;
45  
46      @Override
47      public String getPackageLocation() {
48          return "com/puppycrawl/tools/checkstyle/checks/regexp/regexpmultiline";
49      }
50  
51      @Test
52      public void testIt() throws Exception {
53          final String[] expected = {
54              "78: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "System\\.(out)|(err)\\.print(ln)?\\("),
55          };
56          verifyWithInlineConfigParser(
57                  getPath("InputRegexpMultilineSemantic.java"), expected);
58      }
59  
60      @Test
61      public void testMessageProperty()
62              throws Exception {
63          final String[] expected = {
64              "79: " + "Bad line :(",
65          };
66          verifyWithInlineConfigParser(
67                  getPath("InputRegexpMultilineSemantic2.java"), expected);
68      }
69  
70      @Test
71      public void testIgnoreCaseTrue() throws Exception {
72          final String[] expected = {
73              "79: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "SYSTEM\\.(OUT)|(ERR)\\.PRINT(LN)?\\("),
74          };
75          verifyWithInlineConfigParser(
76                  getPath("InputRegexpMultilineSemantic3.java"), expected);
77      }
78  
79      @Test
80      public void testIgnoreCaseFalse() throws Exception {
81          final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
82          verifyWithInlineConfigParser(
83                  getPath("InputRegexpMultilineSemantic4.java"), expected);
84      }
85  
86      @Test
87      public void testIllegalFailBelowErrorLimit() throws Exception {
88          final String[] expected = {
89              "16: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "^import"),
90              "17: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "^import"),
91              "18: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "^import"),
92          };
93          verifyWithInlineConfigParser(
94                  getPath("InputRegexpMultilineSemantic5.java"), expected);
95      }
96  
97      @Test
98      public void testCarriageReturn() throws Exception {
99          final DefaultConfiguration checkConfig = createModuleConfig(RegexpMultilineCheck.class);
100         checkConfig.addProperty("format", "\\r");
101         checkConfig.addProperty("maximum", "0");
102         final String[] expected = {
103             "1: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "\\r"),
104             "3: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "\\r"),
105         };
106 
107         final File file = Files.createTempFile(temporaryFolder.toPath(), "junit", null).toFile();
108         Files.writeString(file.toPath(),
109                 "first line \r\n second line \n\r third line");
110 
111         verify(checkConfig, file.getPath(), expected);
112     }
113 
114     @Test
115     public void testMaximum() throws Exception {
116         final DefaultConfiguration checkConfig = createModuleConfig(RegexpMultilineCheck.class);
117         checkConfig.addProperty("format", "\\r");
118         checkConfig.addProperty("maximum", "1");
119         final String[] expected = {
120             "3: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "\\r"),
121         };
122 
123         final File file = Files.createTempFile(temporaryFolder.toPath(), "junit", null).toFile();
124         Files.writeString(file.toPath(),
125                 "first line \r\n second line \n\r third line");
126 
127         verify(checkConfig, file.getPath(), expected);
128     }
129 
130     /**
131      * Done as a UT cause new instance of Detector is created each time 'verify' executed.
132      *
133      * @throws Exception some Exception
134      */
135     @Test
136     public void testStateIsBeingReset() throws Exception {
137         final TestLoggingReporter reporter = new TestLoggingReporter();
138         final DetectorOptions detectorOptions = DetectorOptions.newBuilder()
139                 .reporter(reporter)
140                 .format("\\r")
141                 .maximum(1)
142                 .build();
143 
144         final MultilineDetector detector =
145                 new MultilineDetector(detectorOptions,
146                         MSG_REGEXP_EXCEEDED, MSG_REGEXP_MINIMUM,
147                         MSG_EMPTY, MSG_STACKOVERFLOW);
148         final File file = Files.createTempFile(temporaryFolder.toPath(), "junit", null).toFile();
149         Files.writeString(file.toPath(),
150                 "first line \r\n second line \n\r third line");
151 
152         detector.processLines(new FileText(file, StandardCharsets.UTF_8.name()));
153         detector.processLines(new FileText(file, StandardCharsets.UTF_8.name()));
154         assertWithMessage("Logged unexpected amount of issues")
155                 .that(reporter.getLogCount())
156                 .isEqualTo(2);
157     }
158 
159     @Test
160     public void testDefaultConfiguration() throws Exception {
161         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
162         verifyWithInlineConfigParser(
163                 getPath("InputRegexpMultilineSemantic6.java"), expected);
164     }
165 
166     @Test
167     public void testNullFormat() throws Exception {
168         final String[] expected = {
169             "1: " + getCheckMessage(MSG_EMPTY),
170         };
171         verifyWithInlineConfigParser(
172                 getPath("InputRegexpMultilineSemantic7.java"), expected);
173     }
174 
175     @Test
176     public void testEmptyFormat() throws Exception {
177         final String[] expected = {
178             "1: " + getCheckMessage(MSG_EMPTY),
179         };
180         verifyWithInlineConfigParser(
181                 getPath("InputRegexpMultilineSemantic8.java"), expected);
182     }
183 
184     @Test
185     public void testNoStackOverflowError() throws Exception {
186         final DefaultConfiguration checkConfig = createModuleConfig(RegexpMultilineCheck.class);
187         // http://madbean.com/2004/mb2004-20/
188         checkConfig.addProperty("format", "(x|y)*");
189 
190         final String[] expected = {
191             "1: " + getCheckMessage(MSG_STACKOVERFLOW, "(x|y)*"),
192         };
193 
194         final File file = Files.createTempFile(temporaryFolder.toPath(), "junit", null).toFile();
195         Files.writeString(file.toPath(), makeLargeXyString().toString());
196 
197         verify(checkConfig, file.getPath(), expected);
198     }
199 
200     @Test
201     public void testMinimum() throws Exception {
202         final DefaultConfiguration checkConfig = createModuleConfig(RegexpMultilineCheck.class);
203         checkConfig.addProperty("format", "\\r");
204         checkConfig.addProperty("minimum", "5");
205         final String[] expected = {
206             "1: " + getCheckMessage(MSG_REGEXP_MINIMUM, "5", "\\r"),
207         };
208 
209         final File file = Files.createTempFile(temporaryFolder.toPath(), "junit", null).toFile();
210         Files.writeString(file.toPath(), "");
211 
212         verify(checkConfig, file.getPath(), expected);
213     }
214 
215     @Test
216     public void testMinimumWithCustomMessage() throws Exception {
217         final DefaultConfiguration checkConfig = createModuleConfig(RegexpMultilineCheck.class);
218         checkConfig.addProperty("format", "\\r");
219         checkConfig.addProperty("minimum", "5");
220         checkConfig.addProperty("message", "some message");
221         final String[] expected = {
222             "1: some message",
223         };
224 
225         final File file = Files.createTempFile(temporaryFolder.toPath(), "junit", null).toFile();
226         Files.writeString(file.toPath(), "");
227 
228         verify(checkConfig, file.getPath(), expected);
229     }
230 
231     private static CharSequence makeLargeXyString() {
232         // now needs 10'000 or 100'000, as just 1000 is no longer enough today to provoke the
233         // StackOverflowError
234         final int size = 100_000;
235         return "xy".repeat(size / 2);
236     }
237 
238     @Test
239     public void testGoodLimit() throws Exception {
240         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
241         verifyWithInlineConfigParser(
242                 getPath("InputRegexpMultilineSemantic9.java"), expected);
243     }
244 
245     @Test
246     public void testMultilineSupport() throws Exception {
247         final String[] expected = {
248             "22: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "(a)bc.*def"),
249         };
250         verifyWithInlineConfigParser(
251                 getPath("InputRegexpMultilineMultilineSupport.java"), expected);
252     }
253 
254     @Test
255     public void testMultilineSupportNotGreedy() throws Exception {
256         final String[] expected = {
257             "22: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "(a)bc.*?def"),
258             "24: " + getCheckMessage(MSG_REGEXP_EXCEEDED, "(a)bc.*?def"),
259         };
260         verifyWithInlineConfigParser(
261                 getPath("InputRegexpMultilineMultilineSupport2.java"), expected);
262     }
263 
264 }