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

pmd / pmd / 380

29 Jan 2026 03:55PM UTC coverage: 78.964%. Remained the same
380

push

github

adangel
[doc] ADR 3: Clarify javadoc tags (#6392)

18537 of 24358 branches covered (76.1%)

Branch coverage included in aggregate %.

40391 of 50268 relevant lines covered (80.35%)

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.Locale;
12
import java.util.Map;
13
import java.util.Objects;
14
import java.util.Optional;
15

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

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

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

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

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

55
    /**
56
     * @internalApi None of this is published API, and compatibility can be broken anytime! Use this only at your own risk.
57
     */
58
    RuleContext(FileAnalysisListener listener, Rule rule) {
1✔
59
        Objects.requireNonNull(listener, "Listener was null");
1✔
60
        Objects.requireNonNull(rule, "Rule was null");
1✔
61
        this.listener = listener;
1✔
62
        this.rule = rule;
1✔
63
    }
1✔
64

65
    /**
66
     * @internalApi None of this is published API, and compatibility can be broken anytime! Use this only at your own risk.
67
     * Used in {@link AbstractRule} in {@code asCtx(Object)}, through {@link InternalApiBridge}.
68
     */
69
    Rule getRule() {
70
        return rule;
×
71
    }
72

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

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

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

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

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

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

149
    /**
150
     * Record a new violation of the contextual rule, at the given token location.
151
     * The position is refined using the given begin and end line numbers.
152
     * The given violation message ({@link Rule#getMessage()}) is treated
153
     * as a format string for a {@link MessageFormat} and should hence use
154
     * appropriate escapes. The given formatting arguments are used.
155
     *
156
     * @param node Location of the violation (node or token) - only used to determine suppression
157
     * @param token   Report location of the violation
158
     * @param message    Violation message
159
     * @param formatArgs Format arguments for the message
160
     * @since 7.17.0
161
     * @experimental This will probably never be stabilized, will instead be
162
     *      replaced by a fluent API or something to report violations. Do not use
163
     *      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>.
164
     */
165
    @Experimental
166
    public void addViolationWithPosition(Node node, JavaccToken token, String message, Object... formatArgs) {
167
        addViolationWithPosition(node, node.getAstInfo(), token.getReportLocation(), message, formatArgs);
×
168
    }
×
169

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

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

198
        if (suppressed != null) {
1✔
199
            listener.onSuppressedRuleViolation(suppressed);
1✔
200
        } else {
201
            listener.onRuleViolation(violation);
1✔
202
        }
203
    }
1✔
204

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

216
        Node nearestNode = getNearestNode(reportable, astInfo);
×
217
        RuleViolation violation = createViolation(reportable, astInfo, nearestNode, message, formatArgs);
×
218
        listener.onRuleViolation(violation);
×
219
    }
×
220

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

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

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

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

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

264

265
    private String expandVariables(String message, Map<String, String> extraVars) {
266

267
        if (!message.contains("${")) {
1✔
268
            return message;
1✔
269
        }
270

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

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