Type Inference

How JetBrains IDEs type SFCC code without JSDoc - callback parameters, hook functions, job steps, SFRA routes, superModule chains and loops

JSDoc is the usual way to tell the IDE what a parameter holds. In SFCC code most types are already decided by the server or by the code around them, so Intellij SFCC reads them from there, and completion, Cmd+Click and checks work on code that has no JSDoc at all

A callback's parameters take their types from the helper that calls it, SFRA's collections.map or your own visitors.each, so lineItem is a ProductLineItem, index a number and shipment a Shipment, with no JSDoc on them

What gets a type

CodeTyped fromMore
A function you pass to a helper, such as collections.map(items, function (lineItem, index) {...})What the helper passes itCallbacks
req, res and next of an SFRA routeSFRA's typingsSFRA Controllers
A hook function, such as exports.calculate = function (basket)The hook point in hooks.jsonHook functions
A job step functionThe step kind in steptypes.jsonJob steps and pipelets
module.superModule and the scripts that extend itEvery copy below on the cartridge pathsuperModule chains
A loop variable, such as for each (var item in basket.productLineItems)The element of what it iteratesLoop types
server.forms.getForm('profile'), product.custom.badgeTextThe form XML and the metadata XMLForms, Custom Attributes

Callbacks

A function you write in the call to a helper gets the types of the values the helper passes it. The plugin reads the helper, in your cartridges or in SFRA's collections.js, and follows the values into the call

app_custom/cartridge/scripts/helpers/basketSummary.js
var collections = require('*/cartridge/scripts/util/collections');

/** @param {dw.order.Basket} basket */
function summarize(basket) {
    return collections.map(basket.productLineItems, function (lineItem, index) {
        // lineItem is dw.order.ProductLineItem, index is number
        return { quantity: lineItem.quantityValue, position: index + 1 };
    });
}

The helper can call the function in any of these ways:

Helper codeExample
An iterator loopvar it = items.iterator(); while (it.hasNext()) visitor(it.next());
An index loopfor (var i = 0; i < items.length; i++) callback(items[i], i);
call and applyaccept.call(null, cursor.next()), accept.apply(null, [entries[i], i])
An accumulatorcallback(initial, iterator.next()), as in collections.reduce
A member of the valuedone(value.customer) types the parameter as dw.customer.Customer
Another helperrelay(input, consumer) that hands both to deliver, up to 3 helpers deep

Arrow functions work too, at any argument position. The types follow the helper and the call as you edit either one

When a callback stays untyped

The plugin doesn't guess. A parameter keeps the IDE's own type when:

  • It has a JSDoc @param type or a TypeScript annotation, which always wins
  • The helper passes something the plugin can't type, such as the result of an unknown function, or reassigns the value in its loop
  • The helper calls itself, is very long, or passes the function through more than 3 helpers
  • The function is declared elsewhere and passed by name, such as collections.map(items, toRow)

Callback types need a license or trial and wait until indexing ends. They work in the .js and .ds files of linked cartridges

Hook functions

In a script that hooks.json registers for a platform hook point, the function named after the hook's last segment gets the hook's parameters, with no JSDoc:

HookFunctionParameters
dw.order.calculateexports.calculate(dw.order.LineItemCtnr)
dw.ocapi.shop.basket.items.beforePOSTexports.beforePOST(dw.order.Basket, ProductItemDocument[])

A function that several hooks share takes the types of all of them, like Basket | Order. These platform types win over a JSDoc @param. The Hook function returns a value its hook does not expect inspection then checks the return value, so return false in calculate reads "dw.order.calculate must return dw.system.Status, not boolean". See Hook function parameters for the hooks covered

Job steps and pipelets

A step function named in steptypes.json gets the parameters of its step kind, such as (item, Object, dw.job.JobStepExecution) for a process-function. A JSDoc @param wins over these. A pipelet's execute(pdict) gets the keys of its @input and @output header. See Parameters the server passes

superModule chains

var base = module.superModule gets the exports of the copy below, through every override on the cartridge path. Two SFRA patterns keep them too:

Codebase. and require of the script list
var base = module.superModule || {};, then base.getBadge = getBadge;The exports of every copy below and the members you add
module.exports = { getBadge: getBadge };, then module.exports[key] = base[key] in a loop over baseThe literal's members and every export below that the literal doesn't name
module.superModule.getProductType(product), with no variableThe exports of the copy below

Cmd+Click on an inherited member opens the copy that defines it, and completion names that cartridge on the right. See module.superModule

Troubleshooting

SymptomWhat to do
A callback parameter has no typeCheck the list in When a callback stays untyped. Write the function in the call, or add a JSDoc @param
A callback parameter has the wrong typeA JSDoc @param on it wins, so check the comment above the function
A hook function's parameters aren't typedRegister the script in the hooks.json that the cartridge's package.json points to, and export the function under the hook's last segment
base. lists only your own membersThe script must name module.superModule, and the copy below must be in a linked cartridge at the same path, case for case
Types appear only after a whileCallback, hook and step types wait until indexing ends

On this page