Skip to main content
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.
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.

Severity levels

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

Salesforce Code Analyzer engines

kipmox runs the Salesforce Code Analyzer as its primary engine. It bundles several scanners, each covering a different area: 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) or the advanced kipmox.analysis.ruleSelector setting.
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.

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

Rule ID: soql-in-loopA 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.
Rule ID: dml-in-loopA 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.
Rule ID: future-in-loopA @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.
Rule ID: hardcoded-idA 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.
Rule ID: missing-sharing-keywordAn 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.
Rule ID: soql-mode-unspecifiedA 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.
Rule ID: unsafe-single-soqlA 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.
Rule ID: direct-trigger-logicBusiness 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.
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.
Rule ID: apex-system-debugA 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.

LWC JavaScript

Rule ID: lwc-inner-htmlA 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.
Rule ID: lwc-api-mutatedA 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.
Rule ID: lwc-document-accessThe 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.
Rule ID: lwc-wire-error-uncheckedA 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.
Rule ID: lwc-event-target-valueThe 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.
Rule ID: lwc-missing-catchAn 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.
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.

LWC HTML

Rule ID: lwc-string-onclickAn 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.
Rule ID: lwc-for-each-no-keyA 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.

Shared (Apex + LWC)

Rule ID: empty-catchA 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.

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.
1

Write a PMD ruleset (XML)

Create a standard PMD ruleset .xml file with your custom Apex rules.
2

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.
3

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.
To stop using custom rules, run kipmox: Clear Static Analysis Config File.
Custom rules run through PMD via the Salesforce Code Analyzer, so they need the Salesforce CLI and code-analyzer plugin installed.

Code Analysis

How kipmox surfaces these findings in your editor

Commands

Set or clear your custom analysis config