1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.bdd;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.StringReader;
25 import java.math.BigDecimal;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.BitSet;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.Properties;
39 import java.util.Set;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
42 import java.util.stream.Collectors;
43
44 import org.xml.sax.InputSource;
45
46 import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
47 import com.puppycrawl.tools.checkstyle.PropertiesExpander;
48 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
49 import com.puppycrawl.tools.checkstyle.api.Configuration;
50 import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
51 import com.puppycrawl.tools.checkstyle.meta.ModuleDetails;
52 import com.puppycrawl.tools.checkstyle.meta.ModulePropertyDetails;
53 import com.puppycrawl.tools.checkstyle.meta.XmlMetaReader;
54 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
55 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
56
57 public final class InlineConfigParser {
58
59
60 private static final Pattern SLASH_PATTERN = Pattern.compile("[\\\\/]");
61
62
63
64
65
66
67
68
69
70
71 private static final String TRIPLE_QUOTE = "\"\"\"";
72
73
74
75
76
77 private static final Pattern MULTILINE_CONTINUATION_PATTERN =
78 Pattern.compile("^\\s*//(.*)$");
79
80
81
82
83
84 private static final Pattern VIOLATION_MESSAGE_PATTERN = Pattern
85 .compile(".*//\\s*(?:['\"](.*)['\"])?$");
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 private static final Pattern VIOLATION_PATTERN = Pattern
108 .compile(".*//\\s*violation,?\\s*(?:['\"](.*)['\"])?$");
109
110
111 private static final Pattern VIOLATION_ABOVE_PATTERN = Pattern
112 .compile(".*//\\s*violation above,?\\s*(?:['\"](.*))?$");
113
114
115 private static final Pattern VIOLATION_BELOW_PATTERN = Pattern
116 .compile(".*//\\s*violation below,?\\s*(?:['\"](.*))?$");
117
118
119 private static final Pattern VIOLATION_ABOVE_WITH_EXPLANATION_PATTERN = Pattern
120 .compile(".*//\\s*violation above,\\s.+\\s(?:['\"](.*)['\"])?$");
121
122
123 private static final Pattern VIOLATION_BELOW_WITH_EXPLANATION_PATTERN = Pattern
124 .compile(".*//\\s*violation below,\\s.+\\s(?:['\"](.*)['\"])?$");
125
126
127 private static final Pattern VIOLATION_WITH_EXPLANATION_PATTERN = Pattern
128 .compile(".*//\\s*violation,\\s+(?:.*)?$");
129
130
131 private static final Pattern MULTIPLE_VIOLATIONS_PATTERN = Pattern
132 .compile(".*//\\s*(\\d+) violations$");
133
134
135 private static final Pattern MULTIPLE_VIOLATIONS_ABOVE_PATTERN = Pattern
136 .compile(".*//\\s*(\\d+) violations above$");
137
138
139 private static final Pattern MULTIPLE_VIOLATIONS_BELOW_PATTERN = Pattern
140 .compile(".*//\\s*(\\d+) violations below$");
141
142
143 private static final Pattern FILTERED_VIOLATION_PATTERN = Pattern
144 .compile(".*//\\s*filtered violation\\s*(?:['\"](.*)['\"])?$");
145
146
147 private static final Pattern FILTERED_VIOLATION_ABOVE_PATTERN = Pattern
148 .compile(".*//\\s*filtered violation above\\s*(?:['\"](.*)['\"])?$");
149
150
151 private static final Pattern FILTERED_VIOLATION_BELOW_PATTERN = Pattern
152 .compile(".*//\\s*filtered violation below\\s*(?:['\"](.*)['\"])?$");
153
154
155 private static final Pattern FILTERED_VIOLATION_SOME_LINES_ABOVE_PATTERN = Pattern
156 .compile(".*//\\s*filtered violation (\\d+) lines above\\s*(?:['\"](.*))?$");
157
158
159 private static final Pattern FILTERED_VIOLATION_SOME_LINES_BELOW_PATTERN = Pattern
160 .compile(".*//\\s*filtered violation (\\d+) lines below\\s*(?:['\"](.*))?$");
161
162
163 private static final Pattern VIOLATION_SOME_LINES_ABOVE_PATTERN = Pattern
164 .compile(".*//\\s*violation (\\d+) lines above\\s*(?:['\"](.*))?$");
165
166
167 private static final Pattern VIOLATION_SOME_LINES_BELOW_PATTERN = Pattern
168 .compile(".*//\\s*violation (\\d+) lines below\\s*(?:['\"](.*))?$");
169
170
171
172
173
174
175
176
177
178
179
180
181
182 private static final Pattern VIOLATIONS_ABOVE_PATTERN_WITH_MESSAGES = Pattern
183 .compile(".*//\\s*(\\d+) violations above:$");
184
185
186
187
188
189
190
191
192
193
194
195
196
197 private static final Pattern VIOLATIONS_SOME_LINES_ABOVE_PATTERN = Pattern
198 .compile(".*//\\s*(\\d+) violations (\\d+) lines above:$");
199
200
201
202
203
204
205
206
207
208
209
210
211
212 private static final Pattern VIOLATIONS_SOME_LINES_BELOW_PATTERN = Pattern
213 .compile(".*//\\s*(\\d+) violations (\\d+) lines below:$");
214
215
216 private static final Pattern VIOLATION_DEFAULT = Pattern
217 .compile("//.*violation.*");
218
219
220 private static final String NULL_STRING = "(null)";
221
222 private static final String LATEST_DTD = String.format(Locale.ROOT,
223 "<!DOCTYPE module PUBLIC \"%s\" \"%s\">%n",
224 ConfigurationLoader.DTD_PUBLIC_CS_ID_1_3,
225 ConfigurationLoader.DTD_PUBLIC_CS_ID_1_3);
226
227
228
229
230
231
232 private static final Pattern ALLOWED_OK_VIOLATION_PATTERN =
233 Pattern.compile(".*//\\s*(ok|violation)\\b(?:[ ,]\\s*.*)?$");
234
235
236
237
238 private static final Pattern ANY_OK_VIOLATION_PATTERN =
239 Pattern.compile(".*//\\s*(?i)(ok|violation).*");
240
241
242
243
244
245
246 private static final Set<String> PERMANENT_SUPPRESSED_CHECKS = Set.of(
247
248 "com.puppycrawl.tools.checkstyle.checks.OrderedPropertiesCheck",
249 "com.puppycrawl.tools.checkstyle.checks.UniquePropertiesCheck",
250 "com.puppycrawl.tools.checkstyle.checks.TranslationCheck"
251 );
252
253
254
255
256
257 private static final Set<String> SUPPRESSED_CHECKS = Set.of(
258 "com.puppycrawl.tools.checkstyle.checks.coding.MultipleStringLiteralsCheck",
259 "com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck",
260
261 "com.puppycrawl.tools.checkstyle.checks.design.InnerTypeLastCheck",
262 "com.puppycrawl.tools.checkstyle.checks.design.OneTopLevelClassCheck",
263 "com.puppycrawl.tools.checkstyle.checks.javadoc."
264 + "AbstractJavadocCheckTest$TokenIsNotInAcceptablesCheck",
265 "com.puppycrawl.tools.checkstyle.checks.javadoc.AtclauseOrderCheck",
266 "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocBlockTagLocationCheck",
267 "com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocTypeCheck",
268 "com.puppycrawl.tools.checkstyle.checks.javadoc"
269 + ".RequireEmptyLineBeforeBlockTagGroupCheck",
270 "com.puppycrawl.tools.checkstyle.checks.metrics.ClassFanOutComplexityCheck",
271 "com.puppycrawl.tools.checkstyle.checks.metrics.NPathComplexityCheck",
272 "com.puppycrawl.tools.checkstyle.checks.modifier.ClassMemberImpliedModifierCheck",
273 "com.puppycrawl.tools.checkstyle.checks.modifier.InterfaceMemberImpliedModifierCheck",
274 "com.puppycrawl.tools.checkstyle.checks.modifier.RedundantModifierCheck",
275 "com.puppycrawl.tools.checkstyle.checks.naming.AbbreviationAsWordInNameCheck",
276
277 "com.puppycrawl.tools.checkstyle.checks.naming.IllegalIdentifierNameCheck",
278
279 "com.puppycrawl.tools.checkstyle.checks.naming.RecordTypeParameterNameCheck",
280 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck",
281 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck",
282 "com.puppycrawl.tools.checkstyle.checks.sizes.AnonInnerLengthCheck",
283 "com.puppycrawl.tools.checkstyle.checks.sizes.ExecutableStatementCountCheck",
284 "com.puppycrawl.tools.checkstyle.checks.sizes.OuterTypeNumberCheck",
285 "com.puppycrawl.tools.checkstyle.checks.sizes.RecordComponentNumberCheck",
286 "com.puppycrawl.tools.checkstyle.checks.TrailingCommentCheck",
287 "com.puppycrawl.tools.checkstyle.checks.whitespace.SingleSpaceSeparatorCheck",
288 "com.puppycrawl.tools.checkstyle.api.AbstractCheckTest$ViolationAstCheck",
289 "com.puppycrawl.tools.checkstyle.CheckerTest$VerifyPositionAfterTabFileSet"
290 );
291
292
293
294
295
296 private static final Set<String> SUPPRESSED_FILES = Set.of(
297 "InputAvoidEscapedUnicodeCharactersAllEscapedUnicodeCharacters.java"
298 );
299
300
301
302
303
304 private static final Set<String> SUPPRESSED_MODULES = Set.of(
305 "com.puppycrawl.tools.checkstyle.checks.coding.IllegalTypeCheck",
306 "com.puppycrawl.tools.checkstyle.checks.SuppressWarningsHolder",
307 "com.puppycrawl.tools.checkstyle.filters.SuppressionCommentFilter",
308 "com.puppycrawl.tools.checkstyle.filters.SuppressionXpathFilter",
309 "com.puppycrawl.tools.checkstyle.filters.SuppressionXpathSingleFilter"
310 );
311
312
313
314
315
316 private static final Set<String> SUPPRESSED_VALIDATE_DEFAULT_FILES = Set.of(
317 "checks/coding/matchxpath/InputMatchXpath2.java"
318 );
319
320
321 private static final Map<String, String> MODULE_MAPPINGS = new HashMap<>();
322
323 private static final Map<String, ModuleDetails> PUBLIC_MODULE_DETAILS_MAP = new HashMap<>();
324
325
326 static {
327 MODULE_MAPPINGS.put("IllegalCatch",
328 "com.puppycrawl.tools.checkstyle.checks.coding.IllegalCatchCheck");
329 MODULE_MAPPINGS.put("MagicNumber",
330 "com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck");
331 MODULE_MAPPINGS.put("SummaryJavadoc",
332 "com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck");
333 MODULE_MAPPINGS.put("ClassDataAbstractionCoupling",
334 "com.puppycrawl.tools.checkstyle.checks.metrics.ClassDataAbstractionCouplingCheck");
335 MODULE_MAPPINGS.put("ConstantName",
336 "com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck");
337 MODULE_MAPPINGS.put("MemberName",
338 "com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck");
339 MODULE_MAPPINGS.put("GoogleNonConstantFieldName",
340 "com.puppycrawl.tools.checkstyle.checks.naming.GoogleNonConstantFieldNameCheck");
341 MODULE_MAPPINGS.put("MethodName",
342 "com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck");
343 MODULE_MAPPINGS.put("ParameterName",
344 "com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck");
345 MODULE_MAPPINGS.put("RegexpOnFilename",
346 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpOnFilenameCheck");
347 MODULE_MAPPINGS.put("RegexpSingleline",
348 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck");
349 MODULE_MAPPINGS.put("RegexpSinglelineJava",
350 "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck");
351 MODULE_MAPPINGS.put("LineLength",
352 "com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck");
353 MODULE_MAPPINGS.put("ParameterNumber",
354 "com.puppycrawl.tools.checkstyle.checks.sizes.ParameterNumberCheck");
355 MODULE_MAPPINGS.put("NoWhitespaceAfter",
356 "com.puppycrawl.tools.checkstyle.checks.whitespace.NoWhitespaceAfterCheck");
357 MODULE_MAPPINGS.put("OrderedProperties",
358 "com.puppycrawl.tools.checkstyle.checks.OrderedPropertiesCheck");
359 MODULE_MAPPINGS.put("SuppressWarningsHolder",
360 "com.puppycrawl.tools.checkstyle.checks.SuppressWarningsHolder");
361 MODULE_MAPPINGS.put("UniqueProperties",
362 "com.puppycrawl.tools.checkstyle.checks.UniquePropertiesCheck");
363 MODULE_MAPPINGS.put("SuppressionXpathSingleFilter",
364 "com.puppycrawl.tools.checkstyle.filters.SuppressionXpathSingleFilter");
365 MODULE_MAPPINGS.put("SuppressWarningsFilter",
366 "com.puppycrawl.tools.checkstyle.filters.SuppressWarningsFilter");
367 MODULE_MAPPINGS.put("LeftCurly",
368 "com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck");
369 MODULE_MAPPINGS.put("RequireThis",
370 "com.puppycrawl.tools.checkstyle.checks.coding.RequireThisCheck");
371 MODULE_MAPPINGS.put("IllegalThrows",
372 "com.puppycrawl.tools.checkstyle.checks.coding.IllegalThrowsCheck");
373 MODULE_MAPPINGS.put("LocalFinalVariableName",
374 "com.puppycrawl.tools.checkstyle.checks.naming.LocalFinalVariableNameCheck");
375 MODULE_MAPPINGS.put("PackageName",
376 "com.puppycrawl.tools.checkstyle.checks.naming.PackageNameCheck");
377 MODULE_MAPPINGS.put("RedundantModifier",
378 "com.puppycrawl.tools.checkstyle.checks.modifier.RedundantModifierCheck");
379 MODULE_MAPPINGS.put("AbstractClassName",
380 "com.puppycrawl.tools.checkstyle.checks.naming.AbstractClassNameCheck");
381 MODULE_MAPPINGS.put("JavadocMethod",
382 "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck");
383 MODULE_MAPPINGS.put("IllegalIdentifierName",
384 "com.puppycrawl.tools.checkstyle.checks.naming.IllegalIdentifierNameCheck");
385 MODULE_MAPPINGS.put("FileLength",
386 "com.puppycrawl.tools.checkstyle.checks.sizes.FileLengthCheck");
387 MODULE_MAPPINGS.put("EqualsAvoidNull",
388 "com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck");
389 MODULE_MAPPINGS.put("JavadocStyle",
390 "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck");
391 MODULE_MAPPINGS.put("CyclomaticComplexity",
392 "com.puppycrawl.tools.checkstyle.checks.metrics.CyclomaticComplexityCheck");
393 MODULE_MAPPINGS.put("EmptyLineSeparator",
394 "com.puppycrawl.tools.checkstyle.checks.whitespace.EmptyLineSeparatorCheck");
395 MODULE_MAPPINGS.put("LocalVariableName",
396 "com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck");
397 MODULE_MAPPINGS.put("ModifierOrder",
398 "com.puppycrawl.tools.checkstyle.checks.modifier.ModifierOrderCheck");
399 }
400
401
402 private InlineConfigParser() {
403 }
404
405 public static TestInputConfiguration parse(String inputFilePath) throws Exception {
406 return parse(inputFilePath, false);
407 }
408
409
410
411
412
413
414
415
416 private static TestInputConfiguration parse(String inputFilePath,
417 boolean setFilteredViolations) throws Exception {
418 final TestInputConfiguration.Builder testInputConfigBuilder =
419 new TestInputConfiguration.Builder();
420 final Path filePath = Path.of(inputFilePath);
421 final List<String> lines = readFile(filePath);
422 try {
423 setModules(testInputConfigBuilder, inputFilePath, lines);
424 }
425 catch (Exception exc) {
426 throw new CheckstyleException("Config comment not specified properly in "
427 + inputFilePath, exc);
428 }
429 try {
430 setViolations(testInputConfigBuilder, lines, setFilteredViolations, inputFilePath);
431 }
432 catch (CheckstyleException exc) {
433 throw new CheckstyleException("Failed to set violations in " + inputFilePath, exc);
434 }
435 return testInputConfigBuilder.build();
436 }
437
438 public static List<TestInputViolation> getViolationsFromInputFile(String inputFilePath)
439 throws Exception {
440 final TestInputConfiguration.Builder testInputConfigBuilder =
441 new TestInputConfiguration.Builder();
442 final Path filePath = Path.of(inputFilePath);
443 final List<String> lines = readFile(filePath);
444
445 try {
446 for (int lineNo = 0; lineNo < lines.size(); lineNo++) {
447 setViolations(testInputConfigBuilder, lines, false, lineNo, true);
448 }
449 }
450 catch (CheckstyleException exc) {
451 throw new CheckstyleException("Failed to set violations in " + inputFilePath, exc);
452 }
453
454 return testInputConfigBuilder.build().getViolations();
455 }
456
457 public static List<TestInputViolation> getFilteredViolationsFromInputFile(String inputFilePath)
458 throws Exception {
459 final TestInputConfiguration.Builder testInputConfigBuilder =
460 new TestInputConfiguration.Builder();
461 final Path filePath = Path.of(inputFilePath);
462 final List<String> lines = readFile(filePath);
463
464 try {
465 for (int lineNo = 0; lineNo < lines.size(); lineNo++) {
466 setViolations(testInputConfigBuilder, lines, true, lineNo, true);
467 }
468 }
469 catch (CheckstyleException exc) {
470 throw new CheckstyleException("Failed to set violations in " + inputFilePath, exc);
471 }
472
473 return testInputConfigBuilder.build().getFilteredViolations();
474 }
475
476 public static TestInputConfiguration parseWithFilteredViolations(String inputFilePath)
477 throws Exception {
478 return parse(inputFilePath, true);
479 }
480
481
482
483
484
485
486
487 public static TestInputConfiguration parseWithXmlHeader(String inputFilePath)
488 throws Exception {
489
490 final Path filePath = Path.of(inputFilePath);
491 final List<String> lines = readFile(filePath);
492 if (!checkIsXmlConfig(lines)) {
493 throw new CheckstyleException("Config cannot be parsed as xml.");
494 }
495
496 final List<String> inlineConfig = getInlineConfig(lines);
497 final String stringXmlConfig = LATEST_DTD + String.join("", inlineConfig);
498 final InputSource inputSource = new InputSource(new StringReader(stringXmlConfig));
499 final Configuration xmlConfig = ConfigurationLoader.loadConfiguration(
500 inputSource, new PropertiesExpander(System.getProperties()),
501 ConfigurationLoader.IgnoredModulesOptions.EXECUTE
502 );
503 final String configName = xmlConfig.getName();
504 if (!"Checker".equals(configName)) {
505 throw new CheckstyleException(
506 "First module should be Checker, but was " + configName);
507 }
508
509 final TestInputConfiguration.Builder testInputConfigBuilder =
510 new TestInputConfiguration.Builder();
511 testInputConfigBuilder.setXmlConfiguration(xmlConfig);
512 try {
513 setViolations(testInputConfigBuilder, lines, false, inputFilePath);
514 }
515 catch (CheckstyleException exc) {
516 throw new CheckstyleException("Failed to set violations in " + inputFilePath, exc);
517 }
518 return testInputConfigBuilder.buildWithXmlConfiguration();
519 }
520
521
522
523
524
525
526
527 private static boolean checkIsXmlConfig(List<String> lines) {
528 return "/*xml".equals(lines.getFirst());
529 }
530
531 private static void setModules(TestInputConfiguration.Builder testInputConfigBuilder,
532 String inputFilePath, List<String> lines)
533 throws Exception {
534 if (!lines.getFirst().startsWith("/*")) {
535 throw new CheckstyleException("Config not specified on top."
536 + "Please see other inputs for examples of what is required.");
537 }
538
539 final List<String> inlineConfig = getInlineConfig(lines);
540
541 if (checkIsXmlConfig(lines)) {
542 final String stringXmlConfig = LATEST_DTD + String.join("", inlineConfig);
543 final InputSource inputSource = new InputSource(new StringReader(stringXmlConfig));
544 final Configuration xmlConfig = ConfigurationLoader.loadConfiguration(
545 inputSource, new PropertiesExpander(System.getProperties()),
546 ConfigurationLoader.IgnoredModulesOptions.EXECUTE
547 );
548 final String configName = xmlConfig.getName();
549 if (!"Checker".equals(configName)) {
550 throw new CheckstyleException(
551 "First module should be Checker, but was " + configName);
552 }
553 handleXmlConfig(testInputConfigBuilder, inputFilePath, xmlConfig.getChildren());
554 }
555 else {
556 handleKeyValueConfig(testInputConfigBuilder, inputFilePath, inlineConfig);
557 }
558 }
559
560 private static List<String> getInlineConfig(List<String> lines) {
561 return lines.stream()
562 .skip(1)
563 .takeWhile(line -> !line.startsWith("*/"))
564 .toList();
565 }
566
567 private static void handleXmlConfig(TestInputConfiguration.Builder testInputConfigBuilder,
568 String inputFilePath,
569 Configuration... modules)
570 throws CheckstyleException {
571
572 for (Configuration module: modules) {
573 final String moduleName = module.getName();
574 if ("TreeWalker".equals(moduleName)) {
575 handleXmlConfig(testInputConfigBuilder, inputFilePath, module.getChildren());
576 }
577 else {
578 final ModuleInputConfiguration.Builder moduleInputConfigBuilder =
579 new ModuleInputConfiguration.Builder();
580 setModuleName(moduleInputConfigBuilder, inputFilePath, moduleName);
581 setProperties(inputFilePath, module, moduleInputConfigBuilder);
582 testInputConfigBuilder.addChildModule(moduleInputConfigBuilder.build());
583 }
584 }
585 }
586
587 private static void handleKeyValueConfig(TestInputConfiguration.Builder testInputConfigBuilder,
588 String inputFilePath, List<String> lines)
589 throws CheckstyleException, IOException, ReflectiveOperationException {
590 int lineNo = 0;
591 while (lineNo < lines.size()) {
592 final ModuleInputConfiguration.Builder moduleInputConfigBuilder =
593 new ModuleInputConfiguration.Builder();
594 final String moduleName = lines.get(lineNo);
595 setModuleName(moduleInputConfigBuilder, inputFilePath, moduleName);
596 setProperties(moduleInputConfigBuilder, inputFilePath, lines, lineNo + 1, moduleName);
597 testInputConfigBuilder.addChildModule(moduleInputConfigBuilder.build());
598 do {
599 lineNo++;
600 } while (lineNo < lines.size()
601 && lines.get(lineNo).isEmpty()
602 || !lines.get(lineNo - 1).isEmpty());
603 }
604 }
605
606 private static Map<String, String> getDefaultProperties(String fullyQualifiedClassName) {
607
608 final Map<String, String> defaultProperties = new HashMap<>();
609 final boolean isSuppressedModule = SUPPRESSED_MODULES.contains(fullyQualifiedClassName);
610
611 if (PUBLIC_MODULE_DETAILS_MAP.isEmpty()) {
612 XmlMetaReader.readAllModulesIncludingThirdPartyIfAny().forEach(module -> {
613 PUBLIC_MODULE_DETAILS_MAP.put(module.getFullQualifiedName(), module);
614 });
615 }
616
617 final ModuleDetails moduleDetails = PUBLIC_MODULE_DETAILS_MAP.get(fullyQualifiedClassName);
618
619 if (!isSuppressedModule && moduleDetails != null) {
620 defaultProperties.putAll(moduleDetails.getProperties().stream()
621 .filter(prop -> {
622 return prop.getName() != null && prop.getDefaultValue() != null;
623 })
624 .collect(Collectors.toUnmodifiableMap(
625 ModulePropertyDetails::getName,
626 ModulePropertyDetails::getDefaultValue
627 )));
628 }
629
630 return defaultProperties;
631 }
632
633 private static String getFullyQualifiedClassName(String filePath, String moduleName)
634 throws CheckstyleException {
635 String fullyQualifiedClassName;
636 if (MODULE_MAPPINGS.containsKey(moduleName)) {
637 fullyQualifiedClassName = MODULE_MAPPINGS.get(moduleName);
638 }
639 else if (moduleName.startsWith("com.")) {
640 fullyQualifiedClassName = moduleName;
641 }
642 else {
643 final String path = SLASH_PATTERN.matcher(filePath).replaceAll(".");
644 final int endIndex = path.lastIndexOf(moduleName.toLowerCase(Locale.ROOT));
645 if (endIndex == -1) {
646 throw new CheckstyleException("Unable to resolve module name: " + moduleName
647 + ". Please check for spelling errors or specify fully qualified class name.");
648 }
649 final int beginIndex = path.indexOf("com.puppycrawl");
650 fullyQualifiedClassName = path.substring(beginIndex, endIndex) + moduleName;
651 if (!fullyQualifiedClassName.endsWith("Filter")) {
652 fullyQualifiedClassName += "Check";
653 }
654 }
655 return fullyQualifiedClassName;
656 }
657
658 private static String getFilePath(String fileName, String inputFilePath) {
659 final int lastSlashIndex = Math.max(inputFilePath.lastIndexOf('\\'),
660 inputFilePath.lastIndexOf('/'));
661 final String root = inputFilePath.substring(0, lastSlashIndex + 1);
662 return root + fileName;
663 }
664
665 private static String getResourcePath(String fileName, String inputFilePath) {
666 final String filePath = getUriPath(fileName, inputFilePath);
667 final int lastSlashIndex = filePath.lastIndexOf('/');
668 final String root = filePath.substring(filePath.indexOf("puppycrawl") - 5,
669 lastSlashIndex + 1);
670 return root + fileName;
671 }
672
673 private static String getUriPath(String fileName, String inputFilePath) {
674 return new File(getFilePath(fileName, inputFilePath)).toURI().toString();
675 }
676
677 private static String getResolvedPath(String fileValue, String inputFilePath) {
678 final String resolvedFilePath;
679
680 if (fileValue.startsWith("(resource)")) {
681 resolvedFilePath =
682 getResourcePath(fileValue.substring(fileValue.indexOf(')') + 1),
683 inputFilePath);
684 }
685 else if (fileValue.startsWith("(uri)")) {
686 resolvedFilePath =
687 getUriPath(fileValue.substring(fileValue.indexOf(')') + 1), inputFilePath);
688 }
689 else if (fileValue.contains("/") || fileValue.contains("\\")) {
690 resolvedFilePath = fileValue;
691 }
692 else {
693 resolvedFilePath = getFilePath(fileValue, inputFilePath);
694 }
695
696 return resolvedFilePath;
697 }
698
699 private static List<String> readFile(Path filePath) throws CheckstyleException {
700 try {
701 return Files.readAllLines(filePath);
702 }
703 catch (IOException exc) {
704 throw new CheckstyleException("Failed to read " + filePath, exc);
705 }
706 }
707
708 private static void setModuleName(ModuleInputConfiguration.Builder moduleInputConfigBuilder,
709 String filePath, String moduleName)
710 throws CheckstyleException {
711 final String fullyQualifiedClassName = getFullyQualifiedClassName(filePath, moduleName);
712 moduleInputConfigBuilder.setModuleName(fullyQualifiedClassName);
713 }
714
715 private static String toStringConvertForArrayValue(Object value) {
716 String result = NULL_STRING;
717
718 if (value instanceof double[] arr) {
719 result = Arrays.stream(arr)
720 .boxed()
721 .map(number -> {
722 return BigDecimal.valueOf(number)
723 .stripTrailingZeros()
724 .toPlainString();
725 })
726 .collect(Collectors.joining(","));
727 }
728 else if (value instanceof int[] ints) {
729 result = Arrays.toString(ints).replaceAll("[\\[\\]\\s]", "");
730 }
731 else if (value instanceof boolean[] booleans) {
732 result = Arrays.toString(booleans).replaceAll("[\\[\\]\\s]", "");
733 }
734 else if (value instanceof long[] longs) {
735 result = Arrays.toString(longs).replaceAll("[\\[\\]\\s]", "");
736 }
737 else if (value instanceof Object[] objects) {
738 result = Arrays.toString(objects).replaceAll("[\\[\\]\\s]", "");
739 }
740 return result;
741 }
742
743
744
745
746
747
748
749
750 private static void validateDefault(String propertyName,
751 String propertyDefaultValue,
752 String fullyQualifiedModuleName)
753 throws ReflectiveOperationException {
754 final Object checkInstance = createCheckInstance(fullyQualifiedModuleName);
755 final Object actualDefault;
756 final Class<?> propertyType;
757 final String actualDefaultAsString;
758
759 if ("tokens".equals(propertyName)) {
760 actualDefault = TestUtil.invokeMethod(checkInstance,
761 "getDefaultTokens", Object.class);
762 propertyType = actualDefault.getClass();
763 final int[] arr = (int[]) actualDefault;
764 actualDefaultAsString = Arrays.stream(arr)
765 .mapToObj(TokenUtil::getTokenName)
766 .collect(Collectors.joining(", "));
767 }
768 else if ("javadocTokens".equals(propertyName)) {
769 actualDefault = TestUtil.invokeMethod(checkInstance,
770 "getDefaultJavadocTokens", Object.class);
771 propertyType = actualDefault.getClass();
772 final int[] arr = (int[]) actualDefault;
773 actualDefaultAsString = Arrays.stream(arr)
774 .mapToObj(JavadocUtil::getTokenName)
775 .collect(Collectors.joining(", "));
776 }
777 else {
778 actualDefault = getPropertyDefaultValue(checkInstance, propertyName);
779 if (actualDefault == null) {
780 propertyType = null;
781 }
782 else {
783 propertyType = actualDefault.getClass();
784 }
785 actualDefaultAsString = convertDefaultValueToString(actualDefault);
786 }
787 if (!isDefaultValue(propertyDefaultValue, actualDefaultAsString, propertyType)) {
788 final String message = String.format(Locale.ROOT,
789 "Default value mismatch for %s in %s: specified '%s' but actually is '%s'",
790 propertyName, fullyQualifiedModuleName,
791 propertyDefaultValue, actualDefaultAsString);
792 throw new IllegalArgumentException(message);
793 }
794 }
795
796 private static boolean isCollectionValues(String specifiedDefault, String actualDefault) {
797 final Set<String> specifiedSet = new HashSet<>(
798 Arrays.asList(specifiedDefault.replaceAll("[\\[\\]\\s]", "").split(",")));
799 final Set<String> actualSet = new HashSet<>(
800 Arrays.asList(actualDefault.replaceAll("[\\[\\]\\s]", "").split(",")));
801 return actualSet.containsAll(specifiedSet);
802 }
803
804 private static String convertDefaultValueToString(Object value) {
805 final String defaultValueAsString;
806 if (value == null) {
807 defaultValueAsString = NULL_STRING;
808 }
809 else if (value instanceof String strValue) {
810 defaultValueAsString = toStringForStringValue(strValue);
811 }
812 else if (value.getClass().isArray()) {
813 defaultValueAsString = toStringConvertForArrayValue(value);
814 }
815 else if (value instanceof BitSet set) {
816 defaultValueAsString = toStringForBitSetValue(set);
817 }
818 else if (value instanceof Collection<?> values) {
819 defaultValueAsString = toStringForCollectionValue(values);
820 }
821 else {
822 defaultValueAsString = String.valueOf(value);
823 }
824 return defaultValueAsString;
825 }
826
827 private static String toStringForStringValue(String strValue) {
828 final String str;
829 if (strValue.startsWith("(") && strValue.endsWith(")")) {
830 str = strValue.substring(1, strValue.length() - 1);
831 }
832 else {
833 str = strValue;
834 }
835 return str;
836 }
837
838 private static String toStringForBitSetValue(BitSet bitSet) {
839 return bitSet.stream()
840 .mapToObj(TokenUtil::getTokenName)
841 .collect(Collectors.joining(","));
842 }
843
844 private static String toStringForCollectionValue(Collection<?> collection) {
845 return collection.toString().replaceAll("[\\[\\]\\s]", "");
846 }
847
848
849
850
851
852
853
854
855 private static boolean isDefaultValue(final String propertyDefaultValue,
856 final String actualDefault,
857 final Class<?> fieldType) {
858 final boolean result;
859
860 if (NULL_STRING.equals(actualDefault)) {
861 result = isNull(propertyDefaultValue);
862 }
863 else if (isNumericType(fieldType)) {
864 final BigDecimal specified = new BigDecimal(propertyDefaultValue);
865 final BigDecimal actual = new BigDecimal(actualDefault);
866 result = specified.compareTo(actual) == 0;
867 }
868 else if (fieldType.isArray()
869 || Collection.class.isAssignableFrom(fieldType)
870 || BitSet.class.isAssignableFrom(fieldType)) {
871 result = isCollectionValues(propertyDefaultValue, actualDefault);
872 }
873 else if (fieldType.isEnum() || fieldType.isLocalClass()) {
874 result = propertyDefaultValue.equalsIgnoreCase(actualDefault);
875 }
876 else {
877 result = propertyDefaultValue.equals(actualDefault);
878 }
879 return result;
880 }
881
882 private static Object createCheckInstance(String className) throws
883 ReflectiveOperationException {
884 final Class<?> checkClass = Class.forName(className);
885 return TestUtil.instantiate(checkClass);
886 }
887
888 private static String readPropertiesContent(int beginLineNo, List<String> lines) {
889 final StringBuilder stringBuilder = new StringBuilder(128);
890 int lineNo = beginLineNo;
891 String line = lines.get(lineNo);
892 while (!line.isEmpty() && !"*/".equals(line)) {
893 stringBuilder.append(line).append('\n');
894 lineNo++;
895 line = lines.get(lineNo);
896 }
897 return stringBuilder.toString();
898 }
899
900 private static void validateProperties(Map<String, String> propertiesWithMissingDefaultTag,
901 List<String> unusedProperties) throws CheckstyleException {
902
903 if (!propertiesWithMissingDefaultTag.isEmpty()) {
904
905 final String propertiesList = propertiesWithMissingDefaultTag.entrySet().stream()
906 .map(entry -> {
907 return String.format(Locale.ROOT, "%s = (default)%s",
908 entry.getKey(), entry.getValue());
909 })
910 .collect(Collectors.joining(", "));
911
912 final String message = String.format(Locale.ROOT,
913 "Default properties must use the '(default)' tag."
914 + " Properties missing the '(default)' tag: %s", propertiesList);
915 throw new CheckstyleException(message);
916 }
917 if (!unusedProperties.isEmpty()) {
918 final String message = String.format(Locale.ROOT,
919 "All properties must be explicitly specified."
920 + " Found unused properties: %s", unusedProperties);
921 throw new CheckstyleException(message);
922 }
923 }
924
925 private static void validateDefaultProperties(
926 Map<Object, Object> actualProperties,
927 Map<String, String> defaultProperties) throws CheckstyleException {
928
929 final Map<String, String> matchedProperties = actualProperties.entrySet().stream()
930 .filter(entry -> {
931 return entry.getValue()
932 .equals(defaultProperties.get(entry.getKey().toString()));
933 })
934 .collect(HashMap::new,
935 (map, entry) -> {
936 map.put(entry.getKey().toString(), entry.getValue().toString());
937 }, HashMap::putAll);
938 final List<String> missingProperties = defaultProperties.keySet().stream()
939 .filter(propertyName -> !actualProperties.containsKey(propertyName))
940 .toList();
941
942 validateProperties(matchedProperties, missingProperties);
943 }
944
945 private static void setProperties(ModuleInputConfiguration.Builder inputConfigBuilder,
946 String inputFilePath,
947 List<String> lines,
948 int beginLineNo, String moduleName)
949 throws IOException, CheckstyleException, ReflectiveOperationException {
950
951 final String propertyContent = readPropertiesContent(beginLineNo, lines);
952 final Map<Object, Object> properties = loadProperties(propertyContent);
953 final String fullyQualifiedClassName =
954 getFullyQualifiedClassName(inputFilePath, moduleName);
955
956 final boolean isSuppressedValidateDefaultFile = SUPPRESSED_VALIDATE_DEFAULT_FILES.stream()
957 .anyMatch(Path.of(inputFilePath)::endsWith);
958
959 if (!isSuppressedValidateDefaultFile) {
960 validateDefaultProperties(properties, getDefaultProperties(fullyQualifiedClassName));
961 }
962
963 for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
964 final String key = entry.getKey().toString();
965 final String value = entry.getValue().toString();
966
967 if (key.startsWith("message.")) {
968 inputConfigBuilder.addModuleMessage(key.substring(8), value);
969 }
970 else if (NULL_STRING.equals(value)) {
971 inputConfigBuilder.addNonDefaultProperty(key, null);
972 }
973 else if (value.startsWith("(file)")) {
974 final String fileName = value.substring(value.indexOf(')') + 1);
975 final String filePath = getResolvedPath(fileName, inputFilePath);
976 inputConfigBuilder.addNonDefaultProperty(key, filePath);
977 }
978 else if (value.startsWith("(default)")) {
979 final String defaultValue = value.substring(value.indexOf(')') + 1);
980 validateDefault(key, defaultValue, fullyQualifiedClassName);
981
982 if (NULL_STRING.equals(defaultValue)) {
983 inputConfigBuilder.addDefaultProperty(key, null);
984 }
985 else {
986 inputConfigBuilder.addDefaultProperty(key, defaultValue);
987 }
988 }
989 else {
990 inputConfigBuilder.addNonDefaultProperty(key, value);
991 }
992 }
993 }
994
995 private static void setProperties(String inputFilePath, Configuration module,
996 ModuleInputConfiguration.Builder moduleInputConfigBuilder)
997 throws CheckstyleException {
998 final String[] getPropertyNames = module.getPropertyNames();
999 for (final String propertyName : getPropertyNames) {
1000 final String propertyValue = module.getProperty(propertyName);
1001
1002 if ("file".equals(propertyName)) {
1003 final String filePath = getResolvedPath(propertyValue, inputFilePath);
1004 moduleInputConfigBuilder.addNonDefaultProperty(propertyName, filePath);
1005 }
1006 else {
1007 if (NULL_STRING.equals(propertyValue)) {
1008 moduleInputConfigBuilder.addNonDefaultProperty(propertyName, null);
1009 }
1010 else {
1011 moduleInputConfigBuilder.addNonDefaultProperty(propertyName, propertyValue);
1012 }
1013 }
1014 }
1015
1016 final Map<String, String> messages = module.getMessages();
1017 for (final Map.Entry<String, String> entry : messages.entrySet()) {
1018 final String key = entry.getKey();
1019 final String value = entry.getValue();
1020 moduleInputConfigBuilder.addModuleMessage(key, value);
1021 }
1022 }
1023
1024 private static boolean shouldSpecifyViolationMessage(
1025 TestInputConfiguration.Builder inputConfigBuilder,
1026 String inputFilePath) {
1027
1028 boolean result = false;
1029
1030 final List<ModuleInputConfiguration> moduleLists =
1031 inputConfigBuilder.getChildrenModules();
1032
1033 if (moduleLists.size() == 1) {
1034 final String moduleName = moduleLists.getFirst().getModuleName();
1035
1036 if (!PERMANENT_SUPPRESSED_CHECKS.contains(moduleName)
1037 && !SUPPRESSED_CHECKS.contains(moduleName)) {
1038
1039 final String fileName = Path.of(inputFilePath).getFileName().toString();
1040 if (!SUPPRESSED_FILES.contains(fileName)) {
1041 result = true;
1042 }
1043 }
1044 }
1045
1046 return result;
1047 }
1048
1049 private static void setViolations(TestInputConfiguration.Builder inputConfigBuilder,
1050 List<String> lines,
1051 boolean useFilteredViolations,
1052 String inputFilePath)
1053 throws CheckstyleException {
1054
1055 final boolean specifyViolationMessage =
1056 shouldSpecifyViolationMessage(inputConfigBuilder, inputFilePath);
1057
1058 for (int lineNo = 0; lineNo < lines.size(); lineNo++) {
1059 setViolations(inputConfigBuilder, lines,
1060 useFilteredViolations, lineNo, specifyViolationMessage);
1061 }
1062 }
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079 private static void setViolations(TestInputConfiguration.Builder inputConfigBuilder,
1080 List<String> lines, boolean useFilteredViolations,
1081 int lineNo, boolean specifyViolationMessage)
1082 throws CheckstyleException {
1083 final String line = lines.get(lineNo);
1084 if (ANY_OK_VIOLATION_PATTERN.matcher(line).matches()
1085 && !ALLOWED_OK_VIOLATION_PATTERN.matcher(line).matches()) {
1086 throw new CheckstyleException(
1087 "Invalid format (must be \"// ok...\" or \"// violation...\"): " + line);
1088 }
1089
1090 final Matcher violationMatcher =
1091 VIOLATION_PATTERN.matcher(lines.get(lineNo));
1092 final Matcher violationAboveMatcher =
1093 VIOLATION_ABOVE_PATTERN.matcher(lines.get(lineNo));
1094 final Matcher violationBelowMatcher =
1095 VIOLATION_BELOW_PATTERN.matcher(lines.get(lineNo));
1096 final Matcher violationAboveWithExplanationMatcher =
1097 VIOLATION_ABOVE_WITH_EXPLANATION_PATTERN.matcher(lines.get(lineNo));
1098 final Matcher violationBelowWithExplanationMatcher =
1099 VIOLATION_BELOW_WITH_EXPLANATION_PATTERN.matcher(lines.get(lineNo));
1100 final Matcher violationWithExplanationMatcher =
1101 VIOLATION_WITH_EXPLANATION_PATTERN.matcher(lines.get(lineNo));
1102 final Matcher multipleViolationsMatcher =
1103 MULTIPLE_VIOLATIONS_PATTERN.matcher(lines.get(lineNo));
1104 final Matcher multipleViolationsAboveMatcher =
1105 MULTIPLE_VIOLATIONS_ABOVE_PATTERN.matcher(lines.get(lineNo));
1106 final Matcher multipleViolationsBelowMatcher =
1107 MULTIPLE_VIOLATIONS_BELOW_PATTERN.matcher(lines.get(lineNo));
1108 final Matcher violationSomeLinesAboveMatcher =
1109 VIOLATION_SOME_LINES_ABOVE_PATTERN.matcher(lines.get(lineNo));
1110 final Matcher violationSomeLinesBelowMatcher =
1111 VIOLATION_SOME_LINES_BELOW_PATTERN.matcher(lines.get(lineNo));
1112 final Matcher violationsAboveMatcherWithMessages =
1113 VIOLATIONS_ABOVE_PATTERN_WITH_MESSAGES.matcher(lines.get(lineNo));
1114 final Matcher violationsSomeLinesAboveMatcher =
1115 VIOLATIONS_SOME_LINES_ABOVE_PATTERN.matcher(lines.get(lineNo));
1116 final Matcher violationsSomeLinesBelowMatcher =
1117 VIOLATIONS_SOME_LINES_BELOW_PATTERN.matcher(lines.get(lineNo));
1118 final Matcher violationsDefault =
1119 VIOLATION_DEFAULT.matcher(lines.get(lineNo));
1120 if (violationMatcher.matches()) {
1121 final String violationMessage =
1122 extractMessage(violationMatcher.group(1), lines, lineNo);
1123 final int violationLineNum = lineNo + 1;
1124 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1125 violationLineNum);
1126 inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1127 }
1128 else if (violationAboveMatcher.matches()) {
1129 final String violationMessage =
1130 extractMessage(violationAboveMatcher.group(1), lines, lineNo);
1131 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, lineNo);
1132 inputConfigBuilder.addViolation(lineNo, violationMessage);
1133 }
1134 else if (violationBelowMatcher.matches()) {
1135 final String violationMessage =
1136 extractMessage(violationBelowMatcher.group(1), lines, lineNo);
1137 final int violationLineNum = lineNo + 2;
1138 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1139 violationLineNum);
1140 inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1141 }
1142 else if (violationAboveWithExplanationMatcher.matches()) {
1143 final String violationMessage =
1144 extractMessage(violationAboveWithExplanationMatcher.group(1), lines, lineNo);
1145 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, lineNo);
1146 inputConfigBuilder.addViolation(lineNo, violationMessage);
1147 }
1148 else if (violationBelowWithExplanationMatcher.matches()) {
1149 final String violationMessage =
1150 extractMessage(violationBelowWithExplanationMatcher.group(1), lines, lineNo);
1151 final int violationLineNum = lineNo + 2;
1152 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1153 violationLineNum);
1154 inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1155 }
1156 else if (violationWithExplanationMatcher.matches()) {
1157 final int violationLineNum = lineNo + 1;
1158 inputConfigBuilder.addViolation(violationLineNum, null);
1159 }
1160 else if (violationSomeLinesAboveMatcher.matches()) {
1161 final String violationMessage =
1162 extractMessage(violationSomeLinesAboveMatcher.group(2), lines, lineNo);
1163 final int linesAbove = Integer.parseInt(violationSomeLinesAboveMatcher.group(1)) - 1;
1164 final int violationLineNum = lineNo - linesAbove;
1165 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1166 violationLineNum);
1167 inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1168 }
1169 else if (violationSomeLinesBelowMatcher.matches()) {
1170 final String violationMessage =
1171 extractMessage(violationSomeLinesBelowMatcher.group(2), lines, lineNo);
1172 final int linesBelow = Integer.parseInt(violationSomeLinesBelowMatcher.group(1)) + 1;
1173 final int violationLineNum = lineNo + linesBelow;
1174 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1175 violationLineNum);
1176 inputConfigBuilder.addViolation(violationLineNum, violationMessage);
1177 }
1178 else if (violationsAboveMatcherWithMessages.matches()) {
1179 inputConfigBuilder.addViolations(
1180 getExpectedViolationsForSpecificLine(
1181 lines, lineNo, lineNo, violationsAboveMatcherWithMessages));
1182 }
1183 else if (violationsSomeLinesAboveMatcher.matches()) {
1184 inputConfigBuilder.addViolations(
1185 getExpectedViolations(
1186 lines, lineNo, violationsSomeLinesAboveMatcher, true));
1187 }
1188 else if (violationsSomeLinesBelowMatcher.matches()) {
1189 inputConfigBuilder.addViolations(
1190 getExpectedViolations(
1191 lines, lineNo, violationsSomeLinesBelowMatcher, false));
1192 }
1193 else if (multipleViolationsMatcher.matches()) {
1194 Collections
1195 .nCopies(Integer.parseInt(multipleViolationsMatcher.group(1)), lineNo + 1)
1196 .forEach(actualLineNumber -> {
1197 inputConfigBuilder.addViolation(actualLineNumber, null);
1198 });
1199 }
1200 else if (multipleViolationsAboveMatcher.matches()) {
1201 Collections
1202 .nCopies(Integer.parseInt(multipleViolationsAboveMatcher.group(1)), lineNo)
1203 .forEach(actualLineNumber -> {
1204 inputConfigBuilder.addViolation(actualLineNumber, null);
1205 });
1206 }
1207 else if (multipleViolationsBelowMatcher.matches()) {
1208 Collections
1209 .nCopies(Integer.parseInt(multipleViolationsBelowMatcher.group(1)),
1210 lineNo + 2)
1211 .forEach(actualLineNumber -> {
1212 inputConfigBuilder.addViolation(actualLineNumber, null);
1213 });
1214 }
1215 else if (useFilteredViolations) {
1216 setFilteredViolation(inputConfigBuilder, lineNo + 1,
1217 lines, lineNo, specifyViolationMessage);
1218 }
1219 else if (violationsDefault.matches()) {
1220 final int violationLineNum = lineNo + 1;
1221 inputConfigBuilder.addViolation(violationLineNum, null);
1222 }
1223 }
1224
1225 private static List<TestInputViolation> getExpectedViolationsForSpecificLine(
1226 List<String> lines, int lineNo, int violationLineNum,
1227 Matcher matcher) {
1228 final List<TestInputViolation> results = new ArrayList<>();
1229
1230 final int expectedMessageCount =
1231 Integer.parseInt(matcher.group(1));
1232 for (int index = 1; index <= expectedMessageCount; index++) {
1233 final String lineWithMessage = lines.get(lineNo + index);
1234 final Matcher messageMatcher = VIOLATION_MESSAGE_PATTERN.matcher(lineWithMessage);
1235 if (messageMatcher.find()) {
1236 final String violationMessage = messageMatcher.group(1);
1237 results.add(new TestInputViolation(violationLineNum, violationMessage));
1238 }
1239 }
1240 if (results.size() != expectedMessageCount) {
1241 final String message = String.format(Locale.ROOT,
1242 "Declared amount of violation messages at line %s is %s but found %s",
1243 lineNo + 1, expectedMessageCount, results.size());
1244 throw new IllegalStateException(message);
1245 }
1246 return results;
1247 }
1248
1249 private static List<TestInputViolation> getExpectedViolations(
1250 List<String> lines, int lineNo,
1251 Matcher matcher, boolean isAbove) {
1252 final int violationLine =
1253 Integer.parseInt(matcher.group(2));
1254 final int violationLineNum;
1255 if (isAbove) {
1256 violationLineNum = lineNo - violationLine + 1;
1257 }
1258 else {
1259 violationLineNum = lineNo + violationLine + 1;
1260 }
1261 return getExpectedViolationsForSpecificLine(lines,
1262 lineNo, violationLineNum, matcher);
1263 }
1264
1265 private static void setFilteredViolation(TestInputConfiguration.Builder inputConfigBuilder,
1266 int lineNo, List<String> lines,
1267 int currentLineNo,
1268 boolean specifyViolationMessage)
1269 throws CheckstyleException {
1270 final String line = lines.get(currentLineNo);
1271 final Matcher violationMatcher =
1272 FILTERED_VIOLATION_PATTERN.matcher(line);
1273 final Matcher violationAboveMatcher =
1274 FILTERED_VIOLATION_ABOVE_PATTERN.matcher(line);
1275 final Matcher violationBelowMatcher =
1276 FILTERED_VIOLATION_BELOW_PATTERN.matcher(line);
1277 final Matcher violationSomeLinesAboveMatcher =
1278 FILTERED_VIOLATION_SOME_LINES_ABOVE_PATTERN.matcher(line);
1279 final Matcher violationSomeLinesBelowMatcher =
1280 FILTERED_VIOLATION_SOME_LINES_BELOW_PATTERN.matcher(line);
1281 if (violationMatcher.matches()) {
1282 final String violationMessage =
1283 extractMessage(violationMatcher.group(1), lines, currentLineNo);
1284 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage, lineNo);
1285 inputConfigBuilder.addFilteredViolation(lineNo, violationMessage);
1286 }
1287 else if (violationAboveMatcher.matches()) {
1288 final String violationMessage =
1289 extractMessage(violationAboveMatcher.group(1), lines, currentLineNo);
1290 final int violationLineNum = lineNo - 1;
1291 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1292 violationLineNum);
1293 inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1294 }
1295 else if (violationBelowMatcher.matches()) {
1296 final String violationMessage =
1297 extractMessage(violationBelowMatcher.group(1), lines, currentLineNo);
1298 final int violationLineNum = lineNo + 1;
1299 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1300 violationLineNum);
1301 inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1302 }
1303 else if (violationSomeLinesAboveMatcher.matches()) {
1304 final String violationMessage =
1305 extractMessage(violationSomeLinesAboveMatcher.group(2), lines, currentLineNo);
1306 final int linesAbove = Integer.parseInt(violationSomeLinesAboveMatcher.group(1));
1307 final int violationLineNum = lineNo - linesAbove;
1308 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1309 violationLineNum);
1310 inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1311 }
1312 else if (violationSomeLinesBelowMatcher.matches()) {
1313 final String violationMessage =
1314 extractMessage(violationSomeLinesBelowMatcher.group(2), lines, currentLineNo);
1315 final int linesBelow = Integer.parseInt(violationSomeLinesBelowMatcher.group(1));
1316 final int violationLineNum = lineNo + linesBelow;
1317 checkWhetherViolationSpecified(specifyViolationMessage, violationMessage,
1318 violationLineNum);
1319 inputConfigBuilder.addFilteredViolation(violationLineNum, violationMessage);
1320 }
1321 }
1322
1323 private static boolean isViolationComment(String line) {
1324 return VIOLATION_PATTERN.matcher(line).matches()
1325 || VIOLATION_ABOVE_PATTERN.matcher(line).matches()
1326 || VIOLATION_BELOW_PATTERN.matcher(line).matches()
1327 || VIOLATION_SOME_LINES_ABOVE_PATTERN.matcher(line).matches()
1328 || VIOLATION_SOME_LINES_BELOW_PATTERN.matcher(line).matches()
1329 || FILTERED_VIOLATION_PATTERN.matcher(line).matches();
1330 }
1331
1332 private static String assembleTripleQuoteMessage(String firstPart,
1333 List<String> lines, int startLineNo) {
1334 final String result;
1335 if (firstPart.endsWith("\"\"")) {
1336 result = firstPart.substring(0, firstPart.length() - 2);
1337 }
1338 else {
1339 final StringBuilder builder = new StringBuilder(firstPart.strip());
1340 int nextLine = startLineNo + 1;
1341 boolean done = false;
1342 while (!done && nextLine < lines.size()) {
1343 final String candidate = lines.get(nextLine);
1344 final Matcher continuation =
1345 MULTILINE_CONTINUATION_PATTERN.matcher(candidate);
1346
1347 final boolean isViolation = isViolationComment(candidate);
1348 final boolean isContinuation = continuation.matches();
1349
1350 if (!isViolation && isContinuation) {
1351 final String part = continuation.group(1);
1352 if (part.contains(TRIPLE_QUOTE)) {
1353 final int idx = part.indexOf(TRIPLE_QUOTE);
1354 final String lastPart = part.substring(0, idx).trim();
1355 if (!lastPart.isEmpty()) {
1356 builder.append(' ').append(lastPart);
1357 }
1358 done = true;
1359 }
1360 else {
1361 builder.append(' ').append(part.strip());
1362 nextLine++;
1363 }
1364 }
1365 else {
1366 done = true;
1367 }
1368 }
1369 result = builder.toString();
1370 }
1371 return result.replaceAll("\\s+", " ").trim();
1372 }
1373
1374 private static String extractMessage(String rawMessage,
1375 List<String> lines, int lineNo) {
1376 String result = null;
1377
1378 if (rawMessage != null) {
1379 if (rawMessage.startsWith("\"\"")) {
1380 result = assembleTripleQuoteMessage(
1381 rawMessage.substring(2), lines, lineNo);
1382 }
1383 else {
1384
1385 if (rawMessage.endsWith("'") || rawMessage.endsWith("\"")) {
1386 result = rawMessage.substring(0, rawMessage.length() - 1);
1387 }
1388 else {
1389 result = rawMessage;
1390 }
1391 }
1392 }
1393
1394 return result;
1395 }
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405 private static void checkWhetherViolationSpecified(boolean shouldViolationMsgBeSpecified,
1406 String violationMessage, int lineNum) throws CheckstyleException {
1407 if (shouldViolationMsgBeSpecified && violationMessage == null) {
1408 throw new CheckstyleException(
1409 "Violation message should be specified on line " + lineNum);
1410 }
1411 }
1412
1413 private static Map<Object, Object> loadProperties(String propertyContent) throws IOException {
1414 final Properties properties = new Properties();
1415 properties.load(new StringReader(propertyContent));
1416 return properties;
1417 }
1418
1419 private static boolean isNumericType(Class<?> fieldType) {
1420 return Number.class.isAssignableFrom(fieldType)
1421 || fieldType.equals(int.class)
1422 || fieldType.equals(double.class)
1423 || fieldType.equals(long.class)
1424 || fieldType.equals(float.class);
1425 }
1426
1427 public static Object getPropertyDefaultValue(Object checkInstance,
1428 String propertyName) {
1429 Object retVal;
1430 try {
1431 retVal = TestUtil.getInternalState(checkInstance, propertyName, Object.class);
1432 }
1433 catch (IllegalStateException exc) {
1434 retVal = null;
1435 }
1436 return retVal;
1437 }
1438
1439 private static boolean isNull(String propertyDefaultValue) {
1440 return NULL_STRING.equals(propertyDefaultValue)
1441 || propertyDefaultValue.isEmpty()
1442 || "null".equals(propertyDefaultValue)
1443 || "\"\"".equals(propertyDefaultValue);
1444 }
1445
1446 }