• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 196

16 Oct 2025 08:33AM UTC coverage: 78.642% (-0.02%) from 78.661%
196

push

github

web-flow
chore: fix dogfood issues from new rules (#6056)

18180 of 23973 branches covered (75.84%)

Branch coverage included in aggregate %.

2 of 27 new or added lines in 14 files covered. (7.41%)

2 existing lines in 1 file now uncovered.

39693 of 49617 relevant lines covered (80.0%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/rule/AbstractRuleSetFactoryTest.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.test.lang.rule;
6

7
import static org.hamcrest.MatcherAssert.assertThat;
8
import static org.hamcrest.Matchers.emptyString;
9
import static org.junit.jupiter.api.Assertions.assertEquals;
10
import static org.junit.jupiter.api.Assertions.assertFalse;
11
import static org.junit.jupiter.api.Assertions.assertTrue;
12
import static org.junit.jupiter.api.Assertions.fail;
13
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
14

15
import java.io.BufferedReader;
16
import java.io.ByteArrayInputStream;
17
import java.io.ByteArrayOutputStream;
18
import java.io.FileNotFoundException;
19
import java.io.IOException;
20
import java.io.InputStream;
21
import java.io.InputStreamReader;
22
import java.nio.charset.StandardCharsets;
23
import java.util.ArrayList;
24
import java.util.Arrays;
25
import java.util.Collections;
26
import java.util.HashMap;
27
import java.util.HashSet;
28
import java.util.List;
29
import java.util.Locale;
30
import java.util.Map;
31
import java.util.Properties;
32
import java.util.Set;
33
import java.util.StringTokenizer;
34
import java.util.regex.Pattern;
35
import javax.xml.parsers.SAXParser;
36
import javax.xml.parsers.SAXParserFactory;
37

38
import org.junit.jupiter.api.BeforeAll;
39
import org.junit.jupiter.api.Test;
40
import org.junit.jupiter.api.TestInstance;
41
import org.junit.jupiter.params.ParameterizedTest;
42
import org.junit.jupiter.params.provider.MethodSource;
43
import org.slf4j.event.Level;
44
import org.xml.sax.InputSource;
45
import org.xml.sax.SAXException;
46
import org.xml.sax.SAXParseException;
47
import org.xml.sax.helpers.DefaultHandler;
48

49
import net.sourceforge.pmd.internal.util.IOUtil;
50
import net.sourceforge.pmd.lang.Language;
51
import net.sourceforge.pmd.lang.LanguageRegistry;
52
import net.sourceforge.pmd.lang.rule.InternalApiBridge;
53
import net.sourceforge.pmd.lang.rule.Rule;
54
import net.sourceforge.pmd.lang.rule.RuleReference;
55
import net.sourceforge.pmd.lang.rule.RuleSet;
56
import net.sourceforge.pmd.lang.rule.RuleSetLoader;
57
import net.sourceforge.pmd.lang.rule.RuleSetWriter;
58
import net.sourceforge.pmd.lang.rule.impl.UnnecessaryPmdSuppressionRule;
59
import net.sourceforge.pmd.lang.rule.xpath.XPathRule;
60
import net.sourceforge.pmd.properties.PropertyDescriptor;
61
import net.sourceforge.pmd.util.log.internal.MessageReporterBase;
62

63
/**
64
 * Base test class to verify the language's rulesets. This class should be
65
 * subclassed for each language.
66
 */
67
@TestInstance(PER_CLASS)
68
public abstract class AbstractRuleSetFactoryTest {
69

70
    private static ValidateDefaultHandler validateDefaultHandler;
71
    private static SAXParser saxParser;
72

73
    // todo rename this field to validCoreRules or something. Make private.
74
    protected Set<String> validXPathClassNames = new HashSet<>();
×
75
    private final Set<String> languagesToSkip = new HashSet<>();
×
76

77
    public AbstractRuleSetFactoryTest() {
78
        this(new String[0]);
×
79
    }
×
80

81
    /**
82
     * Constructor used when a module that depends on another module wants to filter out the dependee's rulesets.
83
     *
84
     * @param languagesToSkip {@link Language}s terse names that appear in the classpath via a dependency, but should be
85
     * skipped because they aren't the primary language which the concrete instance of this class is testing.
86
     */
87
    public AbstractRuleSetFactoryTest(String... languagesToSkip) {
×
88
        this.languagesToSkip.add("dummy");
×
89
        this.languagesToSkip.addAll(Arrays.asList(languagesToSkip));
×
90
        validXPathClassNames.add(XPathRule.class.getName());
×
91
        validXPathClassNames.add(UnnecessaryPmdSuppressionRule.class.getName());
×
92
    }
×
93

94
    public AbstractRuleSetFactoryTest(Language... languagesToSkip) {
95
        this(Arrays.stream(languagesToSkip).map(Language::getId).toArray(String[]::new));
×
96
    }
×
97

98

99
    /**
100
     * Setups the XML parser with validation.
101
     *
102
     * @throws Exception
103
     *             any error
104
     */
105
    @BeforeAll
106
    static void init() throws Exception {
107
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
×
108
        saxParserFactory.setValidating(true);
×
109
        saxParserFactory.setNamespaceAware(true);
×
110

111
        // Hope we're using Xerces, or this may not work!
112
        // Note: Features are listed here
113
        // http://xerces.apache.org/xerces2-j/features.html
114
        saxParserFactory.setFeature("http://xml.org/sax/features/validation", true);
×
115
        saxParserFactory.setFeature("http://apache.org/xml/features/validation/schema", true);
×
116
        saxParserFactory.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);
×
117

118
        validateDefaultHandler = new ValidateDefaultHandler();
×
119

120
        saxParser = saxParserFactory.newSAXParser();
×
121
    }
×
122

123
    /**
124
     * Checks all rulesets of all languages on the classpath and verifies that
125
     * all required attributes for all rules are specified.
126
     *
127
     * @throws Exception
128
     *             any error
129
     */
130
    @ParameterizedTest
131
    @MethodSource("getRuleSetFileNames")
132
    void testAllPMDBuiltInRulesMeetConventions(String fileName) throws Exception {
133
        int invalidSinceAttributes = 0;
×
134
        int invalidExternalInfoURL = 0;
×
135
        int invalidClassName = 0;
×
136
        int invalidRegexSuppress = 0;
×
137
        int invalidXPathSuppress = 0;
×
138
        int invalidOrder = 0;
×
139
        StringBuilder messages = new StringBuilder();
×
140
        String lastName = null;
×
141
        RuleSet ruleSet = loadRuleSetByFileName(fileName);
×
142
        for (Rule rule : ruleSet.getRules()) {
×
143

144
            // Skip references
145
            if (rule instanceof RuleReference) {
×
146
                continue;
×
147
            }
148
            if (lastName != null
×
149
                    && String.CASE_INSENSITIVE_ORDER.compare(rule.getName(), lastName) < 0) {
×
150
                invalidOrder++;
×
151
                messages.append(rule.getName()).append(" should be before ")
×
152
                    .append(lastName).append("\n");
×
153
            }
154
            lastName = rule.getName();
×
155

156
            Language language = rule.getLanguage();
×
157
            String group = fileName.substring(fileName.lastIndexOf('/') + 1);
×
158
            group = group.substring(0, group.indexOf(".xml"));
×
159
            if (group.indexOf('-') >= 0) {
×
160
                group = group.substring(0, group.indexOf('-'));
×
161
            }
162

163
            // Is since missing ?
164
            if (rule.getSince() == null) {
×
165
                invalidSinceAttributes++;
×
166
                messages.append("Rule ")
×
167
                        .append(fileName)
×
168
                        .append("/")
×
169
                        .append(rule.getName())
×
170
                        .append(" is missing 'since' attribute\n");
×
171
            }
172
            // Is URL valid ?
173
            if (rule.getExternalInfoUrl() == null || "".equalsIgnoreCase(rule.getExternalInfoUrl())) {
×
174
                invalidExternalInfoURL++;
×
175
                messages.append("Rule ")
×
176
                        .append(fileName)
×
177
                        .append("/")
×
178
                        .append(rule.getName())
×
179
                        .append(" is missing 'externalInfoURL' attribute\n");
×
180
            } else {
181
                String expectedExternalInfoURL = "https://docs.pmd-code.org/.+/pmd_rules_"
×
182
                        + language.getId() + "_"
×
183
                        + IOUtil.getFilenameBase(fileName)
×
184
                        + ".html#"
185
                        + rule.getName().toLowerCase(Locale.ROOT);
×
186
                if (rule.getExternalInfoUrl() == null
×
187
                        || !rule.getExternalInfoUrl().matches(expectedExternalInfoURL)) {
×
188
                    invalidExternalInfoURL++;
×
189
                    messages.append("Rule ")
×
190
                            .append(fileName)
×
191
                            .append("/")
×
192
                            .append(rule.getName())
×
193
                            .append(" seems to have an invalid 'externalInfoURL' value (")
×
194
                            .append(rule.getExternalInfoUrl())
×
195
                            .append("), it should be:")
×
196
                            .append(expectedExternalInfoURL)
×
197
                            .append('\n');
×
198
                }
199
            }
200
            // Proper class name/packaging?
201
            String expectedClassName = "net.sourceforge.pmd.lang." + language.getId() + ".rule." + group
×
202
                    + "." + rule.getName() + "Rule";
×
203
            if (!rule.getRuleClass().equals(expectedClassName)
×
204
                    && !validXPathClassNames.contains(rule.getRuleClass())) {
×
205
                invalidClassName++;
×
206
                messages.append("Rule ")
×
207
                        .append(fileName)
×
208
                        .append("/")
×
209
                        .append(rule.getName())
×
210
                        .append(" seems to have an invalid 'class' value (")
×
211
                        .append(rule.getRuleClass())
×
212
                        .append("), it should be:")
×
213
                        .append(expectedClassName)
×
214
                        .append('\n');
×
215
            }
216
            // Should not have violation suppress regex property
217
            if (rule.getProperty(Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR).isPresent()) {
×
218
                invalidRegexSuppress++;
×
219
                messages.append("Rule ")
×
220
                        .append(fileName)
×
221
                        .append("/")
×
222
                        .append(rule.getName())
×
223
                        .append(" should not have '")
×
224
                        .append(Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR.name())
×
225
                        .append("', this is intended for end user customization only.\n");
×
226
            }
227
            // Should not have violation suppress xpath property
228
            if (rule.getProperty(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR).isPresent()) {
×
229
                invalidXPathSuppress++;
×
230
                messages.append("Rule ").append(fileName).append("/").append(rule.getName()).append(" should not have '").append(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR.name()).append("', this is intended for end user customization only.").append(System.lineSeparator());
×
231
            }
232
        }
×
233
        // We do this at the end to ensure we test ALL the rules before failing
234
        // the test
235
        if (invalidSinceAttributes > 0 || invalidExternalInfoURL > 0 || invalidClassName > 0 || invalidRegexSuppress > 0
×
236
                || invalidXPathSuppress > 0 || invalidOrder > 0) {
237
            fail("All built-in PMD rules need 'since' attribute (" + invalidSinceAttributes
×
238
                    + " are missing), a proper ExternalURLInfo (" + invalidExternalInfoURL
239
                    + " are invalid), a class name meeting conventions (" + invalidClassName + " are invalid), no '"
240
                    + Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR.name() + "' property (" + invalidRegexSuppress
×
241
                    + " are invalid), and no '" + Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR.name() + "' property ("
×
242
                    + invalidXPathSuppress + " are invalid) and be alphabetically sorted ("
243
                    + invalidOrder + " misplaced)\n" + messages);
244
        }
245
    }
×
246

247
    /**
248
     * Verifies that all rulesets are valid XML according to the xsd schema.
249
     *
250
     * @throws Exception
251
     *             any error
252
     */
253
    @Test
254
    void testXmlSchema() throws Exception {
255
        boolean allValid = true;
×
256
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
257
        for (String fileName : ruleSetFileNames) {
×
258
            boolean valid = validateAgainstSchema(fileName);
×
259
            allValid = allValid && valid;
×
260
        }
×
261
        assertTrue(allValid, "All XML must parse without producing validation messages.");
×
262
    }
×
263

264
    /**
265
     * @deprecated Since 7.17.0. This method will be removed. PMD has the rule "MissingEncoding" for XML files that
266
     *             is used instead.
267
     */
268
    @Deprecated
269
    public static boolean hasCorrectEncoding(String fileName) throws IOException {
270
        try (InputStream inputStream = loadResourceAsStream(fileName)) {
×
271
            // first bytes must be:
272
            byte[] expectedBytes = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>".getBytes(StandardCharsets.UTF_8);
×
273
            byte[] bytes = new byte[expectedBytes.length];
×
274
            int count = inputStream.read(bytes);
×
275
            if (count != expectedBytes.length || !Arrays.equals(expectedBytes, bytes)) {
×
276
                return false;
×
277
            }
278
        }
×
279
        return true;
×
280
    }
281

282
    /**
283
     * Verifies that all rulesets are valid XML according to the DTD.
284
     *
285
     * @throws Exception
286
     *             any error
287
     */
288
    @Test
289
    void testDtd() throws Exception {
290
        boolean allValid = true;
×
291
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
292
        for (String fileName : ruleSetFileNames) {
×
293
            boolean valid = validateAgainstDtd(fileName);
×
294
            allValid = allValid && valid;
×
295
        }
×
296
        assertTrue(allValid, "All XML must parse without producing validation messages.");
×
297
    }
×
298

299
    /**
300
     * Reads and writes the rulesets to make sure, that no data is lost if the
301
     * rulests are processed.
302
     *
303
     * @throws Exception
304
     *             any error
305
     */
306
    @Test
307
    void testReadWriteRoundTrip() throws Exception {
308

309
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
310
        for (String fileName : ruleSetFileNames) {
×
311
            testRuleSet(fileName);
×
312
        }
×
313

314
    }
×
315

316
    // Gets all test PMD Ruleset XML files
317
    private List<String> getRuleSetFileNames() throws IOException {
318
        List<String> result = new ArrayList<>();
×
319

320
        for (Language language : LanguageRegistry.PMD.getLanguages()) {
×
321
            if (this.languagesToSkip.contains(language.getId())) {
×
322
                continue;
×
323
            }
324
            result.addAll(getRuleSetFileNames(language.getId()));
×
325
        }
×
326

327
        return result;
×
328
    }
329

330
    private List<String> getRuleSetFileNames(String language) throws IOException {
331
        List<String> ruleSetFileNames = new ArrayList<>();
×
332
        ruleSetFileNames.addAll(getRuleSetFileNames(language, "rulesets/" + language + "/rulesets.properties"));
×
333
        ruleSetFileNames.addAll(getRuleSetFileNames(language, "category/" + language + "/categories.properties"));
×
334
        return ruleSetFileNames;
×
335
    }
336

337
    private List<String> getRuleSetFileNames(String language, String propertiesPath) throws IOException {
338
        List<String> ruleSetFileNames = new ArrayList<>();
×
339
        Properties properties = new Properties();
×
340
        @SuppressWarnings("PMD.CloseResource")
341
        InputStream input = loadResourceAsStream(propertiesPath);
×
342
        if (input == null) {
×
343
            // this might happen if a language is only support by CPD, but not
344
            // by PMD
345
            System.err.println("No rulesets found for language " + language + " at " + propertiesPath);
×
346
            return Collections.emptyList();
×
347
        }
348
        try (InputStream is = input) {
×
349
            properties.load(is);
×
350
        }
351
        String fileNames = properties.getProperty("rulesets.filenames");
×
352
        StringTokenizer st = new StringTokenizer(fileNames, ",");
×
353
        while (st.hasMoreTokens()) {
×
354
            ruleSetFileNames.add(st.nextToken());
×
355
        }
356
        return ruleSetFileNames;
×
357
    }
358

359
    private RuleSet loadRuleSetByFileName(String ruleSetFileName) {
360
        final StringBuilder messages = new StringBuilder();
×
361
        class Reporter extends MessageReporterBase {
×
362
            @Override
363
            protected void logImpl(Level level, String message) {
364
                messages.append(message).append(System.lineSeparator());
×
365
            }
×
366
        }
367

368
        RuleSet ruleSet = InternalApiBridge.withReporter(new RuleSetLoader(), new Reporter())
×
369
                .loadFromResource(ruleSetFileName);
×
370

371
        assertThat("There should be no warnings while loading the ruleset",
×
372
                messages.toString(), emptyString());
×
373

374
        return ruleSet;
×
375
    }
376

377
    private boolean validateAgainstSchema(String fileName) throws IOException, SAXException {
378
        try (InputStream inputStream = loadResourceAsStream(fileName)) {
×
379
            boolean valid = validateAgainstSchema(inputStream);
×
380
            if (!valid) {
×
381
                System.err.println("Validation against XML Schema failed for: " + fileName);
×
382
            }
383
            return valid;
×
384
        }
385
    }
386

387
    private boolean validateAgainstSchema(InputStream inputStream) throws IOException, SAXException {
388

389
        saxParser.parse(inputStream, validateDefaultHandler.resetValid());
×
390
        inputStream.close();
×
391
        return validateDefaultHandler.isValid();
×
392
    }
393

394
    private boolean validateAgainstDtd(String fileName) throws IOException, SAXException {
395
        try (InputStream inputStream = loadResourceAsStream(fileName)) {
×
396
            boolean valid = validateAgainstDtd(inputStream);
×
397
            if (!valid) {
×
398
                System.err.println("Validation against DTD failed for: " + fileName);
×
399
            }
400
            return valid;
×
401
        }
402
    }
403

404
    private boolean validateAgainstDtd(InputStream inputStream) throws IOException, SAXException {
405

406
        // Read file into memory
407
        String file = readFullyToString(inputStream);
×
408
        inputStream.close();
×
409

410
        String rulesetNamespace = RuleSetWriter.RULESET_2_0_0_NS_URI;
×
411

412
        // Remove XML Schema stuff, replace with DTD
413
        file = file.replaceAll("<\\?xml [ a-zA-Z0-9=\".-]*\\?>", "");
×
414
        file = file.replaceAll("xmlns=\"" + rulesetNamespace + "\"", "");
×
415
        file = file.replaceAll("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", "");
×
416
        file = file.replaceAll("xsi:schemaLocation=\"" + rulesetNamespace
×
417
                + " https://pmd.sourceforge.io/ruleset_\\d_0_0.xsd\"", "");
418

419
        if (RuleSetWriter.RULESET_2_0_0_NS_URI.equals(rulesetNamespace)) {
×
420
            file = "<?xml version=\"1.0\"?>" + System.lineSeparator()
×
421
                + "<!DOCTYPE ruleset SYSTEM \"https://pmd.sourceforge.io/ruleset_2_0_0.dtd\">" + System.lineSeparator()
×
422
                + file;
423
        } else {
424
            file = "<?xml version=\"1.0\"?>" + System.lineSeparator()
×
425
                + "<!DOCTYPE ruleset>" + System.lineSeparator()
×
426
                + file;
427
        }
428

NEW
429
        try (InputStream modifiedStream = new ByteArrayInputStream(file.getBytes(StandardCharsets.UTF_8))) {
×
430
            saxParser.parse(modifiedStream, validateDefaultHandler.resetValid());
×
431
        }
432
        return validateDefaultHandler.isValid();
×
433
    }
434

435
    private String readFullyToString(InputStream inputStream) throws IOException {
436
        StringBuilder buf = new StringBuilder(64 * 1024);
×
NEW
437
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
×
438
            String line;
439
            while ((line = reader.readLine()) != null) {
×
440
                buf.append(line);
×
441
                buf.append(System.lineSeparator());
×
442
            }
443
            return buf.toString();
×
444
        }
445
    }
446

447
    private static InputStream loadResourceAsStream(String resource) {
448
        return AbstractRuleSetFactoryTest.class.getClassLoader().getResourceAsStream(resource);
×
449
    }
450

451
    private void testRuleSet(String fileName) throws IOException, SAXException {
452

453
        // Load original XML
454
        // String xml1 =
455
        // readFullyToString(ResourceLoader.loadResourceAsStream(fileName));
456
        // System.out.println("xml1: " + xml1);
457

458
        // Load the original RuleSet
459
        RuleSet ruleSet1 = loadRuleSetByFileName(fileName);
×
460

461
        // Write to XML, first time
462
        ByteArrayOutputStream outputStream1 = new ByteArrayOutputStream();
×
463
        RuleSetWriter writer1 = new RuleSetWriter(outputStream1);
×
464
        writer1.write(ruleSet1);
×
465
        writer1.close();
×
NEW
466
        String xml2 = new String(outputStream1.toByteArray(), StandardCharsets.UTF_8);
×
467
        // System.out.println("xml2: " + xml2);
468

469
        // Read RuleSet from XML, first time
470
        RuleSetLoader loader = new RuleSetLoader();
×
471
        RuleSet ruleSet2 = loader.loadFromString("readRuleSet1.xml", xml2);
×
472

473
        // Do write/read a 2nd time, just to be sure
474

475
        // Write to XML, second time
476
        ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
×
477
        RuleSetWriter writer2 = new RuleSetWriter(outputStream2);
×
478
        writer2.write(ruleSet2);
×
479
        writer2.close();
×
NEW
480
        String xml3 = new String(outputStream2.toByteArray(), StandardCharsets.UTF_8);
×
481
        // System.out.println("xml3: " + xml3);
482

483
        // Read RuleSet from XML, second time
484
        RuleSet ruleSet3 = loader.loadFromString("readRuleSet2.xml", xml3);
×
485

486
        // The 2 written XMLs should all be valid w.r.t Schema/DTD
NEW
487
        assertTrue(validateAgainstSchema(new ByteArrayInputStream(xml2.getBytes(StandardCharsets.UTF_8))),
×
488
                "1st roundtrip RuleSet XML is not valid against Schema (filename: " + fileName + ")");
NEW
489
        assertTrue(validateAgainstSchema(new ByteArrayInputStream(xml3.getBytes(StandardCharsets.UTF_8))),
×
490
                "2nd roundtrip RuleSet XML is not valid against Schema (filename: " + fileName + ")");
NEW
491
        assertTrue(validateAgainstDtd(new ByteArrayInputStream(xml2.getBytes(StandardCharsets.UTF_8))),
×
492
                "1st roundtrip RuleSet XML is not valid against DTD (filename: " + fileName + ")");
NEW
493
        assertTrue(validateAgainstDtd(new ByteArrayInputStream(xml3.getBytes(StandardCharsets.UTF_8))),
×
494
                "2nd roundtrip RuleSet XML is not valid against DTD (filename: " + fileName + ")");
495

496
        // All 3 versions of the RuleSet should be the same
497
        assertEqualsRuleSet("Original RuleSet and 1st roundtrip Ruleset not the same (filename: " + fileName + ")",
×
498
                ruleSet1, ruleSet2);
499
        assertEqualsRuleSet("1st roundtrip Ruleset and 2nd roundtrip RuleSet not the same (filename: " + fileName + ")",
×
500
                ruleSet2, ruleSet3);
501

502
        // It's hard to compare the XML DOMs. At least the roundtrip ones should
503
        // textually be the same.
504
        assertEquals(xml2, xml3,
×
505
                "1st roundtrip RuleSet XML and 2nd roundtrip RuleSet XML (filename: " + fileName + ")");
506
    }
×
507

508
    private void assertEqualsRuleSet(String message, RuleSet ruleSet1, RuleSet ruleSet2) {
509
        assertEquals(ruleSet1.getName(), ruleSet2.getName(), message + ", RuleSet name");
×
510
        assertEquals(ruleSet1.getDescription(), ruleSet2.getDescription(), message + ", RuleSet description");
×
511
        assertEquals(ruleSet1.getFileExclusions(), ruleSet2.getFileExclusions(),
×
512
                message + ", RuleSet exclude patterns");
513
        assertEquals(ruleSet1.getFileInclusions(), ruleSet2.getFileInclusions(),
×
514
                message + ", RuleSet include patterns");
515
        assertEquals(ruleSet1.getRules().size(), ruleSet2.getRules().size(), message + ", RuleSet rule count");
×
516

517
        for (int i = 0; i < ruleSet1.getRules().size(); i++) {
×
518
            Rule rule1 = ((List<Rule>) ruleSet1.getRules()).get(i);
×
519
            Rule rule2 = ((List<Rule>) ruleSet2.getRules()).get(i);
×
520

521
            assertFalse(rule1 instanceof RuleReference != rule2 instanceof RuleReference,
×
522
                    message + ", Different RuleReference");
523

524
            if (rule1 instanceof RuleReference) {
×
525
                RuleReference ruleReference1 = (RuleReference) rule1;
×
526
                RuleReference ruleReference2 = (RuleReference) rule2;
×
527
                assertEquals(ruleReference1.getOverriddenMinimumLanguageVersion(),
×
528
                        ruleReference2.getOverriddenMinimumLanguageVersion(),
×
529
                        message + ", RuleReference overridden minimum language version");
530
                assertEquals(ruleReference1.getOverriddenMaximumLanguageVersion(),
×
531
                        ruleReference2.getOverriddenMaximumLanguageVersion(),
×
532
                        message + ", RuleReference overridden maximum language version");
533
                assertEquals(ruleReference1.isOverriddenDeprecated(), ruleReference2.isOverriddenDeprecated(),
×
534
                        message + ", RuleReference overridden deprecated");
535
                assertEquals(ruleReference1.getOverriddenName(), ruleReference2.getOverriddenName(),
×
536
                        message + ", RuleReference overridden name");
537
                assertEquals(ruleReference1.getOverriddenDescription(), ruleReference2.getOverriddenDescription(),
×
538
                        message + ", RuleReference overridden description");
539
                assertEquals(ruleReference1.getOverriddenMessage(), ruleReference2.getOverriddenMessage(),
×
540
                        message + ", RuleReference overridden message");
541
                assertEquals(ruleReference1.getOverriddenExternalInfoUrl(), ruleReference2.getOverriddenExternalInfoUrl(),
×
542
                        message + ", RuleReference overridden external info url");
543
                assertEquals(ruleReference1.getOverriddenPriority(), ruleReference2.getOverriddenPriority(),
×
544
                        message + ", RuleReference overridden priority");
545
                assertEquals(ruleReference1.getOverriddenExamples(), ruleReference2.getOverriddenExamples(),
×
546
                        message + ", RuleReference overridden examples");
547
            }
548

549
            assertEquals(rule1.getName(), rule2.getName(), message + ", Rule name");
×
550
            assertEquals(rule1.getRuleClass(), rule2.getRuleClass(), message + ", Rule class");
×
551
            assertEquals(rule1.getDescription(), rule2.getDescription(),
×
552
                    message + ", Rule description " + rule1.getName());
×
553
            assertEquals(rule1.getMessage(), rule2.getMessage(), message + ", Rule message");
×
554
            assertEquals(rule1.getExternalInfoUrl(), rule2.getExternalInfoUrl(), message + ", Rule external info url");
×
555
            assertEquals(rule1.getPriority(), rule2.getPriority(), message + ", Rule priority");
×
556
            assertEquals(rule1.getExamples(), rule2.getExamples(), message + ", Rule examples");
×
557

558
            List<PropertyDescriptor<?>> propertyDescriptors1 = rule1.getPropertyDescriptors();
×
559
            List<PropertyDescriptor<?>> propertyDescriptors2 = rule2.getPropertyDescriptors();
×
560
            assertEquals(propertyDescriptors1, propertyDescriptors2, message + ", Rule property descriptor ");
×
561
            for (int j = 0; j < propertyDescriptors1.size(); j++) {
×
562
                Object value1 = rule1.getProperty(propertyDescriptors1.get(j));
×
563
                Object value2 = rule2.getProperty(propertyDescriptors2.get(j));
×
564
                // special case for Pattern, there is no equals method
565
                if (value1 instanceof Pattern && value2 instanceof Pattern) {
×
566
                    value1 = ((Pattern) value1).pattern();
×
567
                    value2 = ((Pattern) value2).pattern();
×
568
                }
569
                assertEquals(value1, value2, message + ", Rule " + rule1.getName() + " property "
×
570
                    + propertyDescriptors1.get(j).name());
×
571
            }
572
            assertEquals(propertyDescriptors1.size(), propertyDescriptors2.size(),
×
573
                    message + ", Rule property descriptor count");
574
        }
575
    }
×
576

577
    /**
578
     * Validator for the SAX parser
579
     */
580
    private static class ValidateDefaultHandler extends DefaultHandler {
581
        private boolean valid = true;
×
582
        private final Map<String, String> schemaMapping;
583

584
        ValidateDefaultHandler() {
×
585
            schemaMapping = new HashMap<>();
×
586
            schemaMapping.put("https://pmd.sourceforge.io/ruleset_2_0_0.xsd", "ruleset_2_0_0.xsd");
×
587
            schemaMapping.put("https://pmd.sourceforge.io/ruleset_2_0_0.dtd", "ruleset_2_0_0.dtd");
×
588
        }
×
589

590
        public ValidateDefaultHandler resetValid() {
591
            valid = true;
×
592
            return this;
×
593
        }
594

595
        public boolean isValid() {
596
            return valid;
×
597
        }
598

599
        @Override
600
        public void error(SAXParseException e) {
601
            log("Error", e);
×
602
        }
×
603

604
        @Override
605
        public void fatalError(SAXParseException e) {
606
            log("FatalError", e);
×
607
        }
×
608

609
        @Override
610
        public void warning(SAXParseException e) {
611
            log("Warning", e);
×
612
        }
×
613

614
        private void log(String prefix, SAXParseException e) {
615
            String message = prefix + " at (" + e.getLineNumber() + ", " + e.getColumnNumber() + "): " + e.getMessage();
×
616
            System.err.println(message);
×
617
            valid = false;
×
618
        }
×
619

620
        @Override
621
        public InputSource resolveEntity(String publicId, String systemId) throws IOException {
622
            String resource = schemaMapping.get(systemId);
×
623

624
            if (resource != null) {
×
625
                InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resource);
×
626
                if (inputStream == null) {
×
627
                    throw new FileNotFoundException(resource);
×
628
                }
629
                return new InputSource(inputStream);
×
630
            }
631
            throw new IllegalArgumentException(
×
632
                    "No clue how to handle: publicId=" + publicId + ", systemId=" + systemId);
633
        }
634
    }
635

636
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc