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

pmd / pmd / 415

27 Feb 2026 12:39PM UTC coverage: 79.038% (+0.03%) from 79.004%
415

push

github

adangel
[release] Prepare next development version

18604 of 24437 branches covered (76.13%)

Branch coverage included in aggregate %.

40598 of 50466 relevant lines covered (80.45%)

0.81 hits per line

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

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

5
package net.sourceforge.pmd.renderers;
6

7
import java.io.IOException;
8
import java.io.PrintWriter;
9
import java.io.Writer;
10
import java.net.URLEncoder;
11
import java.util.Iterator;
12
import java.util.List;
13
import java.util.Optional;
14

15
import org.apache.commons.lang3.StringEscapeUtils;
16
import org.apache.commons.lang3.StringUtils;
17

18
import net.sourceforge.pmd.lang.document.FileId;
19
import net.sourceforge.pmd.lang.rule.Rule;
20
import net.sourceforge.pmd.properties.PropertyDescriptor;
21
import net.sourceforge.pmd.properties.PropertyFactory;
22
import net.sourceforge.pmd.reporting.Report;
23
import net.sourceforge.pmd.reporting.Report.ConfigurationError;
24
import net.sourceforge.pmd.reporting.RuleViolation;
25

26
/**
27
 * Renderer to basic HTML format.
28
 *
29
 * FIXME: this class should just work with the XMLRenderer and then apply an
30
 * XSLT transformation + stylesheet. No need to hard-code HTML markup here.
31
 */
32
public class HTMLRenderer extends AbstractIncrementingRenderer {
33

34
    public static final String NAME = "html";
35

36
    public static final PropertyDescriptor<Optional<String>> LINE_PREFIX =
1✔
37
        PropertyFactory.stringProperty("linePrefix")
1✔
38
                       .desc("Prefix for line number anchor in the source file.")
1✔
39
                       .toOptional("<none>")
1✔
40
                       .defaultValue(Optional.empty())
1✔
41
                       .build();
1✔
42

43
    public static final PropertyDescriptor<String> LINK_PREFIX =
1✔
44
        PropertyFactory.stringProperty("linkPrefix").desc("Path to HTML source.").defaultValue("").build();
1✔
45

46
    public static final PropertyDescriptor<Boolean> HTML_EXTENSION =
1✔
47
        PropertyFactory.booleanProperty("htmlExtension")
1✔
48
                       .desc("Replace file extension with .html for the links.")
1✔
49
                       // default value is false - to have the old (pre 6.23.0) behavior, this needs to be set to true.
50
                       .defaultValue(false)
1✔
51
                       .build();
1✔
52

53
    private String linkPrefix;
54
    private String linePrefix;
55
    private boolean replaceHtmlExtension;
56

57
    boolean colorize = true;
1✔
58

59
    public HTMLRenderer() {
60
        super(NAME, "HTML format");
1✔
61

62
        definePropertyDescriptor(LINK_PREFIX);
1✔
63
        definePropertyDescriptor(LINE_PREFIX);
1✔
64
        definePropertyDescriptor(HTML_EXTENSION);
1✔
65
    }
1✔
66

67
    @Override
68
    public String defaultFileExtension() {
69
        return "html";
×
70
    }
71

72
    private static String escape(String s) {
73
        return StringEscapeUtils.escapeHtml4(s);
1✔
74
    }
75

76
    /**
77
     * Write the body of the main body of the HTML content.
78
     */
79
    public void renderBody(PrintWriter writer, Report report) throws IOException {
80
        linkPrefix = getProperty(LINK_PREFIX);
1✔
81
        linePrefix = getProperty(LINE_PREFIX).orElse(null);
1✔
82
        replaceHtmlExtension = getProperty(HTML_EXTENSION);
1✔
83

84
        writer.write("<center><h3>PMD report</h3></center>");
1✔
85
        writer.write("<center><h3>Problems found</h3></center>");
1✔
86
        writer.println("<table align=\"center\" cellspacing=\"0\" cellpadding=\"3\"><tr>");
1✔
87
        writer.println("<th>#</th><th>File</th><th>Line</th><th>Problem</th></tr>");
1✔
88
        setWriter(writer);
1✔
89
        renderFileReport(report);
1✔
90
        writer.write("</table>");
1✔
91
        glomProcessingErrors(writer, errors);
1✔
92
        if (showSuppressedViolations) {
1✔
93
            glomSuppressions(writer, suppressed);
1✔
94
        }
95
        glomConfigurationErrors(writer, configErrors);
1✔
96
    }
1✔
97

98
    @Override
99
    public void start() throws IOException {
100
        linkPrefix = getProperty(LINK_PREFIX);
1✔
101
        linePrefix = getProperty(LINE_PREFIX).orElse(null);
1✔
102
        replaceHtmlExtension = getProperty(HTML_EXTENSION);
1✔
103

104
        writer.println("<html><head><title>PMD</title></head><body>");
1✔
105
        writer.write("<center><h3>PMD report</h3></center>");
1✔
106
        writer.write("<center><h3>Problems found</h3></center>");
1✔
107
        writer.println("<table align=\"center\" cellspacing=\"0\" cellpadding=\"3\"><tr>");
1✔
108
        writer.println("<th>#</th><th>File</th><th>Line</th><th>Problem</th></tr>");
1✔
109
    }
1✔
110

111
    @Override
112
    public void renderFileViolations(Iterator<RuleViolation> violations) throws IOException {
113
        glomRuleViolations(writer, violations);
1✔
114
    }
1✔
115

116
    @Override
117
    public void end() throws IOException {
118
        writer.write("</table>");
1✔
119
        glomProcessingErrors(writer, errors);
1✔
120
        if (showSuppressedViolations) {
1!
121
            glomSuppressions(writer, suppressed);
1✔
122
        }
123
        glomConfigurationErrors(writer, configErrors);
1✔
124
        writer.println("</body></html>");
1✔
125
    }
1✔
126

127
    private void glomRuleViolations(Writer writer, Iterator<RuleViolation> violations) throws IOException {
128
        int violationCount = 1;
1✔
129

130
        StringBuilder buf = new StringBuilder(500);
1✔
131

132
        while (violations.hasNext()) {
1✔
133
            RuleViolation rv = violations.next();
1✔
134
            buf.setLength(0);
1✔
135
            buf.append("<tr");
1✔
136
            if (colorize) {
1✔
137
                buf.append(" bgcolor=\"lightgrey\"");
1✔
138
            }
139
            colorize = !colorize;
1✔
140
            buf.append("> ").append(System.lineSeparator());
1✔
141
            buf.append("<td align=\"center\">").append(violationCount).append("</td>").append(System.lineSeparator());
1✔
142
            buf.append("<td width=\"*%\">")
1✔
143
               .append(renderFileNameEscaped(rv.getFileId(), rv.getBeginLine()))
1✔
144
               .append("</td>")
1✔
145
                .append(System.lineSeparator());
1✔
146
            buf.append("<td align=\"center\" width=\"5%\">").append(rv.getBeginLine()).append("</td>").append(System.lineSeparator());
1✔
147

148
            String d = escape(rv.getDescription());
1✔
149

150
            String infoUrl = rv.getRule().getExternalInfoUrl();
1✔
151
            if (StringUtils.isNotBlank(infoUrl)) {
1!
152
                d = "<a href=\"" + URLEncoder.encode(infoUrl, "UTF-8") + "\">" + d + "</a>";
×
153
            }
154
            buf.append("<td width=\"*\">")
1✔
155
               .append(d)
1✔
156
               .append("</td>")
1✔
157
               .append(System.lineSeparator())
1✔
158
               .append("</tr>")
1✔
159
                .append(System.lineSeparator());
1✔
160
            writer.write(buf.toString());
1✔
161
            violationCount++;
1✔
162
        }
1✔
163
    }
1✔
164

165
    private String renderFileNameEscaped(FileId fileId, int beginLine) {
166
        return maybeWrap(escape(determineFileName(fileId)),
1✔
167
                linePrefix == null || beginLine < 0 ? "" : linePrefix + beginLine);
1✔
168
    }
169

170
    private String renderRuleNameEscaped(Rule rule) {
171
        String name = escape(rule.getName());
1✔
172
        String infoUrl = rule.getExternalInfoUrl();
1✔
173
        if (StringUtils.isNotBlank(infoUrl)) {
1!
174
            return "<a href=\"" + infoUrl + "\">" + name + "</a>";
×
175
        }
176
        return name;
1✔
177
    }
178

179
    private void glomProcessingErrors(PrintWriter writer, List<Report.ProcessingError> errors) throws IOException {
180

181
        if (errors.isEmpty()) {
1✔
182
            return;
1✔
183
        }
184

185
        writer.write("<hr/>");
1✔
186
        writer.write("<center><h3>Processing errors</h3></center>");
1✔
187
        writer.println("<table align=\"center\" cellspacing=\"0\" cellpadding=\"3\"><tr>");
1✔
188
        writer.println("<th>File</th><th>Problem</th></tr>");
1✔
189

190
        StringBuilder buf = new StringBuilder(500);
1✔
191
        boolean colorize = true;
1✔
192
        for (Report.ProcessingError pe : errors) {
1✔
193
            buf.setLength(0);
1✔
194
            buf.append("<tr");
1✔
195
            if (colorize) {
1!
196
                buf.append(" bgcolor=\"lightgrey\"");
1✔
197
            }
198
            colorize = !colorize;
1!
199
            buf.append("> ").append(System.lineSeparator());
1✔
200
            buf.append("<td>").append(renderFileNameEscaped(pe.getFileId(), -1)).append("</td>").append(System.lineSeparator());
1✔
201
            buf.append("<td><pre>").append(escape(pe.getDetail())).append("</pre></td>").append(System.lineSeparator());
1✔
202
            buf.append("</tr>").append(System.lineSeparator());
1✔
203
            writer.write(buf.toString());
1✔
204
        }
1✔
205
        writer.write("</table>");
1✔
206
    }
1✔
207

208
    private void glomSuppressions(PrintWriter writer, List<Report.SuppressedViolation> suppressed) throws IOException {
209
        if (suppressed.isEmpty()) {
1✔
210
            return;
1✔
211
        }
212

213
        writer.write("<hr/>");
1✔
214
        writer.write("<center><h3>Suppressed warnings</h3></center>");
1✔
215
        writer.println("<table align=\"center\" cellspacing=\"0\" cellpadding=\"3\"><tr>");
1✔
216
        writer.println("<th>File</th><th>Line</th><th>Rule</th><th>NOPMD or Annotation</th><th>Reason</th></tr>");
1✔
217

218
        StringBuilder buf = new StringBuilder(500);
1✔
219
        boolean colorize = true;
1✔
220
        for (Report.SuppressedViolation sv : suppressed) {
1✔
221
            buf.setLength(0);
1✔
222
            buf.append("<tr");
1✔
223
            if (colorize) {
1!
224
                buf.append(" bgcolor=\"lightgrey\"");
1✔
225
            }
226
            colorize = !colorize;
1!
227
            buf.append("> ").append(System.lineSeparator());
1✔
228
            RuleViolation rv = sv.getRuleViolation();
1✔
229
            String userMessage = Optional.ofNullable(sv.getUserMessage()).orElse("");
1✔
230
            buf.append("<td align=\"left\">").append(renderFileNameEscaped(rv.getFileId(), rv.getBeginLine())).append("</td>").append(System.lineSeparator());
1✔
231
            buf.append("<td align=\"center\">").append(rv.getBeginLine()).append("</td>").append(System.lineSeparator());
1✔
232
            buf.append("<td align=\"center\">").append(renderRuleNameEscaped(rv.getRule())).append("</td>").append(System.lineSeparator());
1✔
233
            buf.append("<td align=\"center\">").append(escape(sv.getSuppressor().getId())).append("</td>").append(System.lineSeparator());
1✔
234
            buf.append("<td align=\"center\">").append(escape(userMessage)).append("</td>").append(System.lineSeparator());
1✔
235
            buf.append("</tr>").append(System.lineSeparator());
1✔
236
            writer.write(buf.toString());
1✔
237
        }
1✔
238
        writer.write("</table>");
1✔
239
    }
1✔
240

241
    private void glomConfigurationErrors(final PrintWriter writer, final List<ConfigurationError> configErrors) throws IOException {
242
        if (configErrors.isEmpty()) {
1✔
243
            return;
1✔
244
        }
245

246
        writer.write("<hr/>");
1✔
247
        writer.write("<center><h3>Configuration errors</h3></center>");
1✔
248
        writer.println("<table align=\"center\" cellspacing=\"0\" cellpadding=\"3\"><tr>");
1✔
249
        writer.println("<th>Rule</th><th>Problem</th></tr>");
1✔
250

251
        StringBuilder buf = new StringBuilder(500);
1✔
252
        boolean colorize = true;
1✔
253
        for (Report.ConfigurationError ce : configErrors) {
1✔
254
            buf.setLength(0);
1✔
255
            buf.append("<tr");
1✔
256
            if (colorize) {
1!
257
                buf.append(" bgcolor=\"lightgrey\"");
1✔
258
            }
259
            colorize = !colorize;
1!
260
            buf.append("> ").append(System.lineSeparator());
1✔
261
            buf.append("<td>").append(renderRuleNameEscaped(ce.rule())).append("</td>").append(System.lineSeparator());
1✔
262
            buf.append("<td>").append(escape(ce.issue())).append("</td>").append(System.lineSeparator());
1✔
263
            buf.append("</tr>").append(System.lineSeparator());
1✔
264
            writer.write(buf.toString());
1✔
265
        }
1✔
266
        writer.write("</table>");
1✔
267
    }
1✔
268

269
    private String maybeWrap(String filenameEscaped, String line) {
270
        if (StringUtils.isBlank(linkPrefix)) {
1✔
271
            return filenameEscaped;
1✔
272
        }
273
        String newFileName = filenameEscaped.replace('\\', '/');
1✔
274

275
        if (replaceHtmlExtension) {
1✔
276
            int index = filenameEscaped.lastIndexOf('.');
1✔
277
            if (index >= 0) {
1!
278
                newFileName = filenameEscaped.substring(0, index);
×
279
            }
280
        }
281

282
        return "<a href=\"" + linkPrefix + newFileName + (replaceHtmlExtension ? ".html#" : "#") + line + "\">"
1✔
283
            + newFileName + "</a>";
284
    }
285
}
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