Skip to content

Commit 6398d3f

Browse files
feat(eslint-plugin): [max-params] don't count this: void parameter (#7696)
* feat(eslint-plugin): [max-params] don't count `this: void` parameter Closes #7538 * hard-code schema * refactor * lint * snapshot * wip * Update packages/eslint-plugin/src/rules/max-params.ts Co-authored-by: Josh Goldberg ✨ <[email protected]> * Small PR nits, don't mind me, it's great either way --------- Co-authored-by: Josh Goldberg ✨ <[email protected]>
1 parent 7e52f27 commit 6398d3f

File tree

8 files changed

+267
-0
lines changed

8 files changed

+267
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
description: 'Enforce a maximum number of parameters in function definitions.'
3+
---
4+
5+
> 🛑 This file is source code, not the primary documentation location! 🛑
6+
>
7+
> See **https://typescript-eslint.io/rules/max-params** for documentation.
8+
9+
This rule extends the base [`eslint/max-params`](https://eslint.org/docs/rules/max-params) rule.
10+
This version adds support for TypeScript `this` parameters so they won't be counted as a parameter.

packages/eslint-plugin/src/configs/all.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export = {
5252
'@typescript-eslint/lines-around-comment': 'error',
5353
'lines-between-class-members': 'off',
5454
'@typescript-eslint/lines-between-class-members': 'error',
55+
'max-params': 'off',
56+
'@typescript-eslint/max-params': 'error',
5557
'@typescript-eslint/member-delimiter-style': 'error',
5658
'@typescript-eslint/member-ordering': 'error',
5759
'@typescript-eslint/method-signature-style': 'error',

packages/eslint-plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import keySpacing from './key-spacing';
2828
import keywordSpacing from './keyword-spacing';
2929
import linesAroundComment from './lines-around-comment';
3030
import linesBetweenClassMembers from './lines-between-class-members';
31+
import maxParams from './max-params';
3132
import memberDelimiterStyle from './member-delimiter-style';
3233
import memberOrdering from './member-ordering';
3334
import methodSignatureStyle from './method-signature-style';
@@ -164,6 +165,7 @@ export default {
164165
'keyword-spacing': keywordSpacing,
165166
'lines-around-comment': linesAroundComment,
166167
'lines-between-class-members': linesBetweenClassMembers,
168+
'max-params': maxParams,
167169
'member-delimiter-style': memberDelimiterStyle,
168170
'member-ordering': memberOrdering,
169171
'method-signature-style': methodSignatureStyle,
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
2+
3+
import type {
4+
InferMessageIdsTypeFromRule,
5+
InferOptionsTypeFromRule,
6+
} from '../util';
7+
import { createRule } from '../util';
8+
import { getESLintCoreRule } from '../util/getESLintCoreRule';
9+
10+
type FunctionLike =
11+
| TSESTree.FunctionDeclaration
12+
| TSESTree.FunctionExpression
13+
| TSESTree.ArrowFunctionExpression;
14+
15+
type FunctionRuleListener<T extends FunctionLike> = (node: T) => void;
16+
17+
const baseRule = getESLintCoreRule('max-params');
18+
19+
export type Options = InferOptionsTypeFromRule<typeof baseRule>;
20+
export type MessageIds = InferMessageIdsTypeFromRule<typeof baseRule>;
21+
22+
export default createRule<Options, MessageIds>({
23+
name: 'max-params',
24+
meta: {
25+
type: 'suggestion',
26+
docs: {
27+
description:
28+
'Enforce a maximum number of parameters in function definitions',
29+
extendsBaseRule: true,
30+
},
31+
schema: [
32+
{
33+
type: 'object',
34+
properties: {
35+
maximum: {
36+
type: 'integer',
37+
minimum: 0,
38+
},
39+
max: {
40+
type: 'integer',
41+
minimum: 0,
42+
},
43+
countVoidThis: {
44+
type: 'boolean',
45+
},
46+
},
47+
additionalProperties: false,
48+
},
49+
],
50+
messages: baseRule.meta.messages,
51+
},
52+
defaultOptions: [{ max: 3, countVoidThis: false }],
53+
54+
create(context, [{ countVoidThis }]) {
55+
const baseRules = baseRule.create(context);
56+
57+
if (countVoidThis === true) {
58+
return baseRules;
59+
}
60+
61+
const removeVoidThisParam = <T extends FunctionLike>(node: T): T => {
62+
if (
63+
node.params.length === 0 ||
64+
node.params[0].type !== AST_NODE_TYPES.Identifier ||
65+
node.params[0].name !== 'this' ||
66+
node.params[0].typeAnnotation?.typeAnnotation.type !==
67+
AST_NODE_TYPES.TSVoidKeyword
68+
) {
69+
return node;
70+
}
71+
72+
return {
73+
...node,
74+
params: node.params.slice(1),
75+
};
76+
};
77+
78+
const wrapListener = <T extends FunctionLike>(
79+
listener: FunctionRuleListener<T>,
80+
): FunctionRuleListener<T> => {
81+
return (node: T): void => {
82+
listener(removeVoidThisParam(node));
83+
};
84+
};
85+
86+
return {
87+
ArrowFunctionExpression: wrapListener(baseRules.ArrowFunctionExpression),
88+
FunctionDeclaration: wrapListener(baseRules.FunctionDeclaration),
89+
FunctionExpression: wrapListener(baseRules.FunctionExpression),
90+
};
91+
},
92+
});

packages/eslint-plugin/src/util/getESLintCoreRule.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface RuleMap {
1717
'keyword-spacing': typeof import('eslint/lib/rules/keyword-spacing');
1818
'lines-around-comment': typeof import('eslint/lib/rules/lines-around-comment');
1919
'lines-between-class-members': typeof import('eslint/lib/rules/lines-between-class-members');
20+
'max-params': typeof import('eslint/lib/rules/max-params');
2021
'no-dupe-args': typeof import('eslint/lib/rules/no-dupe-args');
2122
'no-dupe-class-members': typeof import('eslint/lib/rules/no-dupe-class-members');
2223
'no-empty-function': typeof import('eslint/lib/rules/no-empty-function');
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { RuleTester } from '@typescript-eslint/rule-tester';
2+
3+
import rule from '../../src/rules/max-params';
4+
5+
const ruleTester = new RuleTester({
6+
parser: '@typescript-eslint/parser',
7+
});
8+
9+
ruleTester.run('max-params', rule, {
10+
valid: [
11+
'function foo() {}',
12+
'const foo = function () {};',
13+
'const foo = () => {};',
14+
'function foo(a) {}',
15+
`
16+
class Foo {
17+
constructor(a) {}
18+
}
19+
`,
20+
`
21+
class Foo {
22+
method(this: void, a, b, c) {}
23+
}
24+
`,
25+
`
26+
class Foo {
27+
method(this: Foo, a, b) {}
28+
}
29+
`,
30+
{
31+
code: 'function foo(a, b, c, d) {}',
32+
options: [{ max: 4 }],
33+
},
34+
{
35+
code: 'function foo(a, b, c, d) {}',
36+
options: [{ maximum: 4 }],
37+
},
38+
{
39+
code: `
40+
class Foo {
41+
method(this: void) {}
42+
}
43+
`,
44+
options: [{ max: 0 }],
45+
},
46+
{
47+
code: `
48+
class Foo {
49+
method(this: void, a) {}
50+
}
51+
`,
52+
options: [{ max: 1 }],
53+
},
54+
{
55+
code: `
56+
class Foo {
57+
method(this: void, a) {}
58+
}
59+
`,
60+
options: [{ max: 2, countVoidThis: true }],
61+
},
62+
],
63+
invalid: [
64+
{ code: 'function foo(a, b, c, d) {}', errors: [{ messageId: 'exceed' }] },
65+
{
66+
code: 'const foo = function (a, b, c, d) {};',
67+
errors: [{ messageId: 'exceed' }],
68+
},
69+
{
70+
code: 'const foo = (a, b, c, d) => {};',
71+
errors: [{ messageId: 'exceed' }],
72+
},
73+
{
74+
code: 'const foo = a => {};',
75+
options: [{ max: 0 }],
76+
errors: [{ messageId: 'exceed' }],
77+
},
78+
{
79+
code: `
80+
class Foo {
81+
method(this: void, a, b, c, d) {}
82+
}
83+
`,
84+
errors: [{ messageId: 'exceed' }],
85+
},
86+
{
87+
code: `
88+
class Foo {
89+
method(this: void, a) {}
90+
}
91+
`,
92+
options: [{ max: 1, countVoidThis: true }],
93+
errors: [{ messageId: 'exceed' }],
94+
},
95+
{
96+
code: `
97+
class Foo {
98+
method(this: Foo, a, b, c) {}
99+
}
100+
`,
101+
errors: [{ messageId: 'exceed' }],
102+
},
103+
],
104+
});

packages/eslint-plugin/tests/schema-snapshots/max-params.shot

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/eslint-plugin/typings/eslint-rules.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,24 @@ declare module 'eslint/lib/rules/keyword-spacing' {
272272
export = rule;
273273
}
274274

275+
declare module 'eslint/lib/rules/max-params' {
276+
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
277+
278+
const rule: TSESLint.RuleModule<
279+
'exceed',
280+
(
281+
| { max: number; countVoidThis?: boolean }
282+
| { maximum: number; countVoidThis?: boolean }
283+
)[],
284+
{
285+
FunctionDeclaration(node: TSESTree.FunctionDeclaration): void;
286+
FunctionExpression(node: TSESTree.FunctionExpression): void;
287+
ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression): void;
288+
}
289+
>;
290+
export = rule;
291+
}
292+
275293
declare module 'eslint/lib/rules/no-dupe-class-members' {
276294
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
277295

0 commit comments

Comments
 (0)