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

pmd / pmd / 152

11 Sep 2025 09:46AM UTC coverage: 78.65% (+0.05%) from 78.603%
152

push

github

web-flow
[java] New rule: ModifierOrder (#5601)

18160 of 23935 branches covered (75.87%)

Branch coverage included in aggregate %.

175 of 182 new or added lines in 10 files covered. (96.15%)

2 existing lines in 2 files now uncovered.

39628 of 49540 relevant lines covered (79.99%)

0.81 hits per line

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

78.5
/pmd-core/src/main/java/net/sourceforge/pmd/reporting/RuleContext.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.reporting;
6

7
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
8

9
import java.text.MessageFormat;
10
import java.util.List;
11
import java.util.Map;
12
import java.util.Objects;
13
import java.util.Optional;
14

15
import org.apache.commons.lang3.StringUtils;
16
import org.checkerframework.checker.nullness.qual.NonNull;
17
import org.checkerframework.checker.nullness.qual.Nullable;
18

19
import net.sourceforge.pmd.annotation.Experimental;
20
import net.sourceforge.pmd.lang.LanguageVersionHandler;
21
import net.sourceforge.pmd.lang.ast.AstInfo;
22
import net.sourceforge.pmd.lang.ast.Node;
23
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
24
import net.sourceforge.pmd.lang.ast.internal.NodeFindingUtil;
25
import net.sourceforge.pmd.lang.document.FileLocation;
26
import net.sourceforge.pmd.lang.document.TextRange2d;
27
import net.sourceforge.pmd.lang.rule.AbstractRule;
28
import net.sourceforge.pmd.lang.rule.Rule;
29
import net.sourceforge.pmd.properties.PropertyDescriptor;
30
import net.sourceforge.pmd.reporting.Report.SuppressedViolation;
31

32
/**
33
 * The API for rules to report violations or errors during analysis.
34
 * This forwards events to a {@link FileAnalysisListener}. It implements
35
 * violation suppression by filtering some violations out, according to
36
 * the {@link ViolationSuppressor}s for the language.
37
 * <p>
38
 * A RuleContext contains a Rule instance and violation reporting methods
39
 * implicitly report only for that rule. Contrary to PMD 6, RuleContext is
40
 * not unique throughout the analysis, a separate one is used per file and rule.
41
 */
42
public final class RuleContext {
43
    // Rule contexts do not need to be thread-safe, within PmdRunnable
44
    // they are stack-local
45

46
    private static final Object[] NO_ARGS = new Object[0];
1✔
47
    static final List<ViolationSuppressor> DEFAULT_SUPPRESSORS = listOf(ViolationSuppressor.NOPMD_COMMENT_SUPPRESSOR,
1✔
48
                                                                        ViolationSuppressor.REGEX_SUPPRESSOR,
49
                                                                        ViolationSuppressor.XPATH_SUPPRESSOR);
50

51
    private final FileAnalysisListener listener;
52
    private final Rule rule;
53

54
    /**
55
     * @apiNote Internal API
56
     */
57
    RuleContext(FileAnalysisListener listener, Rule rule) {
1✔
58
        Objects.requireNonNull(listener, "Listener was null");
1✔
59
        Objects.requireNonNull(rule, "Rule was null");
1✔
60
        this.listener = listener;
1✔
61
        this.rule = rule;
1✔
62
    }
1✔
63

64
    /**
65
     * @apiNote Internal API. Used in {@link AbstractRule} in {@code asCtx(Object)},
66
     * through {@link InternalApiBridge}.
67
     */
68
    Rule getRule() {
69
        return rule;
×
70
    }
71

72
    private String getDefaultMessage() {
73
        return rule.getMessage();
1✔
74
    }
75

76
    /**
77
     * Record a new violation of the contextual rule, at the given node.
78
     *
79
     * @param location Location of the violation
80
     */
81
    public void addViolation(Node location) {
82
        addViolationWithMessage(location, getDefaultMessage(), NO_ARGS);
1✔
83
    }
1✔
84

85
    /**
86
     * Record a new violation of the contextual rule, at the given node.
87
     * The default violation message ({@link Rule#getMessage()}) is formatted
88
     * using the given format arguments.
89
     *
90
     * @param location   Location of the violation
91
     * @param formatArgs Format arguments for the message
92
     *
93
     * @see MessageFormat
94
     */
95
    public void addViolation(Node location, Object... formatArgs) {
96
        addViolationWithMessage(location, getDefaultMessage(), formatArgs);
1✔
97
    }
1✔
98

99
    /**
100
     * Record a new violation of the contextual rule, at the given node.
101
     * The given violation message ({@link Rule#getMessage()}) is treated
102
     * as a format string for a {@link MessageFormat} and should hence use
103
     * appropriate escapes. No formatting arguments are provided.
104
     *
105
     * @param location Location of the violation
106
     * @param message  Violation message
107
     */
108
    public void addViolationWithMessage(Node location, String message) {
109
        addViolationWithPosition(location, -1, -1, message, NO_ARGS);
1✔
110
    }
1✔
111

112
    /**
113
     * Record a new violation of the contextual rule, at the given node.
114
     * The given violation message ({@link Rule#getMessage()}) is treated
115
     * as a format string for a {@link MessageFormat} and should hence use
116
     * appropriate escapes. The given formatting arguments are used.
117
     *
118
     * @param location   Location of the violation
119
     * @param message    Violation message
120
     * @param formatArgs Format arguments for the message
121
     */
122
    public void addViolationWithMessage(Node location, String message, Object... formatArgs) {
123
        addViolationWithPosition(location, -1, -1, message, formatArgs);
1✔
124
    }
1✔
125

126
    /**
127
     * Record a new violation of the contextual rule, at the given node.
128
     * The position is refined using the given begin and end line numbers.
129
     * The given violation message ({@link Rule#getMessage()}) is treated
130
     * as a format string for a {@link MessageFormat} and should hence use
131
     * appropriate escapes. The given formatting arguments are used.
132
     *
133
     * @param node       Location of the violation
134
     * @param message    Violation message
135
     * @param formatArgs Format arguments for the message
136
     */
137
    public void addViolationWithPosition(Node node, int beginLine, int endLine, String message, Object... formatArgs) {
138
        FileLocation location;
139
        if (beginLine != -1 && endLine != -1) {
1!
140
            location = FileLocation.range(node.getTextDocument().getFileId(),
1✔
141
                                          TextRange2d.range2d(beginLine, 1, endLine, 1));
1✔
142
        } else {
143
            location = node.getReportLocation();
1✔
144
        }
145
        addViolationWithPosition(node, node.getAstInfo(), location, message, formatArgs);
1✔
146
    }
1✔
147

148
    /**
149
     * Record a new violation of the contextual rule, at the given token location.
150
     * The position is refined using the given begin and end line numbers.
151
     * The given violation message ({@link Rule#getMessage()}) is treated
152
     * as a format string for a {@link MessageFormat} and should hence use
153
     * appropriate escapes. The given formatting arguments are used.
154
     *
155
     * @param node Location of the violation (node or token) - only used to determine suppression
156
     * @param token   Report location of the violation
157
     * @param message    Violation message
158
     * @param formatArgs Format arguments for the message
159
     * @experimental Since 7.17.0. This will probably never be stabilized, will instead be
160
     *      replaced by a fluent API or something to report violations. Do not use
161
     *      this outside of the PMD codebase. See <a href="https://github.com/pmd/pmd/issues/5039">[core] Add fluent API to report violations #5039</a>.
162
     */
163
    @Experimental
164
    public void addViolationWithPosition(Node node, JavaccToken token, String message, Object... formatArgs) {
NEW
165
        addViolationWithPosition(node, node.getAstInfo(), token.getReportLocation(), message, formatArgs);
×
NEW
166
    }
×
167

168
    /**
169
     * Record a new violation of the contextual rule, at the given location (node or token).
170
     * The position is refined using the given begin and end line numbers.
171
     * The given violation message ({@link Rule#getMessage()}) is treated
172
     * as a format string for a {@link MessageFormat} and should hence use
173
     * appropriate escapes. The given formatting arguments are used.
174
     *
175
     * @param reportable Location of the violation (node or token) - only used to determine suppression
176
     * @param astInfo    Info about the root of the tree ({@link Node#getAstInfo()})
177
     * @param location   Report location of the violation
178
     * @param message    Violation message
179
     * @param formatArgs Format arguments for the message
180
     * @experimental Since 7.9.0. This will probably never be stabilized, will instead be
181
     *      replaced by a fluent API or something to report violations. Do not use
182
     *      this outside of the PMD codebase. See <a href="https://github.com/pmd/pmd/issues/5039">[core] Add fluent API to report violations #5039</a>.
183
     */
184
    @Experimental
185
    public void addViolationWithPosition(Reportable reportable, AstInfo<?> astInfo, FileLocation location,
186
                                         String message, Object... formatArgs) {
187
        Objects.requireNonNull(reportable, "Node was null");
1✔
188
        Objects.requireNonNull(message, "Message was null");
1✔
189
        Objects.requireNonNull(formatArgs, "Format arguments were null, use an empty array");
1✔
190

191
        Node suppressionNode = getNearestNode(reportable, astInfo);
1✔
192
        RuleViolation violation = createViolation(() -> location, astInfo, suppressionNode, message, formatArgs);
1✔
193
        SuppressedViolation suppressed = suppressOrNull(suppressionNode, violation, astInfo);
1✔
194

195
        if (suppressed != null) {
1✔
196
            listener.onSuppressedRuleViolation(suppressed);
1✔
197
        } else {
198
            listener.onRuleViolation(violation);
1✔
199
        }
200
    }
1✔
201

202
    /**
203
     * @experimental Since 7.14.0. See <a href="https://github.com/pmd/pmd/pull/5609">[core] Add rule to report unnecessary suppression comments/annotations #5609</a>
204
     */
205
    @Experimental
206
    public void addViolationNoSuppress(Reportable reportable, AstInfo<?> astInfo,
207
                                String message, Object... formatArgs) {
208
        Objects.requireNonNull(reportable, "Node was null");
×
209
        Objects.requireNonNull(message, "Message was null");
×
210
        Objects.requireNonNull(formatArgs, "Format arguments were null, use an empty array");
×
211

212
        Node nearestNode = getNearestNode(reportable, astInfo);
×
213
        RuleViolation violation = createViolation(reportable, astInfo, nearestNode, message, formatArgs);
×
214
        listener.onRuleViolation(violation);
×
215
    }
×
216

217
    private RuleViolation createViolation(Reportable reportable, AstInfo<?> astInfo, Node nearestNode, String message, Object... formatArgs) {
218
        LanguageVersionHandler handler = astInfo.getLanguageProcessor().services();
1✔
219
        Map<String, String> extraVariables = ViolationDecorator.apply(handler.getViolationDecorator(), nearestNode);
1✔
220
        String description = makeMessage(message, formatArgs, extraVariables);
1✔
221
        FileLocation location = reportable.getReportLocation();
1✔
222
        return new ParametricRuleViolation(rule, location, description, extraVariables);
1✔
223
    }
224

225
    private Node getNearestNode(Reportable reportable, AstInfo<?> astInfo) {
226
        if (reportable instanceof Node) {
1!
227
            return (Node) reportable;
1✔
228
        }
229
        int startOffset = getStartOffset(reportable, astInfo);
×
230
        Optional<Node> foundNode = NodeFindingUtil.findNodeAt(astInfo.getRootNode(), startOffset);
×
231
        // default to the root node
232
        return foundNode.orElse(astInfo.getRootNode());
×
233
    }
234

235
    private static int getStartOffset(Reportable reportable, AstInfo<?> astInfo) {
236
        if (reportable instanceof JavaccToken) {
×
237
            return ((JavaccToken) reportable).getRegion().getStartOffset();
×
238
        }
239
        FileLocation loc = reportable.getReportLocation();
×
240
        return astInfo.getTextDocument().offsetAtLineColumn(loc.getStartPos());
×
241
    }
242

243
    private static @Nullable SuppressedViolation suppressOrNull(Node location, RuleViolation rv, AstInfo<?> astInfo) {
244
        LanguageVersionHandler handler = astInfo.getLanguageProcessor().services();
1✔
245
        SuppressedViolation suppressed = ViolationSuppressor.suppressOrNull(handler.getExtraViolationSuppressors(), rv, location);
1✔
246
        if (suppressed == null) {
1!
247
            suppressed = ViolationSuppressor.suppressOrNull(DEFAULT_SUPPRESSORS, rv, location);
1✔
248
        }
249
        return suppressed;
1✔
250
    }
251

252
    private String makeMessage(@NonNull String message, Object[] args, Map<String, String> extraVars) {
253
        // Escape PMD specific variable message format, specifically the {
254
        // in the ${, so MessageFormat doesn't bitch.
255
        final String escapedMessage = StringUtils.replace(message, "${", "$'{'");
1✔
256
        String formatted = MessageFormat.format(escapedMessage, args);
1✔
257
        return expandVariables(formatted, extraVars);
1✔
258
    }
259

260

261
    private String expandVariables(String message, Map<String, String> extraVars) {
262

263
        if (!message.contains("${")) {
1✔
264
            return message;
1✔
265
        }
266

267
        StringBuilder buf = new StringBuilder(message);
1✔
268
        int startIndex = -1;
1✔
269
        while ((startIndex = buf.indexOf("${", startIndex + 1)) >= 0) {
1✔
270
            final int endIndex = buf.indexOf("}", startIndex);
1✔
271
            if (endIndex >= 0) {
1!
272
                final String name = buf.substring(startIndex + 2, endIndex);
1✔
273
                String variableValue = getVariableValue(name, extraVars);
1✔
274
                if (variableValue != null) {
1✔
275
                    buf.replace(startIndex, endIndex + 1, variableValue);
1✔
276
                }
277
            }
278
        }
1✔
279
        return buf.toString();
1✔
280
    }
281

282
    private String getVariableValue(String name, Map<String, String> extraVars) {
283
        String value = extraVars.get(name);
1✔
284
        if (value != null) {
1✔
285
            return value;
1✔
286
        }
287
        final PropertyDescriptor<?> propertyDescriptor = rule.getPropertyDescriptor(name);
1✔
288
        return propertyDescriptor == null ? null : String.valueOf(rule.getProperty(propertyDescriptor));
1✔
289
    }
290
}
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