Skip to content

Commit b03ad17

Browse files
feat: add TypeScript support to prefer-arrow-callback (#19678)
1 parent e9129e0 commit b03ad17

File tree

3 files changed

+325
-0
lines changed

3 files changed

+325
-0
lines changed

docs/src/rules/prefer-arrow-callback.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,36 @@ foo(function bar(n) { return n && n + bar(n - 1); }); // OK
6464

6565
:::
6666

67+
This rule additionally supports TypeScript type syntax.
68+
69+
Examples of **incorrect** TypeScript code for this rule:
70+
71+
::: incorrect
72+
73+
```ts
74+
/*eslint prefer-arrow-callback: "error"*/
75+
76+
foo(function bar(a: string) { a; });
77+
78+
test('foo', function (this: any) {});
79+
```
80+
81+
:::
82+
83+
Examples of **correct** TypeScript code for this rule:
84+
85+
::: correct
86+
87+
```ts
88+
/*eslint prefer-arrow-callback: "error"*/
89+
90+
foo((a: string) => a);
91+
92+
const foo = function foo(bar: any) {};
93+
```
94+
95+
:::
96+
6797
## Options
6898

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

89119
:::
90120

121+
Examples of **incorrect** TypeScript code for this rule with `{ "allowNamedFunctions": true }`:
122+
123+
::: incorrect
124+
125+
```ts
126+
/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */
127+
128+
foo(function(a: string) {});
129+
```
130+
131+
:::
132+
133+
Examples of **correct** TypeScript code for this rule with `{ "allowNamedFunctions": true }`:
134+
135+
::: correct
136+
137+
```ts
138+
/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */
139+
140+
foo(function bar(a: string) {});
141+
```
142+
143+
:::
144+
91145
### allowUnboundThis
92146

93147
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.
@@ -110,6 +164,20 @@ someArray.map(function(item) { return this.doSomething(item); }, someObject);
110164

111165
:::
112166

167+
Examples of **incorrect** TypeScript code for this rule with `{ "allowUnboundThis": false }`:
168+
169+
::: incorrect
170+
171+
```ts
172+
/* eslint prefer-arrow-callback: [ "error", { "allowUnboundThis": false } ] */
173+
174+
foo(function(a: string) { this; });
175+
176+
foo(function(a: string) { (() => this); });
177+
```
178+
179+
:::
180+
113181
## When Not To Use It
114182

115183
* In environments that have not yet adopted ES6 language features (ES3/5).

lib/rules/prefer-arrow-callback.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ function hasDuplicateParams(paramsList) {
151151
module.exports = {
152152
meta: {
153153
type: "suggestion",
154+
dialects: ["javascript", "typescript"],
155+
language: "javascript",
154156

155157
defaultOptions: [
156158
{ allowNamedFunctions: false, allowUnboundThis: true },
@@ -308,6 +310,13 @@ module.exports = {
308310
return;
309311
}
310312

313+
if (
314+
node.params.length &&
315+
node.params[0].name === "this"
316+
) {
317+
return;
318+
}
319+
311320
// Remove `.bind(this)` if exists.
312321
if (callbackInfo.isLexicalThis) {
313322
const memberNode = node.parent;

tests/lib/rules/prefer-arrow-callback.js

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,251 @@ ruleTester.run("prefer-arrow-callback", rule, {
255255
},
256256
],
257257
});
258+
259+
const ruleTesterTypeScript = new RuleTester({
260+
languageOptions: {
261+
parser: require("@typescript-eslint/parser"),
262+
},
263+
});
264+
265+
ruleTesterTypeScript.run("prefer-arrow-callback", rule, {
266+
valid: [
267+
"foo(a => a);",
268+
"foo((a:string) => a);",
269+
"foo(function*() {});",
270+
"foo(function() { this; });",
271+
{
272+
code: "foo(function bar(a:string) {});",
273+
options: [{ allowNamedFunctions: true }],
274+
},
275+
"foo(function() { (() => this); });",
276+
"foo(function() { this; }.bind(obj));",
277+
"foo(function() { this; }.call(this));",
278+
"foo(a => { (function() {}); });",
279+
"var foo = function foo() {};",
280+
"(function foo() {})();",
281+
"foo(function bar() { bar; });",
282+
"foo(function bar() { arguments; });",
283+
"foo(function bar() { arguments; }.bind(this));",
284+
"foo(function bar() { new.target; });",
285+
"foo(function bar() { new.target; }.bind(this));",
286+
"foo(function bar() { this; }.bind(this, somethingElse));",
287+
"foo((function() {}).bind.bar)",
288+
"foo((function() { this.bar(); }).bind(obj).bind(this))",
289+
"test('clean', function (this: any) { this.foo = 'Cleaned!';});",
290+
"obj.test('clean', function (foo) { this.foo = 'Cleaned!'; });",
291+
],
292+
invalid: [
293+
{
294+
code: "foo(function bar() {});",
295+
output: "foo(() => {});",
296+
errors,
297+
},
298+
{
299+
code: "foo(function(a:string) {});",
300+
output: "foo((a:string) => {});",
301+
options: [{ allowNamedFunctions: true }],
302+
errors,
303+
},
304+
{
305+
code: "foo(function bar() {});",
306+
output: "foo(() => {});",
307+
options: [{ allowNamedFunctions: false }],
308+
errors,
309+
},
310+
{
311+
code: "foo(function() {});",
312+
output: "foo(() => {});",
313+
errors,
314+
},
315+
{
316+
code: "foo(nativeCb || function() {});",
317+
output: "foo(nativeCb || (() => {}));",
318+
errors,
319+
},
320+
{
321+
code: "foo(bar ? function() {} : function() {});",
322+
output: "foo(bar ? () => {} : () => {});",
323+
errors: [errors[0], errors[0]],
324+
},
325+
{
326+
code: "foo(function() { (function() { this; }); });",
327+
output: "foo(() => { (function() { this; }); });",
328+
errors,
329+
},
330+
{
331+
code: "foo(function() { this; }.bind(this));",
332+
output: "foo(() => { this; });",
333+
errors,
334+
},
335+
{
336+
code: "foo(bar || function() { this; }.bind(this));",
337+
output: "foo(bar || (() => { this; }));",
338+
errors,
339+
},
340+
{
341+
code: "foo(function() { (() => this); }.bind(this));",
342+
output: "foo(() => { (() => this); });",
343+
errors,
344+
},
345+
{
346+
code: "foo(function bar(a:string) { a; });",
347+
output: "foo((a:string) => { a; });",
348+
errors,
349+
},
350+
{
351+
code: "foo(function(a:any) { a; });",
352+
output: "foo((a:any) => { a; });",
353+
errors,
354+
},
355+
{
356+
code: "foo(function(arguments:any) { arguments; });",
357+
output: "foo((arguments:any) => { arguments; });",
358+
errors,
359+
},
360+
{
361+
code: "foo(function(a:string) { this; });",
362+
output: null, // No fix applied
363+
options: [{ allowUnboundThis: false }],
364+
errors,
365+
},
366+
{
367+
code: "foo(function() { (() => this); });",
368+
output: null, // No fix applied
369+
options: [{ allowUnboundThis: false }],
370+
errors,
371+
},
372+
{
373+
code: "qux(function(foo:string, bar:number, baz:string) { return foo * 2; })",
374+
output: "qux((foo:string, bar:number, baz:string) => { return foo * 2; })",
375+
errors,
376+
},
377+
{
378+
code: "qux(function(foo:number, bar:number, baz:number) { return foo * bar; }.bind(this))",
379+
output: "qux((foo:number, bar:number, baz:number) => { return foo * bar; })",
380+
errors,
381+
},
382+
{
383+
code: "qux(function(foo:any, bar:any, baz:any) { return foo * this.qux; }.bind(this))",
384+
output: "qux((foo:any, bar:any, baz:any) => { return foo * this.qux; })",
385+
errors,
386+
},
387+
{
388+
code: "foo(function() {}.bind(this, somethingElse))",
389+
output: "foo((() => {}).bind(this, somethingElse))",
390+
errors,
391+
},
392+
{
393+
code: "qux(function(foo = 1, [bar = 2] = [], {qux: baz = 3} = {foo: 'bar'}) { return foo + bar; });",
394+
output: "qux((foo = 1, [bar = 2] = [], {qux: baz = 3} = {foo: 'bar'}) => { return foo + bar; });",
395+
errors,
396+
},
397+
{
398+
code: "qux(function(baz:string, baz:string) { })",
399+
output: null, // Duplicate parameter names are a SyntaxError in arrow functions
400+
errors,
401+
},
402+
{
403+
code: "qux(function( /* no params */ ) { })",
404+
output: "qux(( /* no params */ ) => { })",
405+
errors,
406+
},
407+
{
408+
code: "qux(function( /* a */ foo:string /* b */ , /* c */ bar:string /* d */ , /* e */ baz:string /* f */ ) { return foo; })",
409+
output: "qux(( /* a */ foo:string /* b */ , /* c */ bar:string /* d */ , /* e */ baz:string /* f */ ) => { return foo; })",
410+
errors,
411+
},
412+
{
413+
code: "qux(async function (foo:number = 1, bar:number = 2, baz:number = 3) { return baz; })",
414+
output: "qux(async (foo:number = 1, bar:number = 2, baz:number = 3) => { return baz; })",
415+
errors,
416+
},
417+
{
418+
code: "qux(async function (foo:number = 1, bar:number = 2, baz:number = 3) { return this; }.bind(this))",
419+
output: "qux(async (foo:number = 1, bar:number = 2, baz:number = 3) => { return this; })",
420+
errors,
421+
},
422+
{
423+
code: "foo((bar || function() {}).bind(this))",
424+
output: null,
425+
errors,
426+
},
427+
{
428+
code: "foo(function() {}.bind(this).bind(obj))",
429+
output: "foo((() => {}).bind(obj))",
430+
errors,
431+
},
432+
433+
// Optional chaining
434+
{
435+
code: "foo?.(function() {});",
436+
output: "foo?.(() => {});",
437+
errors,
438+
},
439+
{
440+
code: "foo?.(function() { return this; }.bind(this));",
441+
output: "foo?.(() => { return this; });",
442+
errors,
443+
},
444+
{
445+
code: "foo(function() { return this; }?.bind(this));",
446+
output: "foo(() => { return this; });",
447+
errors,
448+
},
449+
{
450+
code: "foo((function() { return this; }?.bind)(this));",
451+
output: null,
452+
errors,
453+
},
454+
455+
// https://github.com/eslint/eslint/issues/16718
456+
{
457+
code: `
458+
test(
459+
function ()
460+
{ }
461+
);
462+
`,
463+
output: `
464+
test(
465+
() =>
466+
{ }
467+
);
468+
`,
469+
errors,
470+
},
471+
{
472+
code: `
473+
test(
474+
function (
475+
...args
476+
) /* Lorem ipsum
477+
dolor sit amet. */ {
478+
return args;
479+
}
480+
);
481+
`,
482+
output: `
483+
test(
484+
(
485+
...args
486+
) => /* Lorem ipsum
487+
dolor sit amet. */ {
488+
return args;
489+
}
490+
);
491+
`,
492+
errors,
493+
},
494+
{
495+
code: "foo(function():string { return 'foo' });",
496+
output: "foo(():string => { return 'foo' });",
497+
errors,
498+
},
499+
{
500+
code: "test('foo', function (this: any) {});",
501+
output: null,
502+
errors,
503+
},
504+
],
505+
});

0 commit comments

Comments
 (0)