8000 feat(eslint-plugin): [max-params] don't count `this` parameter · StyleShit/typescript-eslint@2779fe8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2779fe8

Browse files
committed
feat(eslint-plugin): [max-params] don't count this parameter
Closes typescript-eslint#7538
1 parent 91a3e0c commit 2779fe8

File tree

7 files changed

+236
-0
lines changed

7 files changed

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

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/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)
0