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

pmd / pmd / 4516

27 Mar 2025 03:42PM UTC coverage: 79.841% (+2.0%) from 77.853%
4516

push

github

adangel
[doc] Fix search index (#5618)

Merge pull request #5618 from adangel:doc/fix-search

16710 of 21943 branches covered (76.15%)

Branch coverage included in aggregate %.

34885 of 42679 relevant lines covered (81.74%)

0.91 hits per line

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

62.3
/pmd-test-schema/src/main/java/net/sourceforge/pmd/test/schema/BaseTestParserImpl.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.test.schema;
6

7
import java.util.ArrayList;
8
import java.util.Collections;
9
import java.util.HashMap;
10
import java.util.HashSet;
11
import java.util.List;
12
import java.util.Map;
13
import java.util.Properties;
14
import java.util.Set;
15
import java.util.stream.Collectors;
16

17
import org.apache.commons.lang3.StringUtils;
18
import org.w3c.dom.Attr;
19
import org.w3c.dom.Document;
20
import org.w3c.dom.Element;
21
import org.w3c.dom.Node;
22

23
import net.sourceforge.pmd.lang.Language;
24
import net.sourceforge.pmd.lang.LanguageRegistry;
25
import net.sourceforge.pmd.lang.LanguageVersion;
26
import net.sourceforge.pmd.lang.document.Chars;
27
import net.sourceforge.pmd.lang.rule.Rule;
28
import net.sourceforge.pmd.properties.PropertyDescriptor;
29
import net.sourceforge.pmd.properties.PropertySource;
30
import net.sourceforge.pmd.test.schema.TestSchemaParser.PmdXmlReporter;
31
import net.sourceforge.pmd.util.StringUtil;
32

33
import com.github.oowekyala.ooxml.DomUtils;
34
import com.github.oowekyala.ooxml.messages.PositionedXmlDoc;
35
import com.github.oowekyala.ooxml.messages.XmlPosition;
36
import com.github.oowekyala.ooxml.messages.XmlPositioner;
37

38
/**
39
 * @author Clément Fournier
40
 */
41
class BaseTestParserImpl {
1✔
42

43
    static class ParserV1 extends BaseTestParserImpl {
1✔
44

45
    }
46

47
    public RuleTestCollection parseDocument(Rule rule, PositionedXmlDoc positionedXmlDoc, PmdXmlReporter err) {
48
        Document doc = positionedXmlDoc.getDocument();
1✔
49
        Element root = doc.getDocumentElement();
1✔
50

51
        Map<String, Element> codeFragments = parseCodeFragments(err, root);
1✔
52

53
        Set<String> usedFragments = new HashSet<>();
1✔
54
        List<Element> testCodes = DomUtils.childrenNamed(root, "test-code");
1✔
55
        RuleTestCollection result = new RuleTestCollection();
1✔
56
        for (int i = 0; i < testCodes.size(); i++) {
1✔
57
            RuleTestDescriptor descriptor = new RuleTestDescriptor(i, rule.deepCopy());
1✔
58

59
            try (PmdXmlReporter errScope = err.newScope()) {
1✔
60
                parseSingleTest(testCodes.get(i), descriptor, codeFragments, usedFragments, positionedXmlDoc.getPositioner(), errScope);
1✔
61
                if (!errScope.hasError()) {
1✔
62
                    result.addTest(descriptor);
1✔
63
                }
64
            }
65
        }
66

67
        codeFragments.keySet().removeAll(usedFragments);
1✔
68
        codeFragments.forEach((id, node) -> err.at(node).warn("Unused code fragment"));
1✔
69

70
        return result;
1✔
71
    }
72

73
    private Map<String, Element> parseCodeFragments(PmdXmlReporter err, Element root) {
74
        Map<String, Element> codeFragments = new HashMap<>();
1✔
75

76
        for (Element node : DomUtils.childrenNamed(root, "code-fragment")) {
1✔
77
            Attr id = getRequiredAttribute("id", node, err);
1✔
78
            if (id == null) {
1!
79
                continue;
×
80
            }
81

82
            Element prev = codeFragments.put(id.getValue(), node);
1✔
83
            if (prev != null) {
1!
84
                err.at(prev).error("Fragment with duplicate id ''{0}'' is ignored", id.getValue());
×
85
            }
86
        }
1✔
87
        return codeFragments;
1✔
88
    }
89

90
    private void parseSingleTest(Element testCode,
91
                                 RuleTestDescriptor descriptor,
92
                                 Map<String, Element> fragments,
93
                                 Set<String> usedFragments,
94
                                 XmlPositioner xmlPositioner,
95
                                 PmdXmlReporter err) {
96
        {
97
            String description = getSingleChildText(testCode, "description", true, err);
1✔
98
            if (description == null) {
1!
99
                return;
×
100
            }
101
            descriptor.setDescription(description.trim());
1✔
102
        }
103

104
        parseBoolAttribute(testCode, "reinitializeRule", true, err, "Attribute 'reinitializeRule' is deprecated and ignored, assumed true");
1✔
105
        parseBoolAttribute(testCode, "useAuxClasspath", true, err, "Attribute 'useAuxClasspath' is deprecated and ignored, assumed true");
1✔
106

107
        boolean disabled = parseBoolAttribute(testCode, "disabled", false, err, null)
1✔
108
                          | !parseBoolAttribute(testCode, "regressionTest", true, err, "Attribute ''regressionTest'' is deprecated, use ''disabled'' with inverted value");
1✔
109

110
        descriptor.setDisabled(disabled);
1✔
111

112

113
        boolean focused = parseBoolAttribute(testCode, "focused", false, err,
1✔
114
                                             "Attribute focused is used, do not forget to remove it when checking in sources");
115

116
        descriptor.setFocused(focused);
1✔
117

118

119
        Properties properties = parseRuleProperties(testCode, descriptor.getRule(), err);
1✔
120
        descriptor.getProperties().putAll(properties);
1✔
121

122
        parseExpectedProblems(testCode, descriptor, err);
1✔
123

124
        String code = getTestCode(testCode, fragments, usedFragments, err);
1✔
125
        if (code == null) {
1!
126
            return;
×
127
        }
128
        descriptor.setCode(code);
1✔
129

130

131
        LanguageVersion lversion = parseLanguageVersion(testCode, err);
1✔
132
        if (lversion != null) {
1!
133
            descriptor.setLanguageVersion(lversion);
×
134
        }
135

136
        XmlPosition startPosition = xmlPositioner.startPositionOf(testCode);
1✔
137
        descriptor.setLineNumber(startPosition.getLine());
1✔
138
    }
1✔
139

140
    private void parseExpectedProblems(Element testCode, RuleTestDescriptor descriptor, PmdXmlReporter err) {
141
        Node expectedProblemsNode = getSingleChild(testCode, "expected-problems", true, err);
1✔
142
        if (expectedProblemsNode == null) {
1!
143
            return;
×
144
        }
145
        int expectedProblems = Integer.parseInt(parseTextNode(expectedProblemsNode));
1✔
146

147
        List<String> expectedMessages = Collections.emptyList();
1✔
148
        {
149
            Element messagesNode = getSingleChild(testCode, "expected-messages", false, err);
1✔
150
            if (messagesNode != null) {
1!
151
                expectedMessages = new ArrayList<>();
×
152
                List<Element> messageNodes = DomUtils.childrenNamed(messagesNode, "message");
×
153
                if (messageNodes.size() != expectedProblems) {
×
154
                    err.at(expectedProblemsNode).error("Number of ''expected-messages'' ({0}) does not match", messageNodes.size());
×
155
                    return;
×
156
                }
157

158
                for (Node message : messageNodes) {
×
159
                    expectedMessages.add(parseTextNode(message));
×
160
                }
×
161
            }
162
        }
163

164
        List<Integer> expectedLineNumbers = Collections.emptyList();
1✔
165
        List<Integer> expectedEndLineNumbers = Collections.emptyList();
1✔
166
        {
167
            Element lineNumbers = getSingleChild(testCode, "expected-linenumbers", false, err);
1✔
168
            if (lineNumbers != null) {
1!
169
                expectedLineNumbers = new ArrayList<>();
×
170
                expectedEndLineNumbers = new ArrayList<>();
×
171
                String[] linenos = parseTextNode(lineNumbers).split(",");
×
172
                if (linenos.length != expectedProblems) {
×
173
                    err.at(expectedProblemsNode).error("Number of ''expected-linenumbers'' ({0}) does not match", linenos.length);
×
174
                    return;
×
175
                }
176
                for (String num : linenos) {
×
177
                    if (num.contains("-")) {
×
178
                        String[] beginAndEnd = num.split("-", 2);
×
179
                        expectedLineNumbers.add(Integer.valueOf(beginAndEnd[0].trim()));
×
180
                        expectedEndLineNumbers.add(Integer.valueOf(beginAndEnd[1].trim()));
×
181
                    } else {
×
182
                        expectedLineNumbers.add(Integer.valueOf(num.trim()));
×
183
                    }
184
                }
185
            }
186
        }
187

188
        descriptor.recordExpectedViolations(
1✔
189
            expectedProblems,
190
            expectedLineNumbers,
191
            expectedEndLineNumbers,
192
            expectedMessages
193
        );
194

195
    }
1✔
196

197
    private String getTestCode(Element testCode, Map<String, Element> fragments, Set<String> usedFragments, PmdXmlReporter err) {
198
        String code = getSingleChildText(testCode, "code", false, err);
1✔
199
        if (code == null) {
1✔
200
            // Should have a coderef
201
            List<Element> coderefs = DomUtils.childrenNamed(testCode, "code-ref");
1✔
202
            if (coderefs.isEmpty()) {
1!
203
                throw new RuntimeException(
×
204
                    "Required tag is missing from the test-xml. Supply either a code or a code-ref tag");
205
            }
206
            Element coderef = coderefs.get(0);
1✔
207
            Attr id = getRequiredAttribute("id", coderef, err);
1✔
208
            if (id == null) {
1!
209
                return null;
×
210
            }
211
            Element fragment = fragments.get(id.getValue());
1✔
212
            if (fragment == null) {
1!
213
                err.at(id).error("Unknown id, known IDs are {0}", fragments.keySet());
×
214
                return null;
×
215
            }
216
            usedFragments.add(id.getValue());
1✔
217
            code = parseTextNodeNoTrim(fragment);
1✔
218
        }
219
        // first trim empty lines at beginning/end, then trim any indentation
220
        code = StringUtil.trimIndent(Chars.wrap(code).trimBlankLines()).toString();
1✔
221
        return code;
1✔
222
    }
223

224
    private LanguageVersion parseLanguageVersion(Element testCode, PmdXmlReporter err) {
225
        Node sourceTypeNode = getSingleChild(testCode, "source-type", false, err);
1✔
226
        if (sourceTypeNode == null) {
1!
227
            return null;
1✔
228
        }
229
        String languageVersionString = parseTextNode(sourceTypeNode);
×
230
        LanguageVersion languageVersion = parseSourceType(languageVersionString);
×
231
        if (languageVersion != null) {
×
232
            return languageVersion;
×
233
        }
234

235
        err.at(sourceTypeNode).error("Unknown language version ''{0}''", languageVersionString);
×
236
        return null;
×
237
    }
238

239
    /** FIXME this is stupid, the language version may be of a different language than the Rule... */
240
    private static LanguageVersion parseSourceType(String languageIdAndVersion) {
241
        final String version;
242
        final String languageId;
243
        if (languageIdAndVersion.contains(" ")) {
×
244
            version = StringUtils.trimToNull(languageIdAndVersion.substring(languageIdAndVersion.lastIndexOf(' ') + 1));
×
245
            languageId = languageIdAndVersion.substring(0, languageIdAndVersion.lastIndexOf(' '));
×
246
        } else {
247
            version = null;
×
248
            languageId = languageIdAndVersion;
×
249
        }
250
        Language language = LanguageRegistry.PMD.getLanguageById(languageId);
×
251
        if (language != null) {
×
252
            if (version == null) {
×
253
                return language.getDefaultVersion();
×
254
            } else {
255
                return language.getVersion(version);
×
256
            }
257
        }
258
        return null;
×
259
    }
260

261
    private Properties parseRuleProperties(Element testCode, PropertySource knownProps, PmdXmlReporter err) {
262
        Properties properties = new Properties();
1✔
263
        for (Element ruleProperty : DomUtils.childrenNamed(testCode, "rule-property")) {
1✔
264
            Node nameAttr = getRequiredAttribute("name", ruleProperty, err);
1✔
265
            if (nameAttr == null) {
1!
266
                continue;
×
267
            }
268
            String propertyName = nameAttr.getNodeValue();
1✔
269
            if (knownProps.getPropertyDescriptor(propertyName) == null) {
1!
270
                String knownNames = knownProps.getPropertyDescriptors().stream().map(PropertyDescriptor::name)
1✔
271
                        .collect(Collectors.joining(", "));
1✔
272
                err.at(nameAttr).error("Unknown property, known property names are {0}", knownNames);
1✔
273
                continue;
1✔
274
            }
275
            properties.setProperty(propertyName, parseTextNode(ruleProperty));
×
276
        }
×
277
        return properties;
1✔
278
    }
279

280
    private Attr getRequiredAttribute(String name, Element ruleProperty, PmdXmlReporter err) {
281
        Attr nameAttr = (Attr) ruleProperty.getAttributes().getNamedItem(name);
1✔
282
        if (nameAttr == null) {
1!
283
            err.at(ruleProperty).error("Missing ''{0}'' attribute", name);
×
284
            return null;
×
285
        }
286
        return nameAttr;
1✔
287
    }
288

289
    private boolean parseBoolAttribute(Element testCode, String attrName, boolean defaultValue, PmdXmlReporter err, String deprecationMessage) {
290
        Attr attrNode = testCode.getAttributeNode(attrName);
1✔
291
        if (attrNode != null) {
1✔
292
            if (deprecationMessage != null) {
1!
293
                err.at(attrNode).warn(deprecationMessage);
1✔
294
            }
295
            return Boolean.parseBoolean(attrNode.getNodeValue());
1✔
296
        }
297
        return defaultValue;
1✔
298
    }
299

300

301
    private String getSingleChildText(Element parentElm, String nodeName, boolean required, PmdXmlReporter err) {
302
        Node node = getSingleChild(parentElm, nodeName, required, err);
1✔
303
        if (node == null) {
1✔
304
            return null;
1✔
305
        }
306
        return parseTextNodeNoTrim(node);
1✔
307
    }
308

309
    private Element getSingleChild(Element parentElm, String nodeName, boolean required, PmdXmlReporter err) {
310
        List<Element> nodes = DomUtils.childrenNamed(parentElm, nodeName);
1✔
311
        if (nodes.isEmpty()) {
1✔
312
            if (required) {
1!
313
                err.at(parentElm).error("Required child ''{0}'' is missing", nodeName);
×
314
            }
315
            return null;
1✔
316
        } else if (nodes.size() > 1) {
1!
317
            err.at(nodes.get(1)).error("Duplicate tag ''{0}'' is ignored", nodeName);
×
318
        }
319
        return nodes.get(0);
1✔
320
    }
321

322
    private static String parseTextNode(Node exampleNode) {
323
        return parseTextNodeNoTrim(exampleNode).trim();
1✔
324
    }
325

326
    private static String parseTextNodeNoTrim(Node exampleNode) {
327
        StringBuilder buffer = new StringBuilder();
1✔
328
        for (int i = 0; i < exampleNode.getChildNodes().getLength(); i++) {
1✔
329
            Node node = exampleNode.getChildNodes().item(i);
1✔
330
            if (node.getNodeType() == Node.CDATA_SECTION_NODE || node.getNodeType() == Node.TEXT_NODE) {
1!
331
                buffer.append(node.getNodeValue());
1✔
332
            }
333
        }
334
        return buffer.toString();
1✔
335
    }
336

337

338
}
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