<?xml version="1.0" encoding="UTF-8"?>

<ruleset name="Performance"
         xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">

    <description>
Rules that flag suboptimal code.
    </description>

    <rule name="AvoidDebugStatements"
          language="apex"
          since="6.36.0"
          message="Avoid debug statements since they impact on performance"
          class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
          externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#avoiddebugstatements">
        <description>
Debug statements contribute to longer transactions and consume Apex CPU time even when debug logs are not being captured.

When possible make use of other debugging techniques such as the Apex Replay Debugger and Checkpoints that could cover *most* use cases.

For other valid use cases that the statement is in fact valid make use of the `@SuppressWarnings` annotation or the `//NOPMD` comment.
        </description>
        <priority>3</priority>
        <properties>
            <property name="xpath">
                <value>
<![CDATA[
//MethodCallExpression[lower-case(@FullMethodName)='system.debug']
]]>
                </value>
            </property>
        </properties>
        <example>
<![CDATA[
public class Foo {
    public void bar() {
        Account acc = [SELECT Name, Owner.Name FROM Account LIMIT 1];
        System.debug(accs); // will get reported
    }

    @SuppressWarnings('PMD.AvoidDebugStatements')
    public void baz() {
        try {
            Account myAccount = bar();
        } catch (Exception e) {
            System.debug(LoggingLevel.ERROR, e.getMessage()); // good to go
        }
    }
}
]]>
        </example>
    </rule>

    <rule name="AvoidNonRestrictiveQueries"
          language="apex"
          since="7.4.0"
          message="Avoid {0} queries without a where or limit statement"
          class="net.sourceforge.pmd.lang.apex.rule.performance.AvoidNonRestrictiveQueriesRule"
          externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#avoidnonrestrictivequeries">
        <description>
            When working with very large amounts of data, unfiltered SOQL or SOSL queries can quickly cause
            [governor limit](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm)
            exceptions.
        </description>
        <priority>3</priority>
        <example>
            <![CDATA[
public class Something {
    public static void main( String[] as ) {
        Account[] accs1 = [ select id from account ];  // Bad
        Account[] accs2 = [ select id from account limit 10 ];  // better

        List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; // bad
    }
}
]]>
        </example>
    </rule>

    <rule name="EagerlyLoadedDescribeSObjectResult"
          language="apex"
          since="6.40.0"
          message="DescribeSObjectResult could be being loaded eagerly with all child relationships."
          class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
          externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#eagerlyloadeddescribesobjectresult">
        <description>
This rule finds `DescribeSObjectResult`s which could have been loaded eagerly via `SObjectType.getDescribe()`.

When using `SObjectType.getDescribe()` or `Schema.describeSObjects()` without supplying a `SObjectDescribeOptions`,
implicitly it will be using `SObjectDescribeOptions.DEFAULT` and then all
child relationships will be loaded eagerly regardless whether this information is needed or not.
This has a potential negative performance impact. Instead [`SObjectType.getDescribe(options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_Schema_SObjectType.htm#unique_346834793)
or [`Schema.describeSObjects(SObjectTypes, options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_describeSObjects)
should be used and a `SObjectDescribeOptions` should be supplied. By using
`SObjectDescribeOptions.DEFERRED` the describe attributes will be lazily initialized at first use.

Lazy loading `DescribeSObjectResult` on picklist fields is not always recommended. The lazy loaded
describe objects might not be 100% accurate. It might be safer to explicitly use
`SObjectDescribeOptions.FULL` in such a case. The same applies when you need the same `DescribeSObjectResult`
to be consistent across different contexts and API versions.

Properties:

* `noDefault`: The behavior of `SObjectDescribeOptions.DEFAULT` changes from API Version 43 to 44:
    With API Version 43, the attributes are loaded eagerly. With API Version 44, they are loaded lazily.
    Simply using `SObjectDescribeOptions.DEFAULT` doesn't automatically make use of lazy loading.
    (unless "Use Improved Schema Caching" critical update is applied, `SObjectDescribeOptions.DEFAULT` does fallback
    to lazy loading)
    With this property enabled, such usages are found.
    You might ignore this, if you can make sure, that you don't run a mix of API Versions.
        </description>
        <priority>3</priority>
        <properties>
            <property name="noDefault" type="Boolean" value="false" description="Do not allow SObjectDescribeOptions.DEFAULT option to ensure consistent results no matter where getDescribe is called"/>
            <property name="xpath">
                <value>
<![CDATA[
//MethodCallExpression
    [
        lower-case(@MethodName) = "getdescribe" and ReferenceExpression[@SObjectType = true()]
        or lower-case(@MethodName) = "describesobjects"
    ]
    [not(VariableExpression/ReferenceExpression
            [lower-case(@Image) = ("sobjectdescribeoptions", "fielddescribeoptions")]
         )
    ]
|
//ReferenceExpression
    [$noDefault = true()]
    [lower-case(@Image) = "sobjectdescribeoptions"]
    [parent::VariableExpression[lower-case(@Image) = "default"]]
]]>
                </value>
            </property>
        </properties>
        <example>
<![CDATA[
public class Foo {
    public static void bar(List<Account> accounts) {
        if (Account.SObjectType.getDescribe(SObjectDescribeOptions.DEFERRED).isCreateable()) {
            insert accounts;
        }
    }
}
]]>
        </example>
    </rule>

    <rule name="OperationWithHighCostInLoop"
          language="apex"
          since="7.0.0"
          message="Avoid operations in loops that may impact performances"
          class="net.sourceforge.pmd.lang.apex.rule.performance.OperationWithHighCostInLoopRule"
          externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#operationwithhighcostinloop">
        <description>
This rule finds method calls inside loops that are known to be likely a performance issue. These methods should be
called only once before the loop.

Schema class methods like [Schema.getGlobalDescribe()](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_getGlobalDescribe)
and [Schema.describeSObjects()](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_describeSObjects)
might be slow depending on the size of your organization. Calling these methods repeatedly inside a loop creates
a potential performance issue.
        </description>
        <priority>3</priority>
        <example><![CDATA[
public class GlobalDescribeExample {
    // incorrect example
    public void getGlobalDescribeInLoop() {
        Set<String> fieldNameSet = new Set<String> {'Id'};
        for (String fieldNameOrDefaultValue : fieldNameOrDefaultValueList) {
            // Schema.getGlobalDescribe() should be called only once before the for-loop
            if (Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().containsKey(fieldNameOrDefaultValue.trim())) {
                fieldNameSet.add(fieldNameOrDefaultValue);
            }
        }
    }

    // corrected example
    public void getGlobalDescribeInLoopCorrected() {
        Map<String, Schema.SObjectField> fieldMap = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap();
        Set<String> fieldNameSet = new Set<String> {'Id'};
        for (String fieldNameOrDefaultValue : fieldNameOrDefaultValueList) {
            if (fieldMap.containsKey(fieldNameOrDefaultValue.trim())) {
                fieldNameSet.add(fieldNameOrDefaultValue);
            }
        }
    }
}
        ]]></example>
        <example><![CDATA[
public class DescribeSObjectsExample {
    // incorrect example
    public void describeSObjectsInLoop() {
        Set<String> fieldNameSet = new Set<String> {'Id'};
        for (String fieldNameOrDefaultValue : fieldNameOrDefaultValueList) {
            Schema.DescribeSObjectResult dsr = Account.sObjectType.getDescribe();
            if (Schema.describeSObjects(new List<String> { sObjectType })[0].fields.getMap().containsKey(fieldNameOrDefaultValue.trim())) {
                fieldNameSet.add(fieldNameOrDefaultValue);
            }
        }
    }

    // corrected example
    public void describeSObjectsInLoop() {
        Map<String, Schema.SObjectField> fieldMap = Schema.describeSObjects(new List<String> { 'Account' })[0].fields.getMap();
        Set<String> fieldNameSet = new Set<String> {'Id'};
        for (String fieldNameOrDefaultValue : fieldNameOrDefaultValueList) {
            if (fieldMap.containsKey(fieldNameOrDefaultValue.trim())) {
                fieldNameSet.add(fieldNameOrDefaultValue);
            }
        }
    }
}
        ]]></example>
    </rule>

    <rule name="OperationWithLimitsInLoop"
          language="apex"
          since="6.29.0"
          message="Avoid operations in loops that may hit governor limits"
          class="net.sourceforge.pmd.lang.apex.rule.performance.OperationWithLimitsInLoopRule"
          externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#operationwithlimitsinloop">
        <description>
            Database class methods, DML operations, SOQL queries, SOSL queries, Approval class methods, Email sending, async scheduling or queueing within loops can cause governor limit exceptions. Instead, try to batch up the data into a list and invoke the operation once on that list of data outside the loop.
        </description>
        <priority>3</priority>
        <example>
            <![CDATA[
public class Something {
    public void databaseMethodInsideOfLoop(List<Account> accounts) {
        for (Account a : accounts) {
            Database.insert(a);
        }
    }

    public void dmlInsideOfLoop() {
        for (Integer i = 0; i < 151; i++) {
            Account account;
            // ...
            insert account;
        }
    }

    public void soqlInsideOfLoop() {
        for (Integer i = 0; i < 10; i++) {
            List<Account> accounts = [SELECT Id FROM Account];
        }
    }

    public void soslInsideOfLoop() {
        for (Integer i = 0; i < 10; i++) {
            List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead];
        }
    }

    public void messageInsideOfLoop() {
        for (Integer i = 0; i < 10; i++) {
            Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
            Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email});
        }
    }

    public void approvalInsideOfLoop(Account[] accs) {
        for (Integer i = 0; i < 10; i++) {
            Account acc = accs[i];
            Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest();
            req.setObjectId(acc.Id);
            Approval.process(req);
            Approval.lock(acc);
            Approval.unlock(acc);
        }
    }

    public void asyncInsideOfLoop() {
        for (Integer i = 0; i < 10; i++) {
            System.enqueueJob(new MyQueueable());
            System.schedule('x', '0 0 0 1 1 ?', new MySchedule());
            System.scheduleBatch(new MyBatch(), 'x', 1);
        }
    }
}
]]>
        </example>
    </rule>

</ruleset>
