ESLint plugin to disallow the optional-call operator

Overview

ESLint Plugin: no-optional-call

npm Module

Overview

The no-optional-call ESLint plugin provides a single rule (non-configurable) that disallows any usage of the ?.( optional-call form.

Unlike the related ?. / ?.[ optional-chaining operators (which are quite useful), the ?.( optional-call operator is total junk, and should never have been added to the language -- at least, not how it was designed.

This plugin allows you to ensure that it never creeps into your project by accident.

Explanation

The supposed usage of this operator is like this:

obj?.func?.(42);

The first ?. is optional-chaining (which is fine!), but the second ?.( is an optional-call (which is bad). But importantly, this is not necessarily an object/method feature, as the optional-call can be used with a single identifier like this:

func?.(42);

In both cases, here's the equivalent code this ?.( operator purports to replace:

if (obj.func != null) {
    obj.func(42);
}

if (func != null) {
    func(42);
}

The usage of != null here is on purpose. It's non-nullish checking, meaning that it avoids null and undefined, but no other values.

WARNING: a common misconception is that the ?.( operator is actually intended for replacing this kind of code (which is a bit more common/relaxed):

obj.func && obj.func(42);

// or:
if (obj.func) {
    obj.func(42);
}

// **********

func && func(42);

// or:
if (func) {
    func(42);
}

These are subtly but importantly different than the previous != null forms. That's the first gotcha! If your existing code has relied on avoiding falsy values in obj.func / func other than the nullish values (null, undefined), such as false, "", NaN, or 0, then switching to ?.( will break your code, since the ?.( operator only stops at null and undefined values.

Oops!

But here's what's worse, what really dooms this ?.( feature, and why you should avoid ever using it in your programs (and rely on this plugin to ensure you don't).

This operator looks like what it's doing is providing a "safe call" type of operator (which exists in other programming languages). It seems like it's making sure that the value is callable (is actually a function) before calling it.

To the untrained eye that's not paying close attention, ?.( looks like it should be doing this:

if (typeof obj.func == "function") {
    obj.func(42);
}

if (typeof func == "function") {
    func(42);
}

But it doesn't! It only avoids null / undefined values.

If obj.func / func holds any non-function truthy value (strings, numbers, objects, arrays, dates, regular expressions, etc), then ?.( will attempt to execute call that value as if it was a function, which of course will fail because none of those are functions.

In other words, all falsy values except null and undefined, and all truthy values besides functions themselves, are all traps where the ?.( operator is going to fall over and break your program.

Types!?

I know a bunch of you are yelling at me that TypeScript solves this problem, because it makes sure all those other value-types are not in obj.func / func.

Here's my simple rebuttal: I call utter B.S. on the design of any JS feature which is full of (technically, an infinite number of) gotcha footguns by itself, and only operates sensibly if you also use TypeScript.

It'd be fine if TypeScript wanted to add this feature. Use it there to your heart's content! But if you're writing only JS, you should never, ever, ever, ever... use this ?.( feature.

Enabling The Plugin

To use no-optional-call, load it as a plugin into ESLint and configure the rules as desired.

.eslintrc.json

To load the plugin and enable its rules via a local or global .eslintrc.json configuration file:

"plugins": [
    "no-optional-call"
],
"rules": {
    "no-optional-call/default": "error"
}

package.json

To load the plugin and enable its rules via a project's package.json:

"eslintConfig": {
    "plugins": [
        "no-optional-call"
    ],
    "rules": {
        "no-optional-call/default": "error"
    }
}

ESLint CLI parameters

To load the plugin and enable its rules via ESLint CLI parameters, use --plugin and --rule flags:

eslint .. --plugin='no-optional-call' --rule='no-optional-call/default: error' ..

ESLint Node API

To use this plugin in Node.js with the ESLint API, require the npm module, and then (for example) pass the rule's definition to Linter#defineRule(..), similar to:

var noOptionalCall = require("eslint-plugin-no-optional-call");

// ..

var eslinter = new (require("eslint").Linter)();

eslinter.defineRule("no-optional-call/default",noOptionalCall.rules.default);

Then lint some code like this:

eslinter.verify(".. some code ..",{
    rules: {
        "no-optional-call/default": "error",
    }
});

Inline Comments

Once the plugin is loaded, the rule can be configured using inline code comments if desired, such as:

/* eslint "no-optional-call/default": "error" */

npm Package

To use this plugin with a global install of ESLint (recommended):

npm install -g eslint-plugin-no-optional-call

To use this plugin with a local install of ESLint:

npm install eslint-plugin-no-optional-call

License

All code and documentation are (c) 2022 Kyle Simpson and released under the MIT License. A copy of the MIT License is also included.

NOTE: This package was heavily inspired by eslint-plugin-no-pipe by @arendjr.

You might also like...

🦕 An opposite function of nullish coalescing operator

unnullish unnullish returns undefined if value is nullish, otherwise it executes callback and returns the result. It is an opposite function of the nu

Dec 15, 2022

Interactive 3D plotting with a simple function call using Three.js

Interactive 3D plotting with a simple function call using Three.js

About Generate interactive 3d plots with a simple function call. Function returns a Three.js scene which can be customized as needed. Basic function c

Oct 20, 2022

The invoker based on event model provides an elegant way to call your methods in another container via promisify functions

The invoker based on event model provides an elegant way to call your methods in another container via promisify functions. (like child-processes, iframe, web worker etc).

Dec 29, 2022

Inside-out promise; lets you call resolve and reject from outside the Promise constructor function.

Inside-out promise; lets you call resolve and reject from outside the Promise constructor function.

Feb 28, 2022

Create a 3D interactive object using images and one simple JS call

#Interactive 3D by Pete R. Create a 3D interactive object using images and one simple JS call Created by Pete R., Founder of BucketListly Demo View de

Nov 17, 2022

fcall, fetch and call any remote hot functions, anywhere, anytime, without installations or configurations.

fcall, fetch and call any remote hot functions, anywhere, anytime, without installations or configurations.

Sep 20, 2022

Minimal web3 implementation: call eth contracts directly from JS

micro-web3 Minimal web3 implementation: call eth contracts directly from JS Connect to any web3 server: host your own with execution layer client, or

Dec 29, 2022

1on1 call demo using Chime SDK meetings, Next.js, AppSync, and CDK!

1on1 call demo using Chime SDK meetings, Next.js, AppSync, and CDK!

Chime SDK Meetings 1on1 call demo with Next.js / AppSync / CDK This is a sample project to demonstrate Chime SDK meetings for one-on-one call with Nex

Dec 15, 2022
Owner
Kyle Simpson
Kyle Simpson is a Human-Centric Technologist. He's fighting for the people behind the pixels.
Kyle Simpson
A Lua plugin, written in TypeScript, to write TypeScript (Lua optional).

typescript.nvim A minimal typescript-language-server integration plugin to set up the language server via nvim-lspconfig and add commands for convenie

Jose Alvarez 315 Dec 29, 2022
A very simple tool that generates multiple spellings of a phone number (to effectively search for it using the OR operator and quotes)

phonenumberqueryconstructor A very simple tool that generates multiple writings of a phone number (to effectively search for it using the OR operator

Cyb_detective 10 Dec 7, 2022
🎡 Generate a random number, a list of them, or a generator with optional configuration

random_number Generate a random number, a list of them, or a generator with optional configuration Usage import randomNumber from "https://deno.land/x

Eliaz Bobadilla 7 Aug 7, 2022
Create your own wrappings with optional key bindings for selected text, a set of useful defaults is also provided.

Create your own wrappings with optional key bindings for selected text, a set of useful defaults is also provided.

Seth Yuan 66 Jan 1, 2023
In this project, you can create optional rooms and people can talk in the rooms

CodeTalk In this project, you can create optional rooms and people can talk in the rooms. Login and Registration page Login page welcomes us. If you d

Nazlı 3 Mar 12, 2022
Dark theme for VSCode with italics support (good for Dank Mono, Operator Mono)

Omni Owl for Visual Studio Code Dark theme for Visual Studio Code (with Italics) Install • Team • Imitate Preview • License Install All instructions c

Guilherme Rodz 59 Dec 23, 2022
A Kubernates Cloud-Shell (Web Terminal) Operator

A Kubernates Cloud-Shell (Web Terminal) Operator. English | Simplified_Chinese Why cloudtty ? Existing project ttyd already provides great feature to

cloudtty-io 307 Dec 27, 2022
The operator CLI for CDK apps.

cdk-app The operator CLI for CDK apps. Experimental. cdk-app lets you associate commands with CDK constructs so that you can quickly invoke functions,

CDK Labs at AWS 42 Dec 8, 2022
Trivy-Operator Lens Extension

Trivy-Operator Lens Extension This is a Lens extension for trivy-operator which provides visibility into vulnerability,misconfiguration and secrets as

Aqua Security 18 Dec 5, 2022
Check the strength of your password simply and quickly, and with optional UI indicators

Check the strength of your password simply and quickly, and with optional UI indicators. Lock Steel is lightweight, has no dependencies and is connected with the UI elements. Just pure CSS and VanillaJS.

Keenlabi 1 Sep 15, 2022