> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kipmox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Code Analysis Rules

> Where kipmox's findings come from — the Salesforce Code Analyzer engines, kipmox's own rules, and your team's custom rules.

kipmox analyses your Apex and LWC code using **multiple engines at once**, then merges everything into one clean list in your editor. This page explains where each finding comes from, the built-in rules kipmox adds on top, and how to plug in your own rules.

## Where your findings come from

Every finding kipmox shows comes from one of these layers. They run together and are **merged and de-duplicated** into a single set of warnings.

| Layer                        | What it is                                                                                                                                                                             | Turn it on/off                                           |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| **Salesforce Code Analyzer** | The primary engine. Runs the industry-standard scanners below against the **Recommended** rule set by default.                                                                         | Requires the Salesforce CLI + `code-analyzer` plugin     |
| **kipmox rules**             | kipmox's own hand-tuned, Salesforce-specific rules — each with a plain-English explanation and a ready-to-apply fix. Also used as a **fallback** if the Code Analyzer isn't available. | Always on (`kipmox.analysis.allowInternalRulesFallback`) |
| **Your own rules**           | Custom PMD rulesets your team maintains, pointed to from a Code Analyzer config file.                                                                                                  | `kipmox: Set Static Analysis Config File`                |

<Note>
  You don't configure any of this to get started — kipmox runs the Recommended rules plus its own out of the box. The custom-rules layer is optional.
</Note>

## Severity levels

kipmox maps every finding — whichever engine produced it — to one of three levels:

|                 |                                                                      |
| --------------- | -------------------------------------------------------------------- |
| 🔴 **Critical** | Governor-limit violations and security issues — fix before deploying |
| 🟡 **Warning**  | Best-practice violations — should be addressed                       |
| 🔵 **Info**     | Code-quality suggestions — low urgency, no production risk           |

## Salesforce Code Analyzer engines

kipmox runs the [Salesforce Code Analyzer](https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview) as its primary engine. It bundles several scanners, each covering a different area:

| Engine                  | Covers                                                                            | Files             |
| ----------------------- | --------------------------------------------------------------------------------- | ----------------- |
| **PMD**                 | Apex best practices, performance, design, error-prone patterns                    | `.cls` `.trigger` |
| **ESLint**              | LWC / JavaScript correctness and best practices (`@salesforce/eslint-plugin-lwc`) | `.js`             |
| **Graph Engine (SFGE)** | Data-flow security analysis — CRUD/FLS violations, sharing issues                 | `.cls` `.trigger` |
| **Flow**                | Flow and Process Builder metadata analysis                                        | Flow metadata     |
| **RetireJS**            | Known vulnerabilities in bundled JavaScript libraries                             | `.js`             |
| **CPD**                 | Copy-paste / duplicate-code detection                                             | Apex + LWC        |
| **Regex**               | Pattern-based checks (e.g. hardcoded URLs, TODO markers)                          | All               |

By default kipmox uses the Code Analyzer's **Recommended** rule set — a curated, high-signal subset. To widen or narrow it, use a config file (see [Add your own rules](#add-your-own-rules)) or the advanced `kipmox.analysis.ruleSelector` setting.

<Note>
  The Code Analyzer runs through the Salesforce CLI. If the CLI or its `code-analyzer` plugin isn't installed, kipmox automatically falls back to its own built-in rules below so analysis still works.
</Note>

## kipmox's built-in rules

On top of the Code Analyzer, kipmox adds its own Salesforce-specific rules. These are the ones with the richest explanations and one-click fixes — the same rules kipmox falls back to when the Code Analyzer isn't available. New rules are added regularly.

### Apex

<AccordionGroup>
  <Accordion title="🔴 SOQL query inside a loop">
    **Rule ID:** `soql-in-loop`

    A SOQL query is being executed inside a `for` loop. Each iteration counts as a separate query against Salesforce's governor limit of 100 SOQL queries per transaction.

    **Why it matters:** In production, this throws `System.LimitException: Too many SOQL queries: 101` when the loop runs more than 100 times.

    **Fix:** Move the SOQL query outside the loop. Collect the IDs you need first, then query with a `WHERE Id IN :idSet` pattern.

    ```apex theme={null}
        // ❌ Before
        for (Account acc : accounts) {
            List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
        }

        // ✅ After
        Set<Id> accountIds = new Map<Id, Account>(accounts).keySet();
        List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];
    ```
  </Accordion>

  <Accordion title="🔴 DML statement inside a loop">
    **Rule ID:** `dml-in-loop`

    A DML statement (`insert`, `update`, `delete`, `upsert`, or `undelete`) is being executed inside a `for` loop. Each iteration counts against Salesforce's governor limit of 150 DML statements per transaction.

    **Why it matters:** In production, this throws `System.LimitException: Too many DML statements: 151` when the loop runs more than 150 times.

    **Fix:** Collect records in a list inside the loop, then perform a single bulk DML operation after the loop.

    ```apex theme={null}
        // ❌ Before
        for (Account acc : accounts) {
            acc.Name = 'Updated';
            update acc;
        }

        // ✅ After
        for (Account acc : accounts) {
            acc.Name = 'Updated';
        }
        update accounts;
    ```
  </Accordion>

  <Accordion title="🔴 @future call inside a loop">
    **Rule ID:** `future-in-loop`

    A `@future` method is being called inside a `for` loop. Each call counts against Salesforce's governor limit of 50 future calls per transaction.

    **Why it matters:** In production, this throws `System.LimitException: Too many future calls: 51` when the loop runs more than 50 times.

    **Fix:** Refactor the `@future` method to accept a collection of IDs and call it once outside the loop.

    ```apex theme={null}
        // ❌ Before
        for (Account acc : accounts) {
            processAsync(acc.Id);
        }

        // ✅ After
        Set<Id> accountIds = new Map<Id, Account>(accounts).keySet();
        processAsync(accountIds);
    ```
  </Accordion>

  <Accordion title="🔴 Hardcoded Salesforce record ID">
    **Rule ID:** `hardcoded-id`

    A Salesforce record ID is hardcoded directly in the code. Record IDs are org-specific and differ across every sandbox, scratch org, and production environment.

    **Why it matters:** Code with hardcoded IDs fails silently or throws errors when deployed to a different org — a common source of bugs when promoting from sandbox to production.

    **Fix:** Use a Custom Label, Custom Setting, or Custom Metadata Type to store org-specific IDs and reference them instead.
  </Accordion>

  <Accordion title="🟡 Missing sharing keyword">
    **Rule ID:** `missing-sharing-keyword`

    An Apex class is declared without an explicit `with sharing`, `without sharing`, or `inherited sharing` keyword.

    **Why it matters:** Without an explicit declaration, the class inherits its caller's sharing context — which may be `without sharing` — potentially exposing records the running user shouldn't access.

    **Fix:** Add an explicit sharing keyword to every Apex class.

    ```apex theme={null}
        // ❌ Before
        public class AccountService {

        // ✅ After
        public with sharing class AccountService {
    ```
  </Accordion>

  <Accordion title="🟡 SOQL mode unspecified">
    **Rule ID:** `soql-mode-unspecified`

    A SOQL query does not explicitly specify `USER_MODE` or `SYSTEM_MODE`.

    **Why it matters:** Without an explicit mode, SOQL runs in system mode by default — ignoring the running user's field-level security and object permissions.

    **Fix:** Add `WITH USER_MODE` to enforce the running user's permissions, or `WITH SYSTEM_MODE` to make the intent explicit.

    ```apex theme={null}
        // ❌ Before
        List<Account> accounts = [SELECT Id, Name FROM Account];

        // ✅ After
        List<Account> accounts = [SELECT Id, Name FROM Account WITH USER_MODE];
    ```
  </Accordion>

  <Accordion title="🟡 Unsafe single-record SOQL">
    **Rule ID:** `unsafe-single-soql`

    A SOQL query expected to return a single record is assigned directly to a variable without null protection or a try-catch block.

    **Why it matters:** If the query returns no records, Salesforce throws `System.QueryException: List has no rows for assignment to SObject`, crashing the transaction.

    **Fix:** Wrap the query in a try-catch block, or query into a list and check for results first.

    ```apex theme={null}
        // ❌ Before
        Account acc = [SELECT Id FROM Account WHERE Name = 'Acme' LIMIT 1];

        // ✅ After
        List<Account> accounts = [SELECT Id FROM Account WHERE Name = 'Acme' LIMIT 1];
        Account acc = accounts.isEmpty() ? null : accounts[0];
    ```
  </Accordion>

  <Accordion title="🟡 Direct trigger logic">
    **Rule ID:** `direct-trigger-logic`

    Business logic is written directly in the trigger body instead of delegating to a handler class.

    **Why it matters:** Triggers with inline logic are hard to test, maintain, and extend. Best practice is to keep trigger bodies thin and delegate all logic to a dedicated handler class.

    **Fix:** Create a trigger handler class and call it from the trigger body.

    ```apex theme={null}
        // ❌ Before
        trigger AccountTrigger on Account (before insert) {
            for (Account acc : Trigger.new) {
                acc.Name = acc.Name.toUpperCase();
            }
        }

        // ✅ After
        trigger AccountTrigger on Account (before insert) {
            AccountTriggerHandler.handleBeforeInsert(Trigger.new);
        }
    ```

    <Note>
      This rule carries a Low Confidence rating — refactoring trigger logic into a handler is a structural change. kipmox explains the pattern, but review the suggested fix thoroughly before applying.
    </Note>
  </Accordion>

  <Accordion title="🔵 System.debug statement left in code">
    **Rule ID:** `apex-system-debug`

    A `System.debug()` statement is present in the code.

    **Why it matters:** Debug statements left in production clutter debug logs, add minor overhead, and can expose sensitive data.

    **Fix:** Remove `System.debug()` statements before deploying to production.
  </Accordion>
</AccordionGroup>

### LWC JavaScript

<AccordionGroup>
  <Accordion title="🔴 Direct innerHTML assignment">
    **Rule ID:** `lwc-inner-html`

    A component uses direct `innerHTML` assignment to render content.

    **Why it matters:** Direct `innerHTML` is an XSS vulnerability — user-supplied content can inject malicious scripts. Lightning Locker also blocks this pattern.

    **Fix:** Use LWC template directives (`lwc:if`, `for:each`) to render dynamic content safely.
  </Accordion>

  <Accordion title="🔴 @api property mutated directly">
    **Rule ID:** `lwc-api-mutated`

    A component directly mutates an `@api` property.

    **Why it matters:** `@api` properties are owned by the parent. Mutating them directly breaks LWC's unidirectional data flow and can cause unpredictable rendering.

    **Fix:** Copy the `@api` value to a tracked internal property and mutate the copy instead.
  </Accordion>

  <Accordion title="🟡 Direct document access">
    **Rule ID:** `lwc-document-access`

    The component uses `document.querySelector()` or similar DOM APIs directly.

    **Why it matters:** Direct `document` access is blocked by Lightning Locker. Each component can only access its own DOM, not the full page.

    **Fix:** Use `this.template.querySelector()` to access elements within the component's own shadow DOM.

    ```javascript theme={null}
        // ❌ Before
        const input = document.querySelector('.my-input');

        // ✅ After
        const input = this.template.querySelector('.my-input');
    ```
  </Accordion>

  <Accordion title="🟡 Wire adapter error unchecked">
    **Rule ID:** `lwc-wire-error-unchecked`

    A wire adapter result is used without checking the `.error` property.

    **Why it matters:** Wire adapters return both `data` and `error`. If the call fails and `.error` isn't handled, the component silently fails or throws when accessing `.data`.

    **Fix:** Always check both `.data` and `.error` when using wire adapters.

    ```javascript theme={null}
        // ❌ Before
        @wire(getContacts, { accountId: '$accountId' })
        contacts;

        // ✅ After
        @wire(getContacts, { accountId: '$accountId' })
        wiredContacts({ error, data }) {
            if (data) {
                this.contacts = data;
            } else if (error) {
                this.error = error;
            }
        }
    ```
  </Accordion>

  <Accordion title="🔵 event.target.value on a Lightning base component">
    **Rule ID:** `lwc-event-target-value`

    The component uses `event.target.value` to read input values.

    **Why it matters:** For Lightning base components (`lightning-input`, `lightning-combobox`, etc.), the correct property is `event.detail.value`. `event.target.value` returns `undefined` for these.

    **Fix:** Use `event.detail.value` for Lightning base components.

    ```javascript theme={null}
        // ❌ Before
        handleChange(event) { this.value = event.target.value; }

        // ✅ After
        handleChange(event) { this.value = event.detail.value; }
    ```
  </Accordion>

  <Accordion title="🔵 Imperative Apex call missing .catch()">
    **Rule ID:** `lwc-missing-catch`

    An imperative Apex call uses `.then()` without a `.catch()` block.

    **Why it matters:** Without a `.catch()`, any error from the Apex method is silently swallowed — the component stops working with no visible error.

    **Fix:** Always add a `.catch()` block to imperative Apex calls.

    ```javascript theme={null}
        // ❌ Before
        getAccountData({ accountId: this.recordId })
            .then(result => { this.account = result; });

        // ✅ After
        getAccountData({ accountId: this.recordId })
            .then(result => { this.account = result; })
            .catch(error => { this.error = error; });
    ```
  </Accordion>

  <Accordion title="🔵 Unnecessary @track on a primitive & console.log left in code">
    **Rule IDs:** `lwc-track-unnecessary`, `console-log-production`

    `@track` on a primitive (string, number, boolean) is unnecessary since API v46 — all fields are reactive by default; remove the decorator. `console.log()` / `console.error()` statements left in a component clutter dev tools and can expose data — remove them before deploying.
  </Accordion>
</AccordionGroup>

### LWC HTML

<AccordionGroup>
  <Accordion title="🟡 String onclick handler">
    **Rule ID:** `lwc-string-onclick`

    An `onclick` attribute uses string function-call syntax.

    **Why it matters:** String-based handlers like `onclick="handleClick()"` aren't supported in LWC templates — a common mistake coming from Aura or plain HTML.

    **Fix:** Use curly-brace expression syntax to reference the handler.

    ```html theme={null}
        <!-- ❌ Before -->
        <button onclick="handleClick()">Click me</button>

        <!-- ✅ After -->
        <button onclick={handleClick}>Click me</button>
    ```
  </Accordion>

  <Accordion title="🟡 for:each missing key attribute">
    **Rule ID:** `lwc-for-each-no-key`

    A `for:each` directive is missing a `key` attribute on the repeated element.

    **Why it matters:** LWC requires a unique `key` on `for:each` elements to track and re-render list items — without it, LWC throws a template rendering error.

    **Fix:** Add a `key` attribute with a unique value — typically the record ID.

    ```html theme={null}
        <!-- ❌ Before -->
        <template for:each={contacts} for:item="contact">
            <p>{contact.Name}</p>
        </template>

        <!-- ✅ After -->
        <template for:each={contacts} for:item="contact">
            <p key={contact.Id}>{contact.Name}</p>
        </template>
    ```
  </Accordion>
</AccordionGroup>

### Shared (Apex + LWC)

<AccordionGroup>
  <Accordion title="🟡 Empty catch block">
    **Rule ID:** `empty-catch`

    A `catch` block is present but empty — exceptions are being silently swallowed.

    **Why it matters:** Empty catch blocks hide errors completely — no log, no user feedback, no way to diagnose.

    **Fix:** Always handle caught exceptions — at minimum, log them or surface an error message.

    ```apex theme={null}
        // ❌ Before
        try { update account; } catch (Exception e) { }

        // ✅ After
        try {
            update account;
        } catch (Exception e) {
            System.debug('Error updating account: ' + e.getMessage());
            throw e;
        }
    ```
  </Accordion>
</AccordionGroup>

## Add your own rules

Your team can layer in its own PMD rules on top of everything above — kipmox merges them into the same findings list.

<Steps>
  <Step title="Write a PMD ruleset (XML)">
    Create a standard PMD ruleset `.xml` file with your custom Apex rules.
  </Step>

  <Step title="Reference it from a Code Analyzer config file">
    Create a Salesforce Code Analyzer config file (`.yml` / `.yaml`) that points to your ruleset(s). A workspace-relative path such as `config/code-analyzer.yml` is best.
  </Step>

  <Step title="Point kipmox at the config file">
    Run **kipmox: Set Static Analysis Config File** from the Command Palette and select your `.yml` file. kipmox stores it in the `kipmox.analysis.configFile` setting and automatically infers the right rule selector from your rulesets.
  </Step>
</Steps>

To stop using custom rules, run **kipmox: Clear Static Analysis Config File**.

| Setting                                      | What it does                                                                                    |
| -------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `kipmox.analysis.configFile`                 | Path to your Salesforce Code Analyzer config `.yml` / `.yaml`                                   |
| `kipmox.analysis.ruleSelector`               | Advanced override for which rules run (leave empty to use Recommended / infer from your config) |
| `kipmox.analysis.allowInternalRulesFallback` | Use kipmox's built-in rules if the Code Analyzer is unavailable (default: on)                   |

<Note>
  Custom rules run through PMD via the Salesforce Code Analyzer, so they need the Salesforce CLI and `code-analyzer` plugin installed.
</Note>

<CardGroup cols={2}>
  <Card title="Code Analysis" icon="magnifying-glass" href="/workflows/code-analysis">
    How kipmox surfaces these findings in your editor
  </Card>

  <Card title="Commands" icon="terminal" href="/reference/commands">
    Set or clear your custom analysis config
  </Card>
</CardGroup>
