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