
Company News
AWS Security Hub Adds Socket for Supply Chain Security
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.
eslint-plugin-apex
Advanced tools
ESLint plugin for Salesforce Apex — ports PMD Apex rules to the ESLint ecosystem
ESLint plugin for Salesforce Apex — inspired by apex-pmd, this plugin ports 57 ESLint rules that align with the PMD Apex rule set into the ESLint ecosystem so that teams working with Salesforce can use a single linting tool for both Apex and Lightning Web Components (LWC) side by side.
If you already use eslint-plugin-lwc for your LWC code, adding eslint-plugin-apex brings your Apex codebase into the same workflow — one eslint.config.js, one npm run lint command, one CI step.
The plugin is backed by the ANTLR4-based @apexdevtools/apex-parser for accurate, whitespace-aware parsing rather than regex heuristics.
Status: Early release. Parser coverage is solid for classes and triggers; advanced patterns (anonymous Apex, complex generics) are progressively improved.
docs/rules/recommended, strict, security, performancenpm install --save-dev eslint-plugin-apex
Requires ESLint v9+ and Node.js v18+.
// eslint.config.js
import apex from 'eslint-plugin-apex';
export default [apex.configs.recommended];
| Config | Severity | Description |
|---|---|---|
apex.configs.recommended | errors for problem rules, warnings for suggestion | Recommended set of rules |
apex.configs.strict | error for all | Every rule enabled as an error |
apex.configs.security | error | Security rules only |
apex.configs.performance | error | Performance rules only |
All configs automatically target **/*.cls, **/*.trigger, and **/*.apex files.
// eslint.config.js
import apex from 'eslint-plugin-apex';
export default [
{
files: ['**/*.cls', '**/*.trigger', '**/*.apex'],
plugins: { apex },
languageOptions: apex.configs.recommended.languageOptions,
rules: {
'apex/security-no-soql-injection': 'error',
'apex/perf-no-dml-in-loop': 'error',
'apex/best-test-has-asserts': 'warn',
// disable a rule
'apex/doc-require-apexdoc': 'off',
},
},
];
Default reflects apex.configs.recommended: problem + recommended: true → error; suggestion + recommended: true → warn; recommended: false → off.
| Rule | Description | PMD equivalent | Default |
|---|---|---|---|
| apex/best-debug-use-logging-level | System.debug() calls should specify a LoggingLevel argument | DebugsShouldUseLoggingLevel | warn |
| apex/best-no-future-annotation | Prefer Queueable over @Future — the future annotation has significant limitations | AvoidFutureAnnotation | off |
| apex/best-no-global-modifier | Avoid 'global' class modifier — it permanently locks the public API in managed packages | AvoidGlobalModifier | warn |
| apex/best-no-logic-in-trigger | Avoid placing business logic directly in triggers — delegate to handler classes | AvoidLogicInTrigger | warn |
| apex/best-no-unused-local-variable | Detects local variables that are declared but never read | UnusedLocalVariable | off |
| apex/best-queueable-needs-finalizer | Queueable classes should attach a Finalizer for error recovery | QueueableWithoutFinalizer (+ legacy QueueableShouldAttachFinalizer) | off |
| apex/best-test-assertions-have-message | System.assert() calls should include a message parameter for clarity | ApexAssertionsShouldIncludeMessage | warn |
| apex/best-test-has-asserts | Apex unit test classes should include at least one assertion | ApexUnitTestClassShouldHaveAsserts | warn |
| apex/best-test-has-run-as | Test classes should include at least one System.runAs() call | ApexUnitTestClassShouldHaveRunAs | warn |
| apex/best-test-method-annotation | Use @IsTest annotation instead of the deprecated 'testMethod' keyword | ApexUnitTestMethodShouldHaveIsTestAnnotation | warn |
| apex/best-test-no-see-all-data | Avoid @isTest(seeAllData=true) as it exposes real org data to test modifications | ApexUnitTestShouldNotUseSeeAllDataTrue | error |
| Rule | Description | PMD equivalent | Default |
|---|---|---|---|
| apex/style-annotation-naming | Annotation names should use PascalCase | AnnotationsNamingConventions | warn |
| apex/style-braces-for-for | Require braces around for loop bodies | ForLoopsMustUseBraces | warn |
| apex/style-braces-for-if | Require braces around if/else statement bodies | IfStmtsMustUseBraces / IfElseStmtsMustUseBraces | warn |
| apex/style-braces-for-while | Require braces around while loop bodies | WhileLoopsMustUseBraces | warn |
| apex/style-fields-at-start | Field declarations should appear before method declarations | FieldDeclarationsShouldBeAtStart | warn |
| apex/style-naming-conventions | Enforce configurable naming conventions for Apex declarations | ClassNamingConventions / MethodNamingConventions / FieldNamingConventions / LocalVariableNamingConventions / FormalParameterNamingConventions / PropertyNamingConventions | warn |
| apex/style-one-declaration-per-line | Declare only one variable per statement | OneDeclarationPerLine | warn |
| Rule | Description | PMD equivalent | Default |
|---|---|---|---|
| apex/design-cognitive-complexity | Limit cognitive complexity of methods and classes | CognitiveComplexity | warn |
| apex/design-cyclomatic-complexity | Limit cyclomatic complexity of methods and classes | CyclomaticComplexity / StdCyclomaticComplexity | warn |
| apex/design-excessive-parameters | Flag methods with too many parameters | ExcessiveParameterList | warn |
| apex/design-excessive-public-count | Flag classes with too many public methods or attributes | ExcessivePublicCount | warn |
| apex/design-ncss-method-count | Limit the number of non-commenting source statements per method and class | NcssMethodCount / NcssCount / NcssTypeCount | warn |
| apex/design-no-boolean-parameters | Avoid boolean parameters in public and global methods | AvoidBooleanMethodParameters | warn |
| apex/design-no-deep-nesting | Avoid deeply nested if statements | AvoidDeeplyNestedIfStmts | warn |
| apex/design-no-unused-method | Detect private methods that are never called within the class | UnusedMethod | off |
| apex/design-too-many-fields | Flag classes with too many fields | TooManyFields | warn |
| Rule | Description | PMD equivalent | Default |
|---|---|---|---|
| apex/doc-require-apexdoc | Require ApexDoc comments on public and global classes, methods, and properties | ApexDoc | off |
| Rule | Description | PMD equivalent | Default |
|---|---|---|---|
| apex/error-aura-enabled-getter-public | @AuraEnabled property getters must be public or global | InaccessibleAuraEnabledGetter (older docs sometimes mis-labeled this as AuraEnabledWithoutCatchBlock) | error |
| apex/error-no-csrf-in-constructor | Disallow DML operations in constructors or class initializers | ApexCSRF | error |
| apex/error-no-direct-trigger-map-access | Avoid direct index access to Trigger.new or Trigger.old — iterate instead | AvoidDirectAccessTriggerMap | error |
| apex/error-no-empty-catch | Disallow empty catch blocks | EmptyCatchBlock | error |
| apex/error-no-empty-if | Disallow empty if statement bodies | EmptyIfStmt | error |
| apex/error-no-empty-try | Disallow empty try or finally blocks | EmptyTryOrFinallyBlock | error |
| apex/error-no-empty-while | Disallow empty while loop bodies | EmptyWhileStmt | error |
| apex/error-no-hardcoded-id | Avoid hardcoding Salesforce record IDs — they differ between environments | AvoidHardcodingId | error |
| apex/error-no-method-name-as-class | Non-constructor methods should not share the name of the enclosing class | MethodWithSameNameAsEnclosingClass | error |
| apex/error-no-nonexistent-annotation | Avoid annotations that do not exist in Apex | AvoidNonExistentAnnotations (+ legacy NonExistentCustomSettingOrMetadata) | error |
| apex/error-no-stateful-db-result | Avoid storing Database result types as instance variables in Database.Stateful batch classes | AvoidStatefulDatabaseResult | off |
| apex/error-no-type-shadow-namespace | Avoid declaring types with the same name as System or Schema namespace types | TypeShadowsBuiltInNamespace (+ legacy AvoidShadowingField) | off |
| apex/error-override-both-equals-hashcode | If overriding equals(), also override hashCode(), and vice versa | OverrideBothEqualsAndHashcode | error |
| apex/error-test-methods-in-test-class | @IsTest methods must reside in @IsTest annotated classes | TestMethodsMustBeInTestClasses | error |
| Rule | Description | PMD equivalent | Default |
|---|---|---|---|
| apex/perf-no-debug-statements | Remove or gate System.debug() calls in production code | AvoidDebugStatements | off |
| apex/perf-no-dml-in-loop | Avoid DML operations, SOQL queries, and governor-limited calls inside loops | OperationWithLimitsInLoop (+ legacy AvoidDmlStatementsInLoops) | error |
| apex/perf-no-eager-describe | Avoid calling Schema.describe*() methods inside loops — cache results instead | AvoidEagerDescribes (distinct from EagerlyLoadedDescribeSObjectResult) | error |
| apex/perf-no-high-cost-in-loop | Avoid high-cost Apex calls inside loops | OperationWithHighCostInLoop (+ legacy AvoidSoqlInLoops / AvoidHighCostInLoopWithoutBulkification) | error |
| apex/perf-no-non-restrictive-query | SOQL queries should include a WHERE clause to limit results | AvoidNonRestrictiveQueries (+ legacy WherelessSOQLQuery) | error |
| Rule | Description | PMD equivalent | Default |
|---|---|---|---|
| apex/security-crud-violation | DML operations and SOQL queries should include CRUD/FLS permission checks | ApexCRUDViolation | warn |
| apex/security-no-dangerous-methods | Flag potentially dangerous Apex method calls | ApexDangerousMethods | warn |
| apex/security-no-hardcoded-crypto | Avoid hardcoded cryptographic keys or IVs | ApexBadCrypto | error |
| apex/security-no-insecure-endpoint | HTTP callout endpoints must use HTTPS | ApexInsecureEndpoint | error |
| apex/security-no-open-redirect | Avoid constructing PageReference from user-controlled input | ApexOpenRedirect | error |
| apex/security-no-soql-injection | Avoid SOQL injection — use bind variables or String.escapeSingleQuotes() | ApexSOQLInjection | error |
| apex/security-no-xss-false-escape | Disabling HTML escaping (escape=false) can introduce XSS vulnerabilities | ApexXSSFromEscapeFalse | error |
| apex/security-no-xss-from-url | URL parameter values must be sanitized before output to prevent XSS | ApexXSSFromURLParam | error |
| apex/security-sharing-violations | Classes that access data should explicitly declare 'with sharing' or 'without sharing' | ApexSharingViolations | warn |
| apex/security-use-named-credentials | Use Named Credentials instead of hardcoding authentication details in HTTP requests | ApexSuggestUsingNamedCred | warn |
The PMD Converter is a browser-based tool that converts PMD Apex XML rule files into:
No data is sent anywhere — conversion happens entirely in the browser.
The offline PMD rule list this project tracks includes ExcessiveClassLength, EmptyStatementBlock, EagerlyLoadedDescribeSObjectResult, and NcssConstructorCount. They are intentionally not mapped to an apex/* rule today; the converter emits a custom-… skeleton for them. See the per-rule notes for perf-no-eager-describe versus EagerlyLoadedDescribeSObjectResult, and design-ncss-method-count versus per-constructor NCSS.
eslint-plugin-apex/
├── src/
│ ├── node-types.js ← custom AST node type constants + VISITOR_KEYS
│ ├── ast-builder.js ← ANTLR4 parse tree → custom ESLint AST
│ ├── apex-parser.js ← parseForESLint() adapter
│ ├── index.js ← plugin entry point, rule registry, shared configs
│ └── rules/
│ ├── best-practices/ ← 11 rules
│ ├── code-style/ ← 7 rules
│ ├── design/ ← 9 rules
│ ├── documentation/ ← 1 rule
│ ├── error-prone/ ← 14 rules
│ ├── performance/ ← 5 rules
│ └── security/ ← 10 rules
├── tests/
│ ├── parser.test.js ← custom parser tests (node:test)
│ ├── rules.test.js ← RuleTester tests for all rules
│ └── docs-coverage.test.js
└── docs/
├── index.html ← GitHub Pages PMD converter
└── rules/ ← one Markdown page per registered rule
├── best-practices/
├── code-style/
├── design/
├── documentation/
├── error-prone/
├── performance/
└── security/
The parser uses @apexdevtools/apex-parser, an ANTLR4-based Apex parser, and wraps its concrete syntax tree into a flat, traversable AST with custom node types.
Contributions welcome — especially:
MIT — see LICENSE.
FAQs
ESLint plugin for Salesforce Apex — ports PMD Apex rules to the ESLint ecosystem
The npm package eslint-plugin-apex receives a total of 1 weekly downloads. As such, eslint-plugin-apex popularity was classified as not popular.
We found that eslint-plugin-apex demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.

Research
/Security News
Popular npm packages keyv and cacheable compromised.

Security News
A misconfiguration gave three Anthropic models internet access, and one, believing it was in a simulation, shipped a credential-stealing package to PyPI.