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

pmd / pmd / 4553

25 Apr 2025 06:55AM UTC coverage: 77.84% (+0.008%) from 77.832%
4553

push

github

adangel
[core] Support language dialects (#5438)

Merge pull request #5438 from Monits:lang-dialects

17661 of 23654 branches covered (74.66%)

Branch coverage included in aggregate %.

113 of 137 new or added lines in 12 files covered. (82.48%)

20 existing lines in 5 files now uncovered.

38710 of 48765 relevant lines covered (79.38%)

0.8 hits per line

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

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

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

7
import java.util.ArrayList;
8
import java.util.Arrays;
9
import java.util.Collection;
10
import java.util.Collections;
11
import java.util.Iterator;
12
import java.util.LinkedHashSet;
13
import java.util.List;
14
import java.util.Objects;
15
import java.util.Set;
16
import java.util.function.Predicate;
17
import java.util.regex.Pattern;
18

19
import org.apache.commons.lang3.StringUtils;
20
import org.slf4j.Logger;
21
import org.slf4j.LoggerFactory;
22

23
import net.sourceforge.pmd.cache.internal.ChecksumAware;
24
import net.sourceforge.pmd.internal.util.PredicateUtil;
25
import net.sourceforge.pmd.lang.LanguageVersion;
26
import net.sourceforge.pmd.lang.document.FileId;
27
import net.sourceforge.pmd.lang.rule.internal.RuleSetReference;
28
import net.sourceforge.pmd.lang.rule.xpath.XPathRule;
29

30
/**
31
 * This class represents a collection of rules along with some optional filter
32
 * patterns that can preclude their application on specific files.
33
 *
34
 * @see Rule
35
 */
36
public class RuleSet implements ChecksumAware {
37

38
    private static final Logger LOG = LoggerFactory.getLogger(RuleSet.class);
1✔
39
    private static final String MISSING_RULE = "Missing rule";
40
    private static final String MISSING_RULESET_DESCRIPTION = "RuleSet description must not be null";
41
    private static final String MISSING_RULESET_NAME = "RuleSet name must not be null";
42

43
    private final long checksum;
44

45
    private final List<Rule> rules;
46
    private final String fileName;
47
    private final String name;
48
    private final String description;
49

50
    /*
51
     * Order is unimportant, but we preserve the order given by the user to be deterministic.
52
     * Using Sets is useless, since Pattern does not override #equals anyway.
53
     */
54
    private final List<Pattern> excludePatterns;
55
    private final List<Pattern> includePatterns;
56

57
    private final Predicate<String> filter;
58

59
    /**
60
     * Creates a new RuleSet with the given checksum.
61
     *
62
     * @param builder
63
     *            A rule set builder.
64
     */
65
    private RuleSet(final RuleSetBuilder builder) {
1✔
66
        checksum = builder.checksum;
1✔
67
        fileName = builder.fileName;
1✔
68
        name = Objects.requireNonNull(builder.name, MISSING_RULESET_NAME);
1✔
69
        description = Objects.requireNonNull(builder.description, MISSING_RULESET_DESCRIPTION);
1✔
70
        // TODO: ideally, the rules would be unmodifiable, too. But removeDysfunctionalRules might change the rules. #3868
71
        rules = builder.rules;
1✔
72
        excludePatterns = Collections.unmodifiableList(new ArrayList<>(builder.excludePatterns));
1✔
73
        includePatterns = Collections.unmodifiableList(new ArrayList<>(builder.includePatterns));
1✔
74

75
        final Predicate<String> regexFilter = PredicateUtil.buildRegexFilterIncludeOverExclude(includePatterns, excludePatterns);
1✔
76
        filter = PredicateUtil.toNormalizedFileFilter(regexFilter);
1✔
77
    }
1✔
78

79
    public RuleSet(final RuleSet rs) {
1✔
80
        checksum = rs.checksum;
1✔
81
        fileName = rs.fileName;
1✔
82
        name = rs.name;
1✔
83
        description = rs.description;
1✔
84

85
        rules = new ArrayList<>(rs.rules.size());
1✔
86
        for (final Rule rule : rs.rules) {
1✔
87
            rules.add(rule.deepCopy());
1✔
88
        }
1✔
89

90
        excludePatterns = rs.excludePatterns; // we can share immutable lists of immutable elements
1✔
91
        includePatterns = rs.includePatterns;
1✔
92
        filter = rs.filter; // filters are immutable, can be shared
1✔
93
    }
1✔
94

95
    /**
96
     * Creates a new ruleset containing a single rule. The ruleset will
97
     * have default description, name, and null file name.
98
     *
99
     * @param rule The rule being created
100
     *
101
     * @return The newly created RuleSet
102
     */
103
    public static RuleSet forSingleRule(final Rule rule) {
104
        final long checksum;
105
        if (rule instanceof XPathRule) {
1!
106
            checksum = ((XPathRule) rule).getXPathExpression().hashCode();
×
107
        } else {
108
            // TODO : Is this good enough? all properties' values + rule name
109
            checksum = rule.getPropertiesByPropertyDescriptor().values().hashCode() * 31 + rule.getName().hashCode();
1✔
110
        }
111

112
        final RuleSetBuilder builder =
1✔
113
            new RuleSetBuilder(checksum)
114
                .withName(rule.getName())
1✔
115
                .withDescription("RuleSet for " + rule.getName());
1✔
116
        builder.addRule(rule);
1✔
117
        return builder.build();
1✔
118
    }
119

120

121
    /**
122
     * Creates a new ruleset with the given metadata such as name, description,
123
     * fileName, exclude/include patterns are used. The rules are taken from the given
124
     * collection.
125
     *
126
     * <p><strong>Note:</strong> The rule instances are shared between the collection
127
     * and the new ruleset (copy-by-reference). This might lead to concurrency issues,
128
     * if the rules of the collection are also referenced by other rulesets and used
129
     * in different threads.
130
     * </p>
131
     *
132
     * @param name            the name of the ruleset
133
     * @param description     the description
134
     * @param fileName        the filename
135
     * @param excludePatterns list of exclude patterns
136
     * @param includePatterns list of include patterns, that override the exclude patterns
137
     * @param rules           the collection with the rules to add to the new ruleset
138
     *
139
     * @return the new ruleset
140
     *
141
     * @throws NullPointerException If any parameter is null, or the collections contain null elements
142
     */
143
    public static RuleSet create(String name,
144
                                 String description,
145
                                 String fileName,
146
                                 Collection<Pattern> excludePatterns,
147
                                 Collection<Pattern> includePatterns,
148
                                 Iterable<? extends Rule> rules) {
149
        RuleSetBuilder builder = new RuleSetBuilder(0L); // TODO: checksum missing
×
150
        builder.withName(name)
×
151
               .withDescription(description)
×
152
               .withFileName(fileName)
×
153
               .replaceFileExclusions(excludePatterns)
×
154
               .replaceFileInclusions(includePatterns);
×
155
        for (Rule rule : rules) {
×
156
            builder.addRule(rule);
×
157
        }
×
158
        return builder.build();
×
159
    }
160

161
    /**
162
     * Creates a copy of the given ruleset. All properties like name, description, fileName
163
     * and exclude/include patterns are copied.
164
     *
165
     * <p><strong>Note:</strong> The rule instances are shared between the original
166
     * and the new ruleset (copy-by-reference). This might lead to concurrency issues,
167
     * if the original ruleset and the new ruleset are used in different threads.
168
     * </p>
169
     *
170
     * @param original the original rule set to copy from
171
     *
172
     * @return the copy
173
     */
174
    public static RuleSet copy(RuleSet original) {
175
        return new RuleSet(original);
×
176
    }
177

178
    /* package */ static class RuleSetBuilder {
179

180
        public String description;
181
        public String name;
182
        public String fileName;
183
        private final List<Rule> rules = new ArrayList<>();
1✔
184
        private final Set<Pattern> excludePatterns = new LinkedHashSet<>();
1✔
185
        private final Set<Pattern> includePatterns = new LinkedHashSet<>();
1✔
186
        private final long checksum;
187

188
        /* package */ RuleSetBuilder(final long checksum) {
1✔
189
            this.checksum = checksum;
1✔
190
        }
1✔
191

192
        /** Copy constructor. Takes the same checksum as the original ruleset. */
193
        /* package */ RuleSetBuilder(final RuleSet original) {
×
194
            checksum = original.getChecksum();
×
195
            this.withName(original.getName())
×
196
                .withDescription(original.getDescription())
×
197
                .withFileName(original.getFileName())
×
198
                .replaceFileExclusions(original.getFileExclusions())
×
199
                .replaceFileInclusions(original.getFileInclusions());
×
200
            addRuleSet(original);
×
201
        }
×
202

203
        /**
204
         * Add a new rule to this ruleset. Note that this method does not check
205
         * for duplicates.
206
         *
207
         * @param newRule
208
         *            the rule to be added
209
         * @return The same builder, for a fluid programming interface
210
         */
211
        public RuleSetBuilder addRule(final Rule newRule) {
212
            if (newRule == null) {
1!
213
                throw new IllegalArgumentException(MISSING_RULE);
×
214
            }
215

216
            // check for duplicates - adding more than one rule with the same name will
217
            // be problematic - see #RuleSet.getRuleByName(String)
218
            for (Rule rule : rules) {
1✔
219
                if (rule.getName().equals(newRule.getName()) && rule.getLanguage().equals(newRule.getLanguage())) {
1!
220
                    LOG.warn("The rule with name {} is duplicated. "
×
221
                            + "Future versions of PMD will reject to load such rulesets.",
222
                            newRule.getName());
×
223
                    break;
×
224
                }
225
            }
1✔
226

227
            rules.add(newRule);
1✔
228
            return this;
1✔
229
        }
230

231
        /**
232
         * Finds an already added rule by same name and language, if it already exists.
233
         * @param rule the rule to search
234
         * @return the already added rule or <code>null</code> if no rule was added yet to the builder.
235
         */
236
        Rule getExistingRule(final Rule rule) {
237
            for (Rule r : rules) {
1✔
238
                if (r.getName().equals(rule.getName()) && r.getLanguage().equals(rule.getLanguage())) {
1!
239
                    return r;
1✔
240
                }
241
            }
1✔
242

243
            return null;
1✔
244
        }
245

246
        /**
247
         * Checks, whether a rule with the same name and language already exists in the
248
         * ruleset.
249
         * @param rule to rule to check
250
         * @return <code>true</code> if the rule already exists, <code>false</code> if the given
251
         *     rule is the first configuration of this rule.
252
         */
253
        boolean hasRule(final Rule rule) {
254
            return getExistingRule(rule) != null;
1✔
255
        }
256

257
        /**
258
         * Adds a rule. If a rule with the same name and language already
259
         * existed before in the ruleset, then the new rule will replace it.
260
         * This makes sure that the rule configured is overridden.
261
         *
262
         * @param rule
263
         *            the new rule to add
264
         * @return The same builder, for a fluid programming interface
265
         */
266
        public RuleSetBuilder addRuleReplaceIfExists(final Rule rule) {
267
            if (rule == null) {
1!
268
                throw new IllegalArgumentException(MISSING_RULE);
×
269
            }
270

271
            for (final Iterator<Rule> it = rules.iterator(); it.hasNext();) {
1✔
272
                final Rule r = it.next();
1✔
273
                if (r.getName().equals(rule.getName()) && r.getLanguage().equals(rule.getLanguage())) {
1!
274
                    it.remove();
1✔
275
                }
276
            }
1✔
277
            addRule(rule);
1✔
278
            return this;
1✔
279
        }
280

281
        /**
282
         * Only adds a rule to the ruleset if no rule with the same name for the
283
         * same language was added before, so that the existent rule
284
         * configuration won't be overridden.
285
         *
286
         * @param ruleOrRef
287
         *            the new rule to add
288
         * @return The same builder, for a fluid programming interface
289
         */
290
        public RuleSetBuilder addRuleIfNotExists(final Rule ruleOrRef) {
291
            if (ruleOrRef == null) {
1!
292
                throw new IllegalArgumentException(MISSING_RULE);
×
293
            }
294

295
            // resolve the underlying rule, to avoid adding duplicated rules
296
            // if the rule has been renamed/merged and moved at the same time
297
            Rule rule = ruleOrRef;
1✔
298
            while (rule instanceof RuleReference) {
1✔
299
                rule = ((RuleReference) rule).getRule();
1✔
300
            }
301

302
            boolean exists = hasRule(rule);
1✔
303
            if (!exists) {
1✔
304
                addRule(ruleOrRef);
1✔
305
            }
306
            return this;
1✔
307
        }
308

309
        /**
310
         * Add a new rule by reference to this ruleset.
311
         *
312
         * @param ruleSetFileName
313
         *            the ruleset which contains the rule
314
         * @param rule
315
         *            the rule to be added
316
         * @return The same builder, for a fluid programming interface
317
         */
318
        public RuleSetBuilder addRuleByReference(final String ruleSetFileName, final Rule rule) {
319
            if (StringUtils.isBlank(ruleSetFileName)) {
×
320
                throw new RuntimeException(
×
321
                        "Adding a rule by reference is not allowed with an empty rule set file name.");
322
            }
323
            if (rule == null) {
×
324
                throw new IllegalArgumentException("Cannot add a null rule reference to a RuleSet");
×
325
            }
326
            final RuleReference ruleReference;
327
            if (rule instanceof RuleReference) {
×
328
                ruleReference = (RuleReference) rule;
×
329
            } else {
330
                final RuleSetReference ruleSetReference = new RuleSetReference(ruleSetFileName);
×
331
                ruleReference = new RuleReference(rule, ruleSetReference);
×
332
            }
333
            rules.add(ruleReference);
×
334
            return this;
×
335
        }
336

337
        /**
338
         * Add all rules of a whole RuleSet to this RuleSet
339
         *
340
         * @param ruleSet
341
         *            the RuleSet to add
342
         * @return The same builder, for a fluid programming interface
343
         */
344
        public RuleSetBuilder addRuleSet(final RuleSet ruleSet) {
345
            rules.addAll(ruleSet.getRules());
1✔
346
            return this;
1✔
347
        }
348

349
        /**
350
         * Add all rules by reference from one RuleSet to this RuleSet. The
351
         * rules can be added as individual references, or collectively as an
352
         * all rule reference.
353
         *
354
         * @param ruleSet
355
         *            the RuleSet to add
356
         * @param allRules
357
         *            <code>true</code> if the ruleset should be added
358
         *            collectively or <code>false</code> to add individual
359
         *            references for each rule.
360
         * @return The same builder, for a fluid programming interface
361
         */
362
        public RuleSetBuilder addRuleSetByReference(final RuleSet ruleSet, final boolean allRules) {
363
            return addRuleSetByReference(ruleSet, allRules, (String[]) null);
1✔
364
        }
365

366
        /**
367
         * Add all rules by reference from one RuleSet to this RuleSet. The
368
         * rules can be added as individual references, or collectively as an
369
         * all rule reference.
370
         *
371
         * @param ruleSet
372
         *            the RuleSet to add
373
         * @param allRules
374
         *            <code>true</code> if the ruleset should be added
375
         *            collectively or <code>false</code> to add individual
376
         *            references for each rule.
377
         * @param excludes
378
         *            names of the rules that should be excluded.
379
         * @return The same builder, for a fluid programming interface
380
         */
381
        public RuleSetBuilder addRuleSetByReference(final RuleSet ruleSet, final boolean allRules,
382
                final String... excludes) {
383
            if (StringUtils.isBlank(ruleSet.getFileName())) {
1✔
384
                throw new RuntimeException(
1✔
385
                        "Adding a rule by reference is not allowed with an empty rule set file name.");
386
            }
387
            final RuleSetReference ruleSetReference;
388
            if (excludes == null) {
1✔
389
                ruleSetReference = new RuleSetReference(ruleSet.getFileName(), allRules);
1✔
390
            } else {
391
                ruleSetReference = new RuleSetReference(ruleSet.getFileName(), allRules, new LinkedHashSet<>(Arrays.asList(excludes)));
1✔
392
            }
393

394
            for (final Rule rule : ruleSet.getRules()) {
1✔
395
                final RuleReference ruleReference = new RuleReference(rule, ruleSetReference);
1✔
396
                rules.add(ruleReference);
1✔
397
            }
1✔
398
            return this;
1✔
399
        }
400

401
        /**
402
         * Adds some new file exclusion patterns.
403
         *
404
         * @param p1   The first pattern
405
         * @param rest Additional patterns
406
         *
407
         * @return This builder
408
         *
409
         * @throws NullPointerException If any of the specified patterns is null
410
         */
411
        public RuleSetBuilder withFileExclusions(Pattern p1, Pattern... rest) {
412
            Objects.requireNonNull(p1, "Pattern was null");
1✔
413
            Objects.requireNonNull(rest, "Other patterns was null");
1✔
414
            excludePatterns.add(p1);
1✔
415
            for (Pattern p : rest) {
1✔
416
                Objects.requireNonNull(p, "Pattern was null");
1✔
417
                excludePatterns.add(p);
1✔
418
            }
419
            return this;
1✔
420
        }
421

422
        /**
423
         * Adds some new file exclusion patterns.
424
         *
425
         * @param patterns Exclusion patterns to add
426
         *
427
         * @return This builder
428
         *
429
         * @throws NullPointerException If any of the specified patterns is null
430
         */
431
        public RuleSetBuilder withFileExclusions(Collection<? extends Pattern> patterns) {
432
            Objects.requireNonNull(patterns, "Pattern collection was null");
1✔
433
            for (Pattern p : patterns) {
1✔
434
                Objects.requireNonNull(p, "Pattern was null");
1✔
435
                excludePatterns.add(p);
1✔
436
            }
1✔
437
            return this;
1✔
438
        }
439

440
        /**
441
         * Replaces the existing exclusion patterns with the given patterns.
442
         *
443
         * @param patterns Exclusion patterns to set
444
         *
445
         * @return This builder
446
         *
447
         * @throws NullPointerException If any of the specified patterns is null
448
         */
449
        public RuleSetBuilder replaceFileExclusions(Collection<? extends Pattern> patterns) {
450
            Objects.requireNonNull(patterns, "Pattern collection was null");
1✔
451
            excludePatterns.clear();
1✔
452
            for (Pattern p : patterns) {
1✔
453
                Objects.requireNonNull(p, "Pattern was null");
1✔
454
                excludePatterns.add(p);
1✔
455
            }
1✔
456
            return this;
1✔
457
        }
458

459

460
        /**
461
         * Adds some new file inclusion patterns.
462
         *
463
         * @param p1   The first pattern
464
         * @param rest Additional patterns
465
         *
466
         * @return This builder
467
         *
468
         * @throws NullPointerException If any of the specified patterns is null
469
         */
470
        public RuleSetBuilder withFileInclusions(Pattern p1, Pattern... rest) {
471
            Objects.requireNonNull(p1, "Pattern was null");
1✔
472
            Objects.requireNonNull(rest, "Other patterns was null");
1✔
473
            includePatterns.add(p1);
1✔
474
            for (Pattern p : rest) {
1✔
475
                Objects.requireNonNull(p, "Pattern was null");
1✔
476
                includePatterns.add(p);
1✔
477
            }
478
            return this;
1✔
479
        }
480

481
        /**
482
         * Adds some new file inclusion patterns.
483
         *
484
         * @param patterns Inclusion patterns to add
485
         *
486
         * @return This builder
487
         *
488
         * @throws NullPointerException If any of the specified patterns is null
489
         */
490
        public RuleSetBuilder withFileInclusions(Collection<? extends Pattern> patterns) {
491
            Objects.requireNonNull(patterns, "Pattern collection was null");
1✔
492
            for (Pattern p : patterns) {
1✔
493
                Objects.requireNonNull(p, "Pattern was null");
1✔
494
                includePatterns.add(p);
1✔
495
            }
1✔
496
            return this;
1✔
497
        }
498

499
        /**
500
         * Replaces the existing inclusion patterns with the given patterns.
501
         *
502
         * @param patterns Inclusion patterns to set
503
         *
504
         * @return This builder
505
         *
506
         * @throws NullPointerException If any of the specified patterns is null
507
         */
508
        public RuleSetBuilder replaceFileInclusions(Collection<? extends Pattern> patterns) {
509
            Objects.requireNonNull(patterns, "Pattern collection was null");
1✔
510
            includePatterns.clear();
1✔
511
            for (Pattern p : patterns) {
1✔
512
                Objects.requireNonNull(p, "Pattern was null");
1✔
513
                includePatterns.add(p);
1✔
514
            }
1✔
515
            return this;
1✔
516
        }
517

518
        public RuleSetBuilder withFileName(final String fileName) {
519
            this.fileName = fileName;
1✔
520
            return this;
1✔
521
        }
522

523
        public RuleSetBuilder withName(final String name) {
524
            this.name = Objects.requireNonNull(name, MISSING_RULESET_NAME);
1✔
525
            return this;
1✔
526
        }
527

528
        public RuleSetBuilder withDescription(final String description) {
529
            this.description = Objects.requireNonNull(description, MISSING_RULESET_DESCRIPTION);
1✔
530
            return this;
1✔
531
        }
532

533
        public boolean hasDescription() {
534
            return this.description != null;
1✔
535
        }
536

537
        public String getName() {
538
            return name;
1✔
539
        }
540

541
        public RuleSet build() {
542
            return new RuleSet(this);
1✔
543
        }
544

545
        public void filterRulesByPriority(RulePriority minimumPriority) {
546
            Iterator<Rule> iterator = rules.iterator();
1✔
547
            while (iterator.hasNext()) {
1✔
548
                Rule rule = iterator.next();
1✔
549
                if (rule.getPriority().compareTo(minimumPriority) > 0) {
1✔
550
                    LOG.debug("Removing rule {} due to priority: {} required: {}",
1✔
551
                            rule.getName(), rule.getPriority(), minimumPriority);
1✔
552
                    iterator.remove();
1✔
553
                }
554
            }
1✔
555
        }
1✔
556
    }
557

558
    /**
559
     * Returns the number of rules in this ruleset
560
     *
561
     * @return an int representing the number of rules
562
     */
563
    public int size() {
564
        return rules.size();
1✔
565
    }
566

567
    /**
568
     * Returns the actual Collection of rules in this ruleset
569
     *
570
     * @return a Collection with the rules. All objects are of type {@link Rule}
571
     */
572
    public Collection<Rule> getRules() {
573
        return rules;
1✔
574
    }
575

576

577
    /**
578
     * Returns the first Rule found with the given name (case-sensitive).
579
     *
580
     * Note: Since we support multiple languages, rule names are not expected to
581
     * be unique within any specific ruleset.
582
     *
583
     * @param ruleName
584
     *            the exact name of the rule to find
585
     * @return the rule or null if not found
586
     */
587
    public Rule getRuleByName(String ruleName) {
588

589
        for (Rule r : rules) {
1✔
590
            if (r.getName().equals(ruleName)) {
1✔
591
                return r;
1✔
592
            }
593
        }
1✔
594
        return null;
1✔
595
    }
596

597
    /**
598
     * Check if a given source file should be checked by rules in this RuleSet.
599
     * A file should not be checked if there is an <code>exclude</code> pattern
600
     * which matches the file, unless there is an <code>include</code> pattern
601
     * which also matches the file. In other words, <code>include</code>
602
     * patterns override <code>exclude</code> patterns.
603
     *
604
     * @param qualFileName the source path to check
605
     *
606
     * @return <code>true</code> if the file should be checked,
607
     *     <code>false</code> otherwise
608
     *
609
     * @apiNote Internal API.
610
     */
611
    boolean applies(FileId qualFileName) {
612
        return filter.test(qualFileName.getAbsolutePath());
1✔
613
    }
614

615
    /**
616
     * Does the given Rule apply to the given LanguageVersion? If so, the
617
     * Language must be the same and be between the minimum and maximums
618
     * versions on the Rule.
619
     *
620
     * @param rule
621
     *            The rule.
622
     * @param languageVersion
623
     *            The language version.
624
     *
625
     * @return <code>true</code> if the given rule matches the given language,
626
     *         which means, that the rule would be executed.
627
     *
628
     * @apiNote This is internal API.
629
     */
630
    static boolean applies(Rule rule, LanguageVersion languageVersion) {
631
        assert rule.getLanguage() != null : "Rule has no language " + rule;
1!
632

633
        if (languageVersion.getLanguage().isDialectOf(rule.getLanguage())) {
1!
634
            // Dialects don't check the version of the base language yet…
NEW
635
            return true;
×
636
        }
637

638
        final LanguageVersion min = rule.getMinimumLanguageVersion();
1✔
639
        final LanguageVersion max = rule.getMaximumLanguageVersion();
1✔
640

641
        // All rules from base languages also apply to dialects. They
642
        // have to share a parser for that to work.
643
        return rule.getLanguage().equals(languageVersion.getLanguage())
1✔
644
                && (min == null || min.compareTo(languageVersion) <= 0)
1✔
645
                && (max == null || max.compareTo(languageVersion) >= 0);
1✔
646
    }
647

648
    /**
649
     * Two rulesets are equals, if they have the same name and contain the same
650
     * rules.
651
     *
652
     * @param o
653
     *            the other ruleset to compare with
654
     * @return <code>true</code> if o is a ruleset with the same name and rules,
655
     *         <code>false</code> otherwise
656
     */
657
    @Override
658
    public boolean equals(Object o) {
659
        if (!(o instanceof RuleSet)) {
1✔
660
            return false; // Trivial
1✔
661
        }
662

663
        if (this == o) {
1✔
664
            return true; // Basic equality
1✔
665
        }
666

667
        RuleSet ruleSet = (RuleSet) o;
1✔
668
        return getName().equals(ruleSet.getName()) && getRules().equals(ruleSet.getRules());
1✔
669
    }
670

671
    @Override
672
    public int hashCode() {
673
        return getName().hashCode() + 13 * getRules().hashCode();
1✔
674
    }
675

676
    public String getFileName() {
677
        return fileName;
1✔
678
    }
679

680
    public String getName() {
681
        return name;
1✔
682
    }
683

684
    public String getDescription() {
685
        return description;
1✔
686
    }
687

688
    /**
689
     * Returns the file exclusion patterns as an unmodifiable list.
690
     */
691
    public List<Pattern> getFileExclusions() {
692
        return excludePatterns;
1✔
693
    }
694

695
    /**
696
     * Returns the file inclusion patterns as an unmodifiable list.
697
     */
698
    public List<Pattern> getFileInclusions() {
699
        return includePatterns;
1✔
700
    }
701

702

703
    /**
704
     * Remove and collect any misconfigured rules.
705
     *
706
     * @param collector
707
     *            the removed rules will be added to this collection
708
     */
709
    // TODO remove this method. This mutates rulesets for nothing. Whether a rule
710
    //  is dysfunctional or not should be checked when it is initialized.
711
    //  See also https://github.com/pmd/pmd/issues/3868 and https://github.com/pmd/pmd/issues/3901
712
    public void removeDysfunctionalRules(Collection<Rule> collector) {
713
        Iterator<Rule> iter = rules.iterator();
1✔
714

715
        while (iter.hasNext()) {
1✔
716
            Rule rule = iter.next();
1✔
717
            if (rule.dysfunctionReason() != null) {
1!
718
                iter.remove();
×
719
                collector.add(rule);
×
720
            }
721
        }
1✔
722
    }
1✔
723

724
    @Override
725
    public long getChecksum() {
726
        return checksum;
×
727
    }
728
}
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