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

pmd / pmd / 22

30 May 2025 01:19PM UTC coverage: 78.601% (+0.8%) from 77.795%
22

push

github

adangel
[core] Reformat SarifLog to comply to coding standards (#5748)

Merge pull request #5748 from adangel:core/cleanup-sariflog

17714 of 23362 branches covered (75.82%)

Branch coverage included in aggregate %.

34 of 104 new or added lines in 1 file covered. (32.69%)

217 existing lines in 2 files now uncovered.

38811 of 48552 relevant lines covered (79.94%)

0.81 hits per line

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

81.48
/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/visualforce/ast/ObjectFieldTypes.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.visualforce.ast;
6

7
import java.io.IOException;
8
import java.lang.reflect.Field;
9
import java.nio.file.Files;
10
import java.nio.file.Path;
11
import java.nio.file.Paths;
12
import java.util.Collections;
13
import java.util.HashMap;
14
import java.util.HashSet;
15
import java.util.List;
16
import java.util.Locale;
17
import java.util.Map;
18
import java.util.Set;
19
import java.util.function.Function;
20
import java.util.stream.Collectors;
21
import javax.xml.parsers.DocumentBuilder;
22
import javax.xml.parsers.DocumentBuilderFactory;
23
import javax.xml.parsers.ParserConfigurationException;
24
import javax.xml.xpath.XPath;
25
import javax.xml.xpath.XPathConstants;
26
import javax.xml.xpath.XPathExpression;
27
import javax.xml.xpath.XPathExpressionException;
28
import javax.xml.xpath.XPathFactory;
29

30
import org.apache.commons.lang3.exception.ContextedRuntimeException;
31
import org.slf4j.Logger;
32
import org.slf4j.LoggerFactory;
33
import org.w3c.dom.Document;
34
import org.w3c.dom.Node;
35
import org.w3c.dom.NodeList;
36
import org.xml.sax.SAXException;
37

38
import net.sourceforge.pmd.lang.visualforce.DataType;
39

40
import com.google.common.reflect.ClassPath;
41

42
/**
43
 * Responsible for storing a mapping of Fields that can be referenced from Visualforce to the type of the field.
44
 *
45
 * <p>SFDX and MDAPI project formats are supported.
46
 * @see <a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_source_file_format.htm">Salesforce DX Project Structure and Source Format</a>
47
 */
48
class ObjectFieldTypes extends SalesforceFieldTypes {
49
    private static final Logger LOG = LoggerFactory.getLogger(ObjectFieldTypes.class);
1✔
50

51
    public static final String CUSTOM_OBJECT_SUFFIX = "__c";
52
    private static final String FIELDS_DIRECTORY = "fields";
53
    private static final String MDAPI_OBJECT_FILE_SUFFIX = ".object";
54
    private static final String SFDX_FIELD_FILE_SUFFIX = ".field-meta.xml";
55

56
    private static final Map<String, DataType> SYSTEM_FIELDS;
57
    private static final Map<String, ClassPath.ClassInfo> SOBJECTS;
58

59
    static {
60
        // see https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/system_fields.htm
61
        SYSTEM_FIELDS = new HashMap<>();
1✔
62
        SYSTEM_FIELDS.put("id", DataType.Lookup);
1✔
63
        SYSTEM_FIELDS.put("isdeleted", DataType.Checkbox);
1✔
64
        SYSTEM_FIELDS.put("createdbyid", DataType.Lookup);
1✔
65
        SYSTEM_FIELDS.put("createddate", DataType.DateTime);
1✔
66
        SYSTEM_FIELDS.put("lastmodifiedbyid", DataType.Lookup);
1✔
67
        SYSTEM_FIELDS.put("lastmodifieddate", DataType.DateTime);
1✔
68
        SYSTEM_FIELDS.put("systemmodstamp", DataType.DateTime);
1✔
69
        // name is not defined as systemfield, but might occur frequently
70
        SYSTEM_FIELDS.put("name", DataType.Text);
1✔
71

72
        try {
73
            // see https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_list.htm
74
            // and https://github.com/apex-dev-tools/sobject-types
75
            SOBJECTS = Collections.unmodifiableMap(
1✔
76
                    ClassPath.from(ClassLoader.getSystemClassLoader())
1✔
77
                            .getTopLevelClasses(com.nawforce.runforce.SObjects.Account.class.getPackage().getName())
1✔
78
                            .stream()
1✔
79
                            .collect(Collectors.toMap(c -> c.getSimpleName().toLowerCase(Locale.ROOT),
1✔
80
                                    Function.identity()))
1✔
81
            );
UNCOV
82
        } catch (IOException e) {
×
UNCOV
83
            throw new RuntimeException(e);
×
84
        }
1✔
85
    }
1✔
86

87
    /**
88
     * Keep track of which ".object" files have already been processed. All fields are processed at once. If an object
89
     * file has been processed
90
     */
91
    private final Set<String> objectFileProcessed;
92

93
    // XML Parsing objects
94
    private final DocumentBuilder documentBuilder;
95
    private final XPathExpression customObjectFieldsExpression;
96
    private final XPathExpression customFieldFullNameExpression;
97
    private final XPathExpression customFieldTypeExpression;
98
    private final XPathExpression sfdxCustomFieldFullNameExpression;
99
    private final XPathExpression sfdxCustomFieldTypeExpression;
100

101
    ObjectFieldTypes() {
1✔
102
        this.objectFileProcessed = new HashSet<>();
1✔
103

104
        try {
105
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
1✔
106
            documentBuilderFactory.setNamespaceAware(false);
1✔
107
            documentBuilderFactory.setValidating(false);
1✔
108
            documentBuilderFactory.setIgnoringComments(true);
1✔
109
            documentBuilderFactory.setIgnoringElementContentWhitespace(true);
1✔
110
            documentBuilderFactory.setExpandEntityReferences(false);
1✔
111
            documentBuilderFactory.setCoalescing(false);
1✔
112
            documentBuilderFactory.setXIncludeAware(false);
1✔
113
            documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
1✔
114
            documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
1✔
115
            documentBuilder = documentBuilderFactory.newDocumentBuilder();
1✔
UNCOV
116
        } catch (ParserConfigurationException e) {
×
UNCOV
117
            throw new RuntimeException(e);
×
118
        }
1✔
119

120
        try {
121
            XPath xPath = XPathFactory.newInstance().newXPath();
1✔
122
            this.customObjectFieldsExpression = xPath.compile("/CustomObject/fields");
1✔
123
            this.customFieldFullNameExpression = xPath.compile("fullName/text()");
1✔
124
            this.customFieldTypeExpression = xPath.compile("type/text()");
1✔
125
            this.sfdxCustomFieldFullNameExpression = xPath.compile("/CustomField/fullName/text()");
1✔
126
            this.sfdxCustomFieldTypeExpression = xPath.compile("/CustomField/type/text()");
1✔
UNCOV
127
        } catch (XPathExpressionException e) {
×
UNCOV
128
            throw new RuntimeException(e);
×
129
        }
1✔
130
    }
1✔
131

132
    /**
133
     * Looks in {@code objectsDirectories} for a custom field identified by {@code expression}.
134
     */
135
    @Override
136
    protected void findDataType(String expression, List<Path> objectsDirectories) {
137
        // The expression should be in the form <objectName>.<fieldName>
138
        String[] parts = expression.split("\\.");
1✔
139
        if (parts.length == 1) {
1!
UNCOV
140
            throw new RuntimeException("Malformed identifier: " + expression);
×
141
        } else if (parts.length == 2) {
1!
142
            String objectName = parts[0];
1✔
143
            String fieldName = parts[1];
1✔
144

145
            addSystemFields(objectName);
1✔
146

147
            // Attempt to find a metadata file that contains the custom field. The information will be located in a
148
            // file located at <objectDirectory>/<objectName>.object or in an file located at
149
            // <objectDirectory>/<objectName>/fields/<fieldName>.field-meta.xml. The list of object directories
150
            // defaults to the [<vfFileName>/../objects] but can be overridden by the user.
151
            for (Path objectsDirectory : objectsDirectories) {
1✔
152
                Path sfdxCustomFieldPath = getSfdxCustomFieldPath(objectsDirectory, objectName, fieldName);
1✔
153
                if (sfdxCustomFieldPath != null) {
1✔
154
                    // SFDX Format
155
                    parseSfdxCustomField(objectName, sfdxCustomFieldPath);
1✔
156
                } else {
157
                    // MDAPI Format
158
                    String fileName = objectName + MDAPI_OBJECT_FILE_SUFFIX;
1✔
159
                    Path mdapiPath = objectsDirectory.resolve(fileName);
1✔
160
                    if (Files.exists(mdapiPath) && Files.isRegularFile(mdapiPath)) {
1!
161
                        parseMdapiCustomObject(mdapiPath);
1✔
162
                    }
163
                }
164

165
                if (containsExpression(expression)) {
1✔
166
                    // Break out of the loop if a variable was found
167
                    break;
1✔
168
                }
169
            }
1✔
170
        } else {
1✔
171
            // TODO: Support cross object relationships, these are expressions that contain "__r"
UNCOV
172
            LOG.debug("Expression does not have two parts: {}", expression);
×
173
        }
174
    }
1✔
175

176
    /**
177
     * Sfdx projects decompose custom fields into individual files. This method will return the individual file that
178
     * corresponds to &lt;objectName&gt;.&lt;fieldName&gt; if it exists.
179
     *
180
     * <p>Note: these metadata files are created not only for custom fields, but also for standard fields that
181
     * are used within the project. The metadata of these standard fields (fields of standard objects) provide
182
     * less explicit information. E.g. the type is not available the metadata file for these standard fields.
183
     *
184
     * @return path to the metadata file for the Custom Field or null if not found
185
     */
186
    private Path getSfdxCustomFieldPath(Path objectsDirectory, String objectName, String fieldName) {
187
        Path fieldsDirectoryPath = Paths.get(objectsDirectory.toString(), objectName, FIELDS_DIRECTORY);
1✔
188
        if (Files.exists(fieldsDirectoryPath) && Files.isDirectory(fieldsDirectoryPath)) {
1!
189
            Path sfdxFieldPath = Paths.get(fieldsDirectoryPath.toString(), fieldName + SFDX_FIELD_FILE_SUFFIX);
1✔
190
            if (Files.exists(sfdxFieldPath) && Files.isRegularFile(sfdxFieldPath)) {
1!
191
                return sfdxFieldPath;
1✔
192
            }
193
        }
194
        return null;
1✔
195
    }
196

197
    /**
198
     * Determine the type of the custom field.
199
     */
200
    private void parseSfdxCustomField(String customObjectName, Path sfdxCustomFieldPath) {
201
        try {
202
            Document document = documentBuilder.parse(sfdxCustomFieldPath.toFile());
1✔
203
            Node fullNameNode = (Node) sfdxCustomFieldFullNameExpression.evaluate(document, XPathConstants.NODE);
1✔
204
            Node typeNode = (Node) sfdxCustomFieldTypeExpression.evaluate(document, XPathConstants.NODE);
1✔
205

206
            DataType dataType = null;
1✔
207

208
            if (typeNode != null) {
1✔
209
                // custom field with a defined type
210
                String type = typeNode.getNodeValue();
1✔
211
                dataType = DataType.fromString(type);
1✔
212
            } else {
1✔
213
                // maybe a field from a standard object - the type is then not explicitly in field-meta.xml provided
214
                ClassPath.ClassInfo classInfo = SOBJECTS.get(customObjectName.toLowerCase(Locale.ROOT));
1✔
215
                if (classInfo != null) {
1!
216
                    Field[] fields = classInfo.load().getFields();
1✔
217
                    for (Field f : fields) {
1!
218
                        if (f.getName().equalsIgnoreCase(fullNameNode.getNodeValue())) {
1✔
219
                            dataType = DataType.fromTypeName(f.getType().getSimpleName());
1✔
220
                            break;
1✔
221
                        }
222
                    }
223
                    if (dataType == null) {
1!
UNCOV
224
                        LOG.warn("Couldn't determine data type of customObjectName={} from {}", customObjectName, sfdxCustomFieldPath);
×
UNCOV
225
                        dataType = DataType.Unknown;
×
226
                    }
227
                } else {
1✔
UNCOV
228
                    LOG.warn("Couldn't determine data type of customObjectName={} - no sobject definition found", customObjectName);
×
UNCOV
229
                    dataType = DataType.Unknown;
×
230
                }
231
            }
232

233
            String key = customObjectName + "." + fullNameNode.getNodeValue();
1✔
234
            putDataType(key, dataType);
1✔
UNCOV
235
        } catch (IOException | SAXException | XPathExpressionException e) {
×
UNCOV
236
            throw new ContextedRuntimeException(e)
×
UNCOV
237
                    .addContextValue("customObjectName", customObjectName)
×
UNCOV
238
                    .addContextValue("sfdxCustomFieldPath", sfdxCustomFieldPath);
×
239
        }
1✔
240
    }
1✔
241

242
    /**
243
     * Parse the custom object path and determine the type of all of its custom fields.
244
     */
245
    private void parseMdapiCustomObject(Path mdapiObjectFile) {
246
        String fileName = mdapiObjectFile.getFileName().toString();
1✔
247

248
        String customObjectName = fileName.substring(0, fileName.lastIndexOf(MDAPI_OBJECT_FILE_SUFFIX));
1✔
249
        if (!objectFileProcessed.contains(customObjectName)) {
1✔
250
            try {
251
                Document document = documentBuilder.parse(mdapiObjectFile.toFile());
1✔
252
                NodeList fieldsNodes = (NodeList) customObjectFieldsExpression.evaluate(document, XPathConstants.NODESET);
1✔
253
                for (int i = 0; i < fieldsNodes.getLength(); i++) {
1✔
254
                    Node fieldsNode = fieldsNodes.item(i);
1✔
255
                    Node fullNameNode = (Node) customFieldFullNameExpression.evaluate(fieldsNode, XPathConstants.NODE);
1✔
256
                    if (fullNameNode == null) {
1!
UNCOV
257
                        throw new RuntimeException("fullName evaluate failed for " + customObjectName + " " + fieldsNode.getTextContent());
×
258
                    }
259
                    String name = fullNameNode.getNodeValue();
1✔
260
                    if (endsWithIgnoreCase(name, CUSTOM_OBJECT_SUFFIX)) {
1✔
261
                        Node typeNode = (Node) customFieldTypeExpression.evaluate(fieldsNode, XPathConstants.NODE);
1✔
262
                        if (typeNode == null) {
1!
UNCOV
263
                            throw new RuntimeException("type evaluate failed for object=" + customObjectName + ", field=" + name + " " + fieldsNode.getTextContent());
×
264
                        }
265
                        String type = typeNode.getNodeValue();
1✔
266
                        DataType dataType = DataType.fromString(type);
1✔
267
                        String key = customObjectName + "." + fullNameNode.getNodeValue();
1✔
268
                        putDataType(key, dataType);
1✔
269
                    }
270
                }
UNCOV
271
            } catch (IOException | SAXException | XPathExpressionException e) {
×
UNCOV
272
                throw new ContextedRuntimeException(e)
×
UNCOV
273
                        .addContextValue("customObjectName", customObjectName)
×
UNCOV
274
                        .addContextValue("mdapiObjectFile", mdapiObjectFile);
×
275
            }
1✔
276
            objectFileProcessed.add(customObjectName);
1✔
277
        }
278
    }
1✔
279

280
    /**
281
     * Add the set of system fields which aren't present in the metadata file, but may be referenced from the
282
     * visualforce page.
283
     *
284
     * @see <a href="https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/system_fields.htm">Overview of Salesforce Objects and Fields / System Fields</a>
285
     */
286
    private void addSystemFields(String customObjectName) {
287
        for (Map.Entry<String, DataType> entry : SYSTEM_FIELDS.entrySet()) {
1✔
288
            putDataType(customObjectName + "." + entry.getKey(), entry.getValue());
1✔
289
        }
1✔
290
    }
1✔
291

292
    /**
293
     * Null safe endsWithIgnoreCase
294
     */
295
    private boolean endsWithIgnoreCase(String str, String suffix) {
296
        return str != null && str.toLowerCase(Locale.ROOT).endsWith(suffix.toLowerCase(Locale.ROOT));
1!
297
    }
298

299
    @Override
300
    protected DataType putDataType(String name, DataType dataType) {
301
        DataType previousType = super.putDataType(name, dataType);
1✔
302
        if (previousType != null && !previousType.equals(dataType)) {
1!
303
            // It should not be possible to have conflicting types for CustomFields
UNCOV
304
            throw new RuntimeException("Conflicting types for "
×
305
                    + name
306
                    + ". CurrentType="
307
                    + dataType
308
                    + ", PreviousType="
309
                    + previousType);
310
        }
311
        return previousType;
1✔
312
    }
313
}
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