Awesome
ESLint Plugin: proper-arrows
Overview
The proper-arrows ESLint plugin provides rules that control the definitions of =>
arrow functions, restricting them to a narrower and more proper/readable form.
The rules defined in this plugin:
-
"params"
: controls definitions of=>
arrow function parameters, such as forbidding unused parameters, forbidding short/unsemantic parameter names, etc. -
"name"
: requires=>
arrow functions to only be used in positions where they receive an inferred name (i.e., assigned to a variable or property, etc), to avoid the poor readbility/debuggability of anonymous function expressions.Note: This rule is like the "as-needed" mode of the built-in ESLint "func-names" rule, but applied to
=>
arrow functions; the built-in rule ignores them. -
"where"
: restricts where in program structure=>
arrow functions can be used: forbidding them in the global/top-level-module scope, object properties,export
statements, etc. -
"return"
: restricts the concise return value kind for=>
arrow functions, such as forbidding object literal concise returns (x => ({ x })
), forbidding concise returns of conditional/ternary expressions (x => x ? y : z
), etc. -
"this"
: requires/disallows=>
arrow functions using athis
reference, in the=>
arrow function itself or in a nested=>
arrow function; optionally, this rule can forbidthis
-containing=>
arrow functions only from the global scope.
Trivial =>
Arrow Functions
It's common for =>
arrow functions to be used as a shorthand for simple/trivial function roles, such as:
// no-op function
() => {};
// constant function
() => 42;
// closure function
() => v;
// identity function
v => v;
Note: To be specific on the definition of "trivial" being used here: trivial functions have no more than 1 parameter (simple identifier only), and either have no return ({}
) or a concise return of: a single variable, primitive (non-object) literal, or a void __
expression.
These types of functions are so simple that they don't suffer much from readability issues despite their concise syntax. As such, it's likely preferred to allow them even if one of the rules here would normally report them.
By default, all rules ignore these trivial functions. To force a rule to check trivial functions, include this configuration option for the rule:
{ "trivial": true }
Note: The "trivial"
option will not necessarily report trivial functions, but it will force trivial functions to be checked by the rule/mode in question.
Enabling The Plugin
To use proper-arrows, load it as a plugin into ESLint and configure the rules as desired.
extends
If you'd like to use the proper-arrows plugin in a recommended configuration preset, you can add the plugin in the extends
clause of your ESLint configuration, and pick a preset by name:
"extends": [
// ..
"plugin:@getify/proper-arrows/CONFIG-PRESET-NAME",
// ..
]
Note: All included configuration presets not only define specific rule configurations but also automatically load the plugin itself, so you don't need to list proper-arrows in the plugins
clause.
The available configuration presets to choose from:
-
getify-says
: This is my personal configuration. See the preset definition. -
..TBA..
It's important to note that you can still override any of the preset rule definitions in your configuration. Think of these presets as convenience "defaults" that can still be customized.
.eslintrc.json
To load the plugin and enable its rules via a local or global .eslintrc.json
configuration file:
"plugins": [
"@getify/proper-arrows"
],
"rules": {
"@getify/proper-arrows/params": ["error",{"unused":"trailing"}],
"@getify/proper-arrows/name": ["error",{"trivial":false}],
"@getify/proper-arrows/where": ["error",{"global":true}],
"@getify/proper-arrows/return": ["error",{"object":true}],
"@getify/proper-arrows/this": ["error","always",{"no-global":true}]
}
package.json
To load the plugin and enable its rules via a project's package.json
:
"eslintConfig": {
"plugins": [
"@getify/proper-arrows"
],
"rules": {
"@getify/proper-arrows/params": ["error",{"unused":"trailing"}],
"@getify/proper-arrows/name": ["error",{"trivial":false}],
"@getify/proper-arrows/name": ["error",{"global":true}],
"@getify/proper-arrows/return": ["error",{"object":true}],
"@getify/proper-arrows/this": ["error","always",{"no-global":true}]
}
}
ESLint CLI parameters
To load the plugin and enable its rules via ESLint CLI parameters, use --plugin
and --rule
flags:
eslint .. --plugin='@getify/proper-arrows' --rule='@getify/proper-arrows/params: [error,{"unused":"trailing"}]' ..
eslint .. --plugin='@getify/proper-arrows' --rule='@getify/proper-arrows/name: [error,{"trivial":false}]' ..
eslint .. --plugin='@getify/proper-arrows' --rule='@getify/proper-arrows/where: [error,{"global":true}]' ..
eslint .. --plugin='@getify/proper-arrows' --rule='@getify/proper-arrows/return: [error,{"object":true}' ..
eslint .. --plugin='@getify/proper-arrows' --rule='@getify/proper-arrows/this: [error,always,{"no-global":true}]' ..
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 properArrows = require("@getify/eslint-plugin-proper-arrows");
// ..
var eslinter = new (require("eslint").Linter)();
eslinter.defineRule("@getify/proper-arrows/params",properArrows.rules.params);
eslinter.defineRule("@getify/proper-arrows/name",properArrows.rules.name);
eslinter.defineRule("@getify/proper-arrows/where",properArrows.rules.where);
eslinter.defineRule("@getify/proper-arrows/return",properArrows.rules.return);
eslinter.defineRule("@getify/proper-arrows/this",properArrows.rules.this);
Then lint some code like this:
eslinter.verify(".. some code ..",{
rules: {
"@getify/proper-arrows/params": ["error",{unused:"trailing"}],
"@getify/proper-arrows/name": ["error",{trivial:false}],
"@getify/proper-arrows/where": ["error",{trivial:true}],
"@getify/proper-arrows/return": ["error",{object:true}],
"@getify/proper-arrows/this": ["error","always",{"no-global":true}]
}
});
Inline Comments
Once the plugin is loaded, the rule can be configured using inline code comments if desired, such as:
/* eslint "@getify/proper-arrows/params": ["error",{"unused":"trailing"}] */
/* eslint "@getify/proper-arrows/name": "error" */
/* eslint "@getify/proper-arrows/return": ["error",{"object":true}] */
/* eslint "@getify/proper-arrows/this": ["error","always",{"no-global":true}] */
Rule: "params"
The proper-arrows/params rule controls definitions of parameters for =>
arrow functions.
This rule can be configured to forbid unused parameter names ("unused"
), limit the number of parameters ("count"
), and require parameter names to be at least a certain length ("length"
). Also, this rule can specify a list of exceptions to always allow certain parameter names ("allow"
).
To turn this rule on:
"@getify/proper-arrows/params": "error"
"@getify/proper-arrows/params": [ "error", { "unused": true, "count": 3, "length": 4, "allow": [ "e", "err" ], "trivial": false } ]
The main purpose of this rule is to avoid readability harm for =>
arrow functions by ensuring the parameters are clean and "proper".
By forbidding unused parameters, the reader is not confused searching for their usage. By requiring parameters that are long enough to have meaningful names, the reader can understand the function better. By limiting the number of parameters, the reader can more easily visually distinguish the whole function definition.
For example:
var fn = (data,user) => ajax(user.id,data);
In this snippet, the =>
arrow function has two parameters, and both are 4 characters long and used in the function body. Therefore, the proper-arrows/params rule would not report any errors.
By contrast, this rule would report errors for:
var fn = (d,u,m,b) => ajax(u.id,d);
Here, the =>
arrow function has 4 parameters (too many!), each one is a single character (too short!), and two of them are unused.
Rule Configuration
The proper-arrows/params rule can be configured with any combination of three modes: "unused"
, "count"
, and "length"
. Also, parameter names can be explicitly allowed by name (thus not reporting any error for these modes) in the "allowed"
setting.
Note: The default behavior is that all three modes are turned on. You must specifically configure each mode to (effectively) disable it.
-
"unused"
(default:"all"
) forbids named parameters that are not used inside the function. Can also be set to"trailing"
to only report unused errors for trailing parameters -- that is, only those which come after the last parameter that is used -- or set to"none"
to disable this mode. -
"count"
(default:3
) is the maximum count for parameters on an=>
arrow function. All parameters are counted to check against the limit, including any"allowed"
parameters and the "...rest" parameter. Set to a larger number to effectively disable this mode. -
"length"
(default:2
) is the minimum length of allowed parameter names. Set to0
to effectively disable this mode. -
"allowed"
(default:[]
) is a list of parameter names to ignore and not report any errors for.
Rule "params"
Configuration: "unused"
Note: This rule mode resembles the built-in "no-unused-vars" rule, but instead focuses only on the parameters of =>
arrow functions.
To configure this rule mode (default: "all"
):
"@getify/proper-arrows/params": "error"
"@getify/proper-arrows/params": [ "error", { "unused": "all", "trivial": false } ]
The "unused": "all"
(default) mode checks each parameter to see if it's used anywhere within the function. If not, the parameter is reported as an error.
For example:
var fn1 = (one,two) => one + two;
var fn2 = (one,two = one) => two * 2;
var fn3 = one => two => three => one * two * three;
These statements all report no errors, because all parameters are used somewhere in each function.
By contrast, this rule would report errors for:
var fn1 = (one,two) => one * 2;
var fn2 = (one,...rest) => one * 2;
var fn3 = one => two => three => one * three;
In each statement, a parameter is defined in an =>
arrow function that is not used within.
"unused": "trailing"
It is sometimes desired to name some positional parameters that are effectively ignored by the function body, while using other named parameters that come after. As such, it can be helpful to suppress reports except on unused parameters that come after the last used parameter in the function signature.
In the "unused": "trailing"
mode, only unused parameters that come positionally after the last used parameter are reported:
var fn1 = (one,two,three) => two * 2;
In this snippet, since two
is the last used parameter, only three
would be reported as an unused parameter (ignores one
).
"unused": "none"
Disables this "unused"
mode; no checks are made for any unused parameters.
Rule "params"
Configuration: "count"
To configure this rule mode (default: 3
):
"@getify/proper-arrows/params": "error"
"@getify/proper-arrows/params": [ "error", { "count": 3, "trivial": false } ]
This rule mode counts all parameters to make sure the maximum count
limit is not violated.
For example:
var fn0 = () => "";
var fn1 = one => one;
var fn2 = (one,two) => one + two;
var fn3 = (one,two,three) => one * two * three;
var fn3b = (one,two,...three) => one * two * three[0];
These statements all report no errors, because all parameter counts are below the limit (default: 3
).
By contrast, this rule would report errors for:
var fn4 = (one,two,three,four) => one + two * three / four;
var fn4b = (one,two,three,...four) => one + two * three / four[0];
In each statement, the parameter count is above the limit.
Rule "params"
Configuration: "length"
Note: This rule mode resembles the built-in ESLint "id-length" rule, but instead focuses only on the parameters of =>
arrow functions.
To configure this rule mode (default: 2
):
"@getify/proper-arrows/params": "error"
"@getify/proper-arrows/params": [ "error", { "length": 2, "trivial": false } ]
This rule mode checks all parameters to make sure their basic character length is at least the minimum "length"
threshold.
For example:
var fn0 = () => 42;
var fn1 = (data) => data.id;
var fn2 = (user,cb) => ajax(user,cb);
These statements all report no errors, because all parameters are at least the length threshold specified (default: 2
), and the absence of a parameter is ignored.
By contrast, this rule would report errors for:
users.map(v => v * 2);
In this statement, the parameter length of v
is below the minimum threshold.
Rule "params"
Configuration: "allowed"
To configure named exceptions to the main three rule modes (default: []
):
"@getify/proper-arrows/params": [ "error", { "allowed": [ "e", "err" ], "trivial": false } ]
This exception list prevents any listed parameter from being reported as an error by any of this proper-arrows/params rule's modes.
For example:
var fn = (one,two,three,e) => 0;
By default, e
would report all 3 errors: it's unused, it's beyond the default count limit, and it's below the minimum length threshold. However, no errors will be reported if "e"
is included in the "allowed"
exception list.
Rule: "name"
The proper-arrows/name rule requires =>
arrow functions to be in a position where they will receive an inferred name, such as from being assigned to a variable or property.
To turn this rule on:
"@getify/proper-arrows/name": "error"
"@getify/proper-arrows/name": ["error",{ "trivial": false }]
The main purpose of this rule is to reduce the impact of the anonymous nature of =>
arrow function expressions, making them more readable, improving stack trace output, and giving them a named self-reference (recursion, event unbinding, etc). Primarily, this rule disallows =>
arrow functions passed directly as inline function expression arguments, as well as returned directly from other functions.
Note: This rule is like the "as-needed" mode of the built-in ESLint "func-names" rule, but applied to =>
arrow functions; the built-in rule ignores them.
Before being used (passed, called, returned, etc), =>
arrow functions should be assigned somewhere, to receive a name inference:
function multiplier(x) {
var multipliedBy = v => v * x;
return multipliedBy;
}
var tripled = v => v * 3;
var fns = {
doubled: multiplier(2),
tripled,
quadrupled: v => v * 4
};
[1,2,3].map(fns.doubled); // [2,4,6]
[1,2,3].map(fns.tripled); // [3,6,9]
[1,2,3].map(fns.quadrupled); // [4,8,12]
fns.doubled.name; // "multipledBy"
fns.tripled.name; // "tripled"
fns.quadrupled.name; // "quadrupled"
In this snippet, each =>
arrow function is first assigned to a variable or property, giving it an inferred name ("multipledBy"
, "tripled"
, or "quadrupled"
). As such, they would all pass this rule.
By contrast, this rule would report errors for each of the =>
arrow functions here:
function getName(fn) {
return fn.name;
}
function multiplier(x) {
return v => v * x;
}
fns = [ multiplier(2), v => v * 3 ];
getName( fns[0] ); // ""
getName( fns[1] ); // ""
getName( v => v * 4 ); // ""
In this snippet, all three =>
arrow functions remain anonymous because no name inferences are possible.
Rule: "where"
The proper-arrows/where rule restricts where in program structure =>
arrow functions can be used.
This rule can be configured to forbid =>
arrow functions in the global/top-level-module scope ("global"
) (or "global-declaration"
just for global arrow declarations), forbid =>
arrow functions as object properties ("property"
), and forbid =>
arrow functions in export
statements ("export"
).
To turn this rule on:
"@getify/proper-arrows/where": "error"
"@getify/proper-arrows/where": [ "error", { "global": true, "property": true, "export": true, "trivial": false } ]
The main purpose of this rule is to avoid readability harm when using =>
arrow functions in certain program structure locations.
Placing =>
arrow functions in the global/top-level-module scope has no benefit other than preferred style; they're more proper as regular function declarations. Placing =>
arrow functions on object properties has no benefit other than preferred style; they're more proper as concise object methods. Placing arrow functions in export
statements offers no benefit other than being more concise; they're more proper as exported named function declarations.
For example:
var PEOPLE_URL = "http://some.tld/api";
var People = {
getData(id,cb) {
ajax(PEOPLE_URL,{ id },cb);
}
};
function onData(data) {
console.log(data);
}
export default function lookup(id) {
return People.getData(id,onData);
}
export var lookup = function(id) {
return People.getData(id,onData);
};
In this snippet, the getData(..)
function is a clear and proper concise method on People
object, onData(..)
is a regular function declaration, and lookup(..)
is either a default or named-declaration export declaration. Therefore, the proper-arrows/where rule would not report any errors.
By contrast, this rule would default to reporting errors for each of these =>
arrow functions:
var PEOPLE_URL = "http://some.tld/api";
var People = {
getData: (id,cb) => ajax(PEOPLE_URL,{ id },cb)
};
const onData = data => {
console.log(data);
};
export default id => People.getData(id,onData)
export var lookup = id => People.getData(id,onData)
These usages of =>
arrow functions are not helping the readability or behavior of this snippet.
Rule Configuration
The proper-arrows/where rule can be configured with four (non-exclusive) modes: "global"
, "global-declaration"
, "property"
, and "export"
.
Note: The default behavior is that all three modes are turned on for this rule. You must specifically configure each mode to disable it.
-
"global"
(default:true
) forbids=>
arrow functions in general expressions located in the global/top-level-module scope. -
"global-declaration"
(default: same asglobal
) forbids=>
"arrow function declarations" (e.g.const foo = x => x * 2
) in the global/top-level-module scope. -
"property"
(default:true
) forbids assigning=>
arrow functions to object properties. -
"export"
(default:true
) forbids=>
arrow functions inexport
statements.
Rule "where"
Configuration: "global"
To configure this rule mode (on by default, set as false
to turn off):
"@getify/proper-arrows/where": [ "error", { "global": true, "trivial": false } ]
For inline function expressions in the global/top-level-module scope, use the regular function
declaration form to avoid errors:
getResults({ query: "foo" },function onResults(results){
console.log(results);
});
By contrast, this rule mode would report errors for:
getResults({ query: "foo" },results => {
console.log(results);
});
In this snippet, the =>
arrow function is less obviously a function than the function expression form.
Global Arrow Declarations
A "global arrow declaration" is specifically and only an arrow function expression initially assigned to a variable at the moment the variable is declared (in the global/top-level-module scope):
const onData = data => {
console.log(data);
};
The global
rule mode includes forbidding this form of "declaration". Instead, the standard function declaration form should be used to avoid such errors:
function onData(data) {
console.log(data);
}
However, the "global-declaration"
mode only applies to these types of "declarations", not all arrow expressions, and it overrides this rule mode's treatment of such declarations.
Rule "where"
Configuration: "global-declaration"
This rule mode by default mirrors the "global"
rule mode, whatever value it's set or defaulted to. However, you can explicitly configure this rule mode to a different value as such:
"@getify/proper-arrows/where": [ "error", { "global-declaration": true, "trivial": false } ]
This rule mode only controls reporting an error for a "global arrow declaration", such as:
const onData = data => {
console.log(data);
};
To avoid such errors, use the regular function
declaration form:
function onData(data) {
console.log(data);
}
Other global arrow expressions are unaffected by this particular rule mode; however, the separate "global"
rule mode, which is on by default, controls all arrow function forms in the global/top-level-module scope.
If "global"
mode is explicitly turned off (via false
), but this "global-declaration"
is explicitly turned on (via true
), ONLY "global arrow declarations" will report errors.
If "global"
mode is on (by default, or set to true
explicitly), but this "global-declaration"
mode is explicitly turned off (via false
), all other global/top-level-module scope arrow function expressions EXCEPT "global arrow declarations" will report errors.
However, if this "global-declaration"
mode is not set explicitly, it will always mirror the value of the "global"
rule mode, whether it is set explicitly or left to default.
Rule "where"
Configuration: "property"
To configure this rule mode (on by default, set as false
to turn off):
"@getify/proper-arrows/where": [ "error", { "property": true, "trivial": false } ]
When defining a function in an object literal definition, use the concise method form:
var People = {
getData(id,cb) {
ajax(PEOPLE_URL,{ id },cb);
}
};
By contrast, this rule mode would report errors for:
var People = {
getData: (id,cb) => ajax(PEOPLE_URL,{ id },cb)
};
In this snippet, the =>
arrow function is less obviously a method than the concise method form.
Rule "where"
Configuration: "export"
To configure this rule mode (on by default, set as false
to turn off):
"@getify/proper-arrows/where": [ "error", { "export": true, "trivial": false } ]
When exporting function declarations/expressions, use non-arrow functions:
export default function lookup(id) {
return People.getData(id,onData);
}
export var lookup = function(id) {
return People.getData(id,onData);
}
By contrast, this rule mode would report errors for:
export default id => People.getData(id,onData)
export var lookup = id => People.getData(id,onData)
In this snippet, the =>
arrow functions are less obviously a function than named function form.
Rule: "return"
The proper-arrows/return rule restricts the concise return values for =>
arrow functions.
This rule can be configured to forbid concise return of object literals ("object"
), forbid concise return of =>
arrow functions (aka, "chained arrow returns") without visual delimiters like ( .. )
("chained"
), forbid concise return of ternary/conditional expressions ("ternary"
), and forbid concise returns of comma sequences ("sequence"
).
To turn this rule on:
"@getify/proper-arrows/return": "error"
"@getify/proper-arrows/return": [ "error", { "object": true, "chained": true, "sequence": true, "trivial": false } ]
The main purpose of this rule is to avoid readability harm for =>
arrow functions by ensuring concise return values are clean and "proper".
By forbidding concise return of object literals, the reader is not confused at first glance by the { .. }
looking like a non-concise function body. By forbidding chained =>
arrow function concise returns without enclosing visual delimiters (like ( .. )
), the reader doesn't have to visually parse functions in a reverse/right-to-left fashion to determine the function boundaries. By forbidding concise return of comma sequences (ie, (x = 3,y = foo(x + 1),[x,y])
), the reader doesn't have as much trouble figuring out which value will be returned from the function. By forbidding concise return of ternary/conditional expressions, especially nested ternaries, the function's boundary is not as visually ambiguous.
For example:
var fn1 = prop => ( val => { return { [prop]: val }; } );
var fn2 = (x,y) => { x = 3; y = foo(x + 1); return [x,y]; };
var fn3 = (x,y) => {
return (
x > 3 ? x :
y > 3 ? y :
3;
);
};
In this snippet, the chained =>
arrow function fn1(..)
is surrounded by ( .. )
to visually delimit it, and the object literal being returned is done with a full function body and return
keyword. For fn2(..)
, the function body's return value ([x,y]
) is clear. For fn3(..)
, the presence of a return
keyword inside the { }
function body more clearly delimits the nested ternary/conditional expression as determining the return value. Therefore, the proper-arrows/return rule would not report any errors.
By contrast, this rule would report errors for each of these statements:
var fn1 = prop => val => ({ [prop]: val });
var fn2 = (x,y) => (x = 3, y = foo(x + 1), [x,y]);
var fn3 = (x,y) => x > 3 ? x : y > 3 ? y : 3;
In this snippet, the chained =>
arrow function return is not as clear, the concise return of the object literal can be confused as a function block at first glance, the concise return of the comma sequence makes it hard to determine which value will actually be returned, and the nested ternary/conditional expression obscures the determination of the return value.
Some claim that extra whitespace solves these readability issues. However, if you go to the trouble to space/indent to this extent, skipping the return
keyword doesn't really contribute to the readability, and in fact still makes the return values a little less visually distinct than the above forms.
var fn1 = prop
=> val
=> (
{ [prop]: val }
);
var fn2 = (x,y) => (
x = 3,
y = foo(x + 1),
[x,y]
);
var fn3 = (x,y) =>
x > 3 ? x :
y > 3 ? y :
3;
Also, many proponents of this whitespace "solution" advocate it in theory only; in practice, these sorts of functions are often just found in their less readable single-line form.
Rule Configuration
The proper-arrows/return rule can be configured with four (non-exclusive) modes: "object"
, "ternary"
, "chained"
, and "sequence"
.
Note: The default behavior is that all four modes are turned on for this rule. You must specifically configure each mode to disable it.
-
"object"
(default:true
) forbids returning object literals as concise return expressions. -
"ternary"
(default:0
) controls whether (and to what extent of nesting) conditional/ternary expressions (x ? y : z
) are allowed as the concise return expression of an=>
arrow function. -
"chained"
(default:true
) forbids returning=>
arrow functions (aka, "chained arrow returns") as concise return expressions, without visual delimiters( .. )
. -
"sequence"
(default:true
) forbids returning comma sequences (ie,(x = 3, y + x)
) as concise return expressions.
Rule "return"
Configuration: "object"
Note: This rule is similar to the built-in ESLint "arrow-body-style" rule, specifically the requireReturnForObjectLiteral: true
mode. However, that built-in rule mode is only defined for as-needed
, which requires always using the concise return expression form for all other =>
arrow functions where it's possible to do so.
The proper-arrows/return rule's "object"
mode is different (more narrowly focused): it only disallows the concise return of an object literal; otherwise, it doesn't place any requirements or restrictions on usage of =>
arrow functions.
To configure this rule mode (on by default, set as false
to turn off):
"@getify/proper-arrows/return": [ "error", { "object": true, "trivial": false } ]
If returning an object literal from an =>
arrow function, use the full-body return form:
var fn = prop => val => { return { [prop]: val }; };
In this snippet, the =>
arrow function uses the full-body return form for the object literal. As such, it would pass this rule.
By contrast, this rule mode would report errors for:
var fn = prop => val => ({ [prop]: val });
In this snippet, the =>
arrow function has an object literal as the concise return expression.
Rule "return"
Configuration: "ternary"
Note: This rule is similar to the built-in ESLint "no-ternary" rule and "no-nested-ternary" rule. However, those built-in rules focus on all conditional/ternary (? :
) expressions, and aren't configurable to an allowed level of nesting.
The proper-arrows/return rule's "ternary"
mode is different (more narrowly focused): it only controls the ternary/conditional expressions as the concise return of an =>
arrow function; otherwise, it doesn't place any requirements or restrictions on ternary/conditional expressions.
To configure this rule mode (defaults to 0
, set to a higher number to effectively disable):
"@getify/proper-arrows/return": [ "error", { "ternary": 1, "trivial": false } ]
The number specified for the "ternary"
option controls what level of (nested) ternary/conditional expressions are allowed as the concise return of an =>
arrow function. 0
means none allowed, 1
means a single ternary/conditional allowed, 2
means one level nested (x ? y ? z : w : u
) allowed, etc.
If this rule mode is set to 1
, it would pass this snippet:
var fn = data => data.id ? lookup(data.id) : lookup(-1);
But this rule mode set to 1
would report errors for:
var fn = data => data.id ? data.extra ? lookup(data.id,data.extra) : lookup(data.id) : lookup(-1);
Rule "return"
Configuration: "chained"
To configure this rule mode (on by default, set as false
to turn off):
"@getify/proper-arrows/return": [ "error", { "chained": true, "trivial": false } ]
This rule mode requires ( .. )
surrounding any concise return that is itself an =>
arrow function, because the parentheses help visually disambiguate where the function boundaries are, especially when there are several =>
arrow functions chained together.
If concise returning a chained =>
arrow function, wrap ( .. )
around it:
var fn = prop => ( val => { return { [prop]: val }; } );
In this snippet, the chained =>
arrow function return is visually disambiguated with ( .. )
. As such, it would pass this rule.
Though this rule does require it, visual ambiguity can even further be reduced by using whitespace to position the delimiters similarly to regular function bodies delimited by { .. }
:
var fn = prop => (
val => { return { [prop]: val }; }
);
Note: The extra whitespace is merely a readability suggestion if you plan to use this rule, not a requirement of it.
By contrast, this rule mode would report errors for:
var fn = prop => val => { return { [prop]: val }; };
In this snippet, the boundary of the chained =>
arrow function return is less clear.
Rule "return"
Configuration: "sequence"
Note: This rule is similar to the built-in ESLint "no-sequences" rule.
The proper-arrows/return rule's "sequence"
mode is different (more narrowly focused): it only disallows comma sequences as =>
arrow function concise return expressions.
To configure this rule mode (on by default, set as false
to turn off):
"@getify/proper-arrows/return": [ "error", { "sequence": true, "trivial": false } ]
Comma sequences are not very common, but one place they're a bit more common is as the concise return expression from an =>
arrow function so that multiple expression-statements can be strung together without needing a full function body. This may be clever or concise, but it's almost always less readable than just using separate statements.
For example:
var fn2 = (x,y) => { x = 3; y = foo(x + 1); return [x,y]; };
In this snippet, the =>
arrow function uses the full-body return form with separate statements and an explicit return
statement. As such, it would pass this rule.
By contrast, this rule mode would report errors for:
var fn2 = (x,y) => (x = 3, y = foo(x + 1), [x,y] );
In this snippet, the =>
arrow function has a comma sequence as the concise return expression.
Rule: "this"
The proper-arrows/this rule requires =>
arrow functions to reference the this
keyword. It also supports a "never"
configuration mode, which reverses the rule and means that =>
arrow functions must never use this
, as well as a "never-global"
configuration mode which only disallows this
usage in arrow functions that are in the global scope.
To turn this rule on:
"@getify/proper-arrows/this": "error"
"@getify/proper-arrows/this": [ "error", "always", { "no-global": true, "trivial": false } ]
The main purpose of this rule is to avoid usage of =>
arrow functions as just function shorthand (i.e., arr.map(x => x * 2)
), which can be argued as a misusage. Concise =>
arrow function syntax (with all its myriad variations) can harm readability, so instead =>
arrow functions should only be used when the "lexical this" behavior is needed.
Since =>
arrow functions don't have their own this
, they treat any this
reference as a normal variable (not a special keyword). That means this
is lexically looked up through any parent scopes until a valid definition of this
is found -- from a normal non-arrow function, or finally in the global scope itself.
Such this
behavior is referred to as "lexical this", and it is one of the main design characteristics for =>
arrow functions.
For example:
var o = {
id: 42,
getData() {
ajax("https://some.tld/api",() => console.log(this.id));
}
};
o.getData(); // 42
In this snippet, the =>
arrow function is inside a normal function and therefore looks up and adopts its this
(aka, "lexical this") -- which is set to the o
object by the o.getData()
call. Therefore, the proper-arrows/this rule would not report an error.
By contrast, this rule would report an error for:
var o = {
id: 42,
getData() {
ajax("https://some.tld/api",() => console.log(o.id));
}
};
o.getData(); // 42
Here, the =>
arrow function is lexically closed over the o
rather than using "lexical this", so it can be considered a misusage of the =>
arrow function merely for shorthand; rather, the callback should have used this.id
instead of o.id
, or just been a regular named function expression (ie, function onResp(){ console.log(o.id); }
).
To pass the proper-arrows/this rule without a reported error, all =>
arrow functions must reference a this
somewhere in their arguments or body -- or, when using the "nested"
configuration, any nested =>
arrow functions.
Rule Configuration
The proper-arrows/this rule can be configured in one of four modes: "nested"
(default), "always"
, "never"
and "never-global"
.
-
"nested"
(default) permits athis
to appear lower in a nested=>
arrow function (i.e.,x = y => z => this.foo(z)
), as long as there is no non-arrow function boundary crossed.Additionally, include the
"no-global"
option (default:false
) to forbidthis
-containing=>
arrow functions from the global scope (where they might inherit the global object asthis
) or from the top-level of a module (wherethis
would beundefined
). -
"always"
is more strict, requiring every single=>
arrow function to have its ownthis
reference.Additionally, include the
"no-global"
option (default:false
) to forbidthis
-containing=>
arrow functions from the global scope (where they might inherit the global object asthis
) or from the top-level of a module (wherethis
would beundefined
). -
"never"
is the reverse of the rule: all=>
arrow functions are forbidden from usingthis
. -
"never-global"
disallowsthis
usage in arrow functions that are in the global scope.
Rule "this"
Configuration: "nested"
To configure this rule mode (default):
"@getify/proper-arrows/this": "error"
"@getify/proper-arrows/this": [ "error", "nested" ]
"@getify/proper-arrows/this": [ "error", "nested", { "no-global": true, "trivial": false } ]
This rule mode allows a this
to appear either in the =>
arrow function, or nested deeper in a chain of =>
arrow functions, as long as there is no non-arrow function boundary crossed in the nesting.
If the "no-global"
option has not been set for this mode, these statements will all pass the rule:
var a = b => this.foo(b);
var c = d => e => this.foo(e);
var f = (g = h => this.foo(h)) => g;
But these statements will each fail this rule:
var a = b => foo(b);
var c = d => this.foo(e => e);
var f = (g = h => h) => this.foo(g);
var h = i => function(j){ this.foo(j); };
var k = l => function(){ return m => this.foo(m); };
"no-global"
Option
If the "no-global"
option has been set for this mode, these statements will all pass the rule:
function foo() { return a => this.bar(a); }
function foo() { return a => b => this.bar(a,b); }
function foo(cb = a => this.bar(a)) { .. }
function foo(cb = a => b => this.bar(a,b)) { .. }
And these statments will each fail this "no-global"
option rule:
var foo = a => this.bar(a);
var foo = a => b => this.bar(a,b);
var o = {
foo: a => this.bar(a)
};
var o = {
foo: a => b => this.bar(a,b)
};
Rule "this"
Configuration: "always"
To configure this rule mode:
"@getify/proper-arrows/this": [ "error", "always" ]
"@getify/proper-arrows/this": [ "error", "always", { "no-global": true, "trivial": false } ]
This rule mode requires a this
reference to appear in every single =>
arrow function (e.g., nested this
is not sufficient).
If the "no-global"
option has not been set for this mode, these statements will all pass the rule:
var a = b => this.foo(b);
var c = d => this.foo(e => this.bar(e));
var f = (g = h => this.foo(h)) => this.bar(g);
But these statements will each fail this rule:
var a = b => foo(b);
var c = d => e => this.foo(e);
var f = g => this.foo(h => h);
var i = (j = k => k) => this.foo(j);
Note: In each of the above examples, at least one of the =>
arrow functions does not have its own this
, hence the mode's rule failure (doesn't consider nested this
).
"no-global"
Option
If the "no-global"
option has been set for this mode, these statements will all pass the rule:
function foo() { return a => this.bar(a); }
function foo(cb = a => this.bar(a)) { .. }
And these statements will each fail this "no-global"
option rule:
var foo = a => this.bar(a);
var o = {
foo: a => this.bar(a)
};
Rule "this"
Configuration: "never"
To configure this rule mode:
"@getify/proper-arrows/this": [ "error", "never" ]
This rule mode forbids a this
reference from appearing in any =>
arrow function.
These statements will all pass the rule in this mode:
var a = b => foo(b);
var c = d => foo(e => bar(e));
var f = (g = h => foo(h)) => bar(g);
But these statements will each fail the rule in this mode:
var a = b => this.foo(b);
var c = d => e => this.foo(e);
var f = g => foo(h => this.bar(h));
Note: The "no-global"
option and "trivial"
option are not applicable and have no effect for this rule mode.
Rule "this"
Configuration: "never-global"
To configure this rule mode:
"@getify/proper-arrows/this": [ "error", "never-global" ]
This rule mode forbids this
usage in arrow functions that are in the global scope (including nested arrow functions).
These statements will all pass the rule in this mode:
var a = b => foo(b);
var c = d => foo({ set baz(v){ return z => this.bar(z); } })
var e = f => foo({ method(){ return z => this.bar(z); } })
But these statements will each fail the rule in this mode:
var a = b => this.foo(b);
var c = d => e => this.foo(e);
var f = g => foo(h => this.bar(h));
npm Package
To use this plugin with a global install of ESLint (recommended):
npm install -g @getify/eslint-plugin-proper-arrows
To use this plugin with a local install of ESLint:
npm install @getify/eslint-plugin-proper-arrows
Builds
If you need to bundle/distribute this eslint plugin, use dist/eslint-plugin-proper-arrows.js
, which comes pre-built with the npm package distribution; you shouldn't need to rebuild it under normal circumstances.
However, if you download this repository via Git:
-
The included build utility (
scripts/build-core.js
) builds (and minifies)dist/eslint-plugin-proper-arrows.js
from source. -
To install the build and test dependencies, run
npm install
from the project root directory. -
To manually run the build utility with npm:
npm run build
-
To run the build utility directly without npm:
node scripts/build-core.js
Tests
A comprehensive test suite is included in this repository, as well as the npm package distribution. The default test behavior runs the test suite against lib/index.js
.
-
The included Node.js test utility (
scripts/node-tests.js
) runs the test suite. -
Ensure the test dependencies are installed by running
npm install
from the project root directory.- Note: Starting with npm v5, the test utility is not run automatically during this
npm install
. With npm v4 and before, the test utility automatically runs at this point.
- Note: Starting with npm v5, the test utility is not run automatically during this
-
To run the test utility with npm:
npm test
Other npm test scripts:
-
npm run test:dist
will run the test suite againstdist/eslint-plugins-proper-arrows.js
instead of the default oflib/index.js
. -
npm run test:package
will run the test suite as if the package had just been installed via npm. This ensurespackage.json
:main
properly referencesdist/eslint-plugins-proper-arrows.js
for inclusion. -
npm run test:all
will run all three modes of the test suite.
-
-
To run the test utility directly without npm:
node scripts/node-tests.js
Test Coverage
If you have Istanbul already installed on your system (requires v1.0+), you can use it to check the test coverage:
npm run coverage
Then open up coverage/lcov-report/index.html
in a browser to view the report.
To run Istanbul directly without npm:
istanbul cover scripts/node-tests.js
Note: The npm script coverage:report
is only intended for use by project maintainers; it sends coverage reports to Coveralls.
License
All code and documentation are (c) 2019-2021 Kyle Simpson and released under the MIT License. A copy of the MIT License is also included.