Skip to content

sfcc/prefer-explicit-empty-check ​

Suggests an explicit JavaScript replacement for empty(...) when the argument type is known and the SFCC behavior can be preserved.

SFCC behavior ​

Salesforce documents empty(value) as true for:

  • null
  • undefined
  • strings with zero length
  • arrays without elements
  • collections without elements

Plain objects are not considered empty. Consequently, Object.keys(value).length === 0 is not a behavior-preserving replacement for empty(value).

See the official TopLevel.global.empty() documentation.

Default behavior ​

  • 🟡 Severity: warn
  • 🛠️ Auto-fix: opt-in through the autofix option
  • 💡 Suggestions: one behavior-preserving replacement when it can be applied without changing argument evaluation

Suggestions are not applied automatically so callers can simplify conditions with surrounding control-flow knowledge. The rule omits a code action when an explicit replacement would repeat or discard a potentially stateful expression.

Set the autofix option to true to apply replacements that preserve both the result and evaluation behavior:

js
{
  "sfcc/prefer-explicit-empty-check": ["error", { autofix: true }]
}

Autofix is limited to positive checks whose expressions can be evaluated safely. Type-derived replacements retain explicit null and undefined guards even when the static type is non-null, because legacy callers and optional platform attributes can violate that annotation at runtime. Safe negated checks remain available as parenthesized editor suggestions. Member access and method calls that would be evaluated more than once remain diagnostics without a code action.

Preserve one evaluation

The diagnostic identifies the known argument semantics, but a code action cannot introduce a well-named local variable. Cache a member access or method result before expanding it when a getter, proxy, or method could return a different value or perform work on each access.

The empty-migration ESLint preset enables this option together with sfcc/no-ambiguous-empty. Oxlint can generate the equivalent override with createOxlintEmptyMigrationOverride(). Potentially side-effecting expressions remain manual-review diagnostics in both migration modes.

Oxlint support ​

Available in Oxlint with ESLint type refinement

Oxlint uses literals, constant string initializers, directly recognizable SFCC collections, and local @type or @param JSDoc to select safe replacements. Explicit SFCC JSDoc types also connect Site Preferences and Custom Attributes to configured metadata. ESLint TypeScript parser services additionally follow inferred types and aliases, producing suggestions or fixes for more calls.

Metadata-aware values ​

When siteTemplatePath points to exported object metadata, the rule also classifies:

  • Site.current.getCustomPreferenceValue("literalName") calls with a static preference name;
  • .custom.attributeName accesses whose receiver has one unambiguous parser-provided SFCC object type.

Without parser services, an explicit JSDoc receiver type such as dw.customer.Customer or import("dw/customer/Customer") provides the same metadata connection. Unqualified names such as Customer do not, because they could refer to an application type with the same name.

String-like and multiple-value metadata types use length checks. Numeric, boolean, date, markup, and single-select enum values use nullish checks. Custom Attribute values remain nullable defensively even when metadata marks them as mandatory.

Dynamic preference names, unknown receiver types, unsupported metadata types, and conflicting definitions remain ambiguous. The rule does not infer an object type from an attribute name alone.

Replacements ​

Known argument typeSuggested condition
string or arrayvalue.length === 0
nullable string or array`value === null
SFCC collectionvalue.isEmpty()
nullable SFCC collection`value === null
nullable non-collection object`value === null
non-null plain object or non-empty primitivefalse
null or undefined onlytrue

0 and false belong to the non-empty primitive row. Do not use !value as a general replacement because it changes SFCC behavior for both values.

Example ​

Invalid
js
/** @type {string[] | null | undefined} */
const productIds = getProductIds()

if (empty(productIds)) {
  return
}
Valid
js
if (productIds === null || productIds === undefined || productIds.length === 0) {
  return
}

Stateful member access ​

Invalid
js
if (empty(customer.custom.loyaltyID)) {
  return
}

Read the value once before applying the suggested condition:

Valid
js
const loyaltyID = customer.custom.loyaltyID
if (loyaltyID === null || loyaltyID === undefined || loyaltyID.length === 0) {
  return
}

Always-empty and never-empty values ​

A true or false replacement indicates a redundant condition rather than a useful runtime check. For example, a property read before its constructor assignment can be statically always undefined. Review constructor reuse and .call() patterns before removing that guard. Likewise, a non-null number or boolean is never SFCC-empty, including 0 and false.

Native and adapter collections ​

Use isEmpty() for a native dw.util.Collection. A test double for that platform type should implement the same method. If an application deliberately accepts another collection shape, such as an adapter with an empty property, document and check that adapter contract separately rather than applying the native collection replacement to it.

Released under the MIT License.