There’s a gotcha with Javascript’s Array.reduce. If you don’t pass an initial value to Array.reduce, you’ll receive a TypeError when the array is empty.

In this post we’ll demonstrate how to use ESLint’s no-restricted-syntax rule to build a lint for Array.reduce.

Preventing errors with Array.reduce

The following code raises a TypeError because we don’t provide an initial value to Array.reduce.

[].reduce((a, b) => a + b);
// Uncaught TypeError: Reduce of empty array with no initial value
//     at Array.reduce (<anonymous>)

We can prevent this TypeError by passing an initial value, 0.

[].reduce((a, b) => a + b, 0);
// Success!

To prevent the TypeError, our lint should require us to pass an initial value to all Array.reduce calls.

To create our lint with no-restricted-syntax, we need an ESLint AST Selector that matches our problem code.

Building an AST Selector

Our AST Selector should match only the syntax we’d like to ban. Let’s go step-by-step and build a Selector.

  1. Find a sample of the syntax you’d like to ban, like the code above.
     // ban
     [].reduce((a, b) => a + b);
     // allow
     [].reduce((a, b) => a + b, 0);
    
  2. Visit astexplorer.net and select the “espree” parser. This is the parser used by ESLint.

    Paste in your sample code.

    Looking at the AST, we see that our Array.reduce expression is represented as a CallExpression.

     [].reduce((a, b) => a + b);
    
     // AST, truncated for readability
     {
         "type": "CallExpression",
         "callee": {
           "type": "MemberExpression",
           "property": {
             "type": "Identifier",
             // we should match CallExpression's with a callee.property.name of
             // "reduce".
             "name": "reduce"
           }
         },
         // we should match CallExpression's with one argument.
         "arguments": [
           {
             "type": "ArrowFunctionExpression"
             // truncated
           }
         ]
       }
    
  3. Find a feature of the AST node that differentiates the good syntax from the bad.

    A bad Array.reduce call will only have one argument (the callback), while a good call will have two arguments. So we want to match all Array.reduce calls with one argument.

    We also only care about calls for .reduce(). Looking at our AST, we want to match CallExpressions with a callee MemberExpression having an Identifier with a name "reduce".

    Using the Selector syntax, we can match a CallExpression with a single argument as:

     CallExpression[arguments.length=1]
    

    To match the "reduce" callee, we can write:

     CallExpression[callee.property.name='reduce']
    

    Combining our two selectors together, we have:

     CallExpression[arguments.length=1][callee.property.name='reduce']
    
  4. Test your selector on the esquery website.

    Here we can test how our selector matches on our sample code.

    Paste your sample code and enter your selector. You’ll see “1 nodes found” towards the bottom of the page if your selector matches.

    Testing our selector against our sample code from above, everything looks good.

  5. Add your selector to ESLint’s config (.eslintrc.js)

    This will configure ESLint’s no-restricted-syntax rule to use our new selector.

     module.exports = {
       rules: {
         "no-restricted-syntax": [
           "error",
           {
             selector:
               "CallExpression[arguments.length=1][callee.property.name='reduce']",
             message: "Provide initialValue to .reduce().",
           },
         ],
       },
     };
    

    If we write, [].reduce((a, b) => a + b), we’ll get an error: “Provide initialValue to .reduce().”

  6. Success!

    ESLint should now warn about incorrect Array.reduce calls.

alternative solutions

We could have written our Selector using nesting like the following:

CallExpression[arguments.length=1] > MemberExpression.callee > Identifier.property[name=reduce]

By nesting our selector, we can tell ESLint to only highlight the final Identifier node when reporting the error.

Which looks like:

Check out the “Selectors” page on the ESLint website for more information about ESLint AST Selectors.