Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/src/rules/prefer-arrow-callback.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,36 @@ foo(function bar(n) { return n && n + bar(n - 1); }); // OK

:::

This rule additionally supports TypeScript type syntax.

Examples of **incorrect** TypeScript code for this rule:

::: incorrect

```ts
/*eslint prefer-arrow-callback: "error"*/

foo(function bar(a: string) { a; });

test('foo', function (this: any) {});
```

:::

Examples of **correct** TypeScript code for this rule:

::: correct

```ts
/*eslint prefer-arrow-callback: "error"*/

foo((a: string) => a);

const foo = function foo(bar: any) {};
```

:::

## Options

Access further control over this rule's behavior via an options object.
Expand All @@ -88,6 +118,30 @@ foo(function bar() {});

:::

Examples of **incorrect** TypeScript code for this rule with `{ "allowNamedFunctions": true }`:

::: incorrect

```ts
/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */

foo(function(a: string) {});
```

:::

Examples of **correct** TypeScript code for this rule with `{ "allowNamedFunctions": true }`:

::: correct

```ts
/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */

foo(function bar(a: string) {});
```

:::

### allowUnboundThis

By default `{ "allowUnboundThis": true }`, this `boolean` option allows function expressions containing `this` to be used as callbacks, as long as the function in question has not been explicitly bound.
Expand All @@ -110,6 +164,20 @@ someArray.map(function(item) { return this.doSomething(item); }, someObject);

:::

Examples of **incorrect** TypeScript code for this rule with `{ "allowUnboundThis": false }`:

::: incorrect

```ts
/* eslint prefer-arrow-callback: [ "error", { "allowUnboundThis": false } ] */

foo(function(a: string) { this; });

foo(function(a: string) { (() => this); });
```

:::

## When Not To Use It

* In environments that have not yet adopted ES6 language features (ES3/5).
Expand Down
9 changes: 9 additions & 0 deletions lib/rules/prefer-arrow-callback.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ function hasDuplicateParams(paramsList) {
module.exports = {
meta: {
type: "suggestion",
dialects: ["javascript", "typescript"],
language: "javascript",

defaultOptions: [
{ allowNamedFunctions: false, allowUnboundThis: true },
Expand Down Expand Up @@ -308,6 +310,13 @@ module.exports = {
return;
}

if (
node.params.length &&
node.params[0].name === "this"
) {
return;
}

// Remove `.bind(this)` if exists.
if (callbackInfo.isLexicalThis) {
const memberNode = node.parent;
Expand Down
248 changes: 248 additions & 0 deletions tests/lib/rules/prefer-arrow-callback.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,251 @@ ruleTester.run("prefer-arrow-callback", rule, {
},
],
});

const ruleTesterTypeScript = new RuleTester({
languageOptions: {
parser: require("@typescript-eslint/parser"),
},
});

ruleTesterTypeScript.run("prefer-arrow-callback", rule, {
valid: [
"foo(a => a);",
"foo((a:string) => a);",
"foo(function*() {});",
"foo(function() { this; });",
{
code: "foo(function bar(a:string) {});",
options: [{ allowNamedFunctions: true }],
},
"foo(function() { (() => this); });",
"foo(function() { this; }.bind(obj));",
"foo(function() { this; }.call(this));",
"foo(a => { (function() {}); });",
"var foo = function foo() {};",
"(function foo() {})();",
"foo(function bar() { bar; });",
"foo(function bar() { arguments; });",
"foo(function bar() { arguments; }.bind(this));",
"foo(function bar() { new.target; });",
"foo(function bar() { new.target; }.bind(this));",
"foo(function bar() { this; }.bind(this, somethingElse));",
"foo((function() {}).bind.bar)",
"foo((function() { this.bar(); }).bind(obj).bind(this))",
"test('clean', function (this: any) { this.foo = 'Cleaned!';});",
"obj.test('clean', function (foo) { this.foo = 'Cleaned!'; });",
],
invalid: [
{
code: "foo(function bar() {});",
output: "foo(() => {});",
errors,
},
{
code: "foo(function(a:string) {});",
output: "foo((a:string) => {});",
options: [{ allowNamedFunctions: true }],
errors,
},
{
code: "foo(function bar() {});",
output: "foo(() => {});",
options: [{ allowNamedFunctions: false }],
errors,
},
{
code: "foo(function() {});",
output: "foo(() => {});",
errors,
},
{
code: "foo(nativeCb || function() {});",
output: "foo(nativeCb || (() => {}));",
errors,
},
{
code: "foo(bar ? function() {} : function() {});",
output: "foo(bar ? () => {} : () => {});",
errors: [errors[0], errors[0]],
},
{
code: "foo(function() { (function() { this; }); });",
output: "foo(() => { (function() { this; }); });",
errors,
},
{
code: "foo(function() { this; }.bind(this));",
output: "foo(() => { this; });",
errors,
},
{
code: "foo(bar || function() { this; }.bind(this));",
output: "foo(bar || (() => { this; }));",
errors,
},
{
code: "foo(function() { (() => this); }.bind(this));",
output: "foo(() => { (() => this); });",
errors,
},
{
code: "foo(function bar(a:string) { a; });",
output: "foo((a:string) => { a; });",
errors,
},
{
code: "foo(function(a:any) { a; });",
output: "foo((a:any) => { a; });",
errors,
},
{
code: "foo(function(arguments:any) { arguments; });",
output: "foo((arguments:any) => { arguments; });",
errors,
},
{
code: "foo(function(a:string) { this; });",
output: null, // No fix applied
options: [{ allowUnboundThis: false }],
errors,
},
{
code: "foo(function() { (() => this); });",
output: null, // No fix applied
options: [{ allowUnboundThis: false }],
errors,
},
{
code: "qux(function(foo:string, bar:number, baz:string) { return foo * 2; })",
output: "qux((foo:string, bar:number, baz:string) => { return foo * 2; })",
errors,
},
{
code: "qux(function(foo:number, bar:number, baz:number) { return foo * bar; }.bind(this))",
output: "qux((foo:number, bar:number, baz:number) => { return foo * bar; })",
errors,
},
{
code: "qux(function(foo:any, bar:any, baz:any) { return foo * this.qux; }.bind(this))",
output: "qux((foo:any, bar:any, baz:any) => { return foo * this.qux; })",
errors,
},
{
code: "foo(function() {}.bind(this, somethingElse))",
output: "foo((() => {}).bind(this, somethingElse))",
errors,
},
{
code: "qux(function(foo = 1, [bar = 2] = [], {qux: baz = 3} = {foo: 'bar'}) { return foo + bar; });",
output: "qux((foo = 1, [bar = 2] = [], {qux: baz = 3} = {foo: 'bar'}) => { return foo + bar; });",
errors,
},
{
code: "qux(function(baz:string, baz:string) { })",
output: null, // Duplicate parameter names are a SyntaxError in arrow functions
errors,
},
{
code: "qux(function( /* no params */ ) { })",
output: "qux(( /* no params */ ) => { })",
errors,
},
{
code: "qux(function( /* a */ foo:string /* b */ , /* c */ bar:string /* d */ , /* e */ baz:string /* f */ ) { return foo; })",
output: "qux(( /* a */ foo:string /* b */ , /* c */ bar:string /* d */ , /* e */ baz:string /* f */ ) => { return foo; })",
errors,
},
{
code: "qux(async function (foo:number = 1, bar:number = 2, baz:number = 3) { return baz; })",
output: "qux(async (foo:number = 1, bar:number = 2, baz:number = 3) => { return baz; })",
errors,
},
{
code: "qux(async function (foo:number = 1, bar:number = 2, baz:number = 3) { return this; }.bind(this))",
output: "qux(async (foo:number = 1, bar:number = 2, baz:number = 3) => { return this; })",
errors,
},
{
code: "foo((bar || function() {}).bind(this))",
output: null,
errors,
},
{
code: "foo(function() {}.bind(this).bind(obj))",
output: "foo((() => {}).bind(obj))",
errors,
},

// Optional chaining
{
code: "foo?.(function() {});",
output: "foo?.(() => {});",
errors,
},
{
code: "foo?.(function() { return this; }.bind(this));",
output: "foo?.(() => { return this; });",
errors,
},
{
code: "foo(function() { return this; }?.bind(this));",
output: "foo(() => { return this; });",
errors,
},
{
code: "foo((function() { return this; }?.bind)(this));",
output: null,
errors,
},

// https://github.com/eslint/eslint/issues/16718
{
code: `
test(
function ()
{ }
);
`,
output: `
test(
() =>
{ }
);
`,
errors,
},
{
code: `
test(
function (
...args
) /* Lorem ipsum
dolor sit amet. */ {
return args;
}
);
`,
output: `
test(
(
...args
) => /* Lorem ipsum
dolor sit amet. */ {
return args;
}
);
`,
errors,
},
{
code: "foo(function():string { return 'foo' });",
output: "foo(():string => { return 'foo' });",
errors,
},
{
code: "test('foo', function (this: any) {});",
output: null,
errors,
},
],
});
Loading