Skip to content

Commit 32957cd

Browse files
feat: support TS syntax in max-params (#19557)
* feat: support TS syntax in `max-params` * docs: add countVoidThis option * docs: add countVoidThis option * chore: fix lint * docs: udpate examples * docs: revert formatting * refactor: simplify logic * refactor: simplify logic * docs: fix examples * docs: update docs/src/rules/max-params.md Co-authored-by: Milos Djermanovic <[email protected]> * docs: update types * test: add more test cases * test: add more test cases * chore: update tests * chore: remove unwanted changes --------- Co-authored-by: Milos Djermanovic <[email protected]>
1 parent 35304dd commit 32957cd

4 files changed

Lines changed: 260 additions & 7 deletions

File tree

docs/src/rules/max-params.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ This rule enforces a maximum number of parameters allowed in function definition
2929
This rule has a number or object option:
3030

3131
* `"max"` (default `3`) enforces a maximum number of parameters in function definitions
32+
* `"countVoidThis"` (default `false`) counts a `this` declaration when the type is `void` (TypeScript only)
3233

3334
**Deprecated:** The object property `maximum` is deprecated; please use the object property `max` instead.
3435

@@ -69,3 +70,63 @@ let foo2 = (bar, baz, qux) => {
6970
```
7071

7172
:::
73+
74+
### countVoidThis (TypeScript only)
75+
76+
This rule has a TypeScript-specific option `countVoidThis` that allows you to count a `this` declaration when the type is `void`.
77+
78+
Examples of **correct** TypeScript code for this rule with the default `{ "countVoidThis": false }` option:
79+
80+
:::correct
81+
82+
```ts
83+
/*eslint max-params: ["error", { "max": 2, "countVoidThis": false }]*/
84+
85+
function hasNoThis(this: void, first: string, second: string) {
86+
// ...
87+
}
88+
```
89+
90+
:::
91+
92+
Examples of **incorrect** TypeScript code for this rule with the default `{ "countVoidThis": false }` option:
93+
94+
:::incorrect
95+
96+
```ts
97+
/*eslint max-params: ["error", { "max": 2, "countVoidThis": false }]*/
98+
99+
function hasNoThis(this: void, first: string, second: string, third: string) {
100+
// ...
101+
}
102+
```
103+
104+
:::
105+
106+
Examples of **correct** TypeScript code for this rule with the `{ "countVoidThis": true }` option:
107+
108+
:::correct
109+
110+
```ts
111+
/*eslint max-params: ["error", { "max": 2, "countVoidThis": true }]*/
112+
113+
function hasNoThis(this: void, first: string) {
114+
// ...
115+
}
116+
```
117+
118+
:::
119+
120+
Examples of **incorrect** TypeScript code for this rule with the `{ "countVoidThis": true }` option:
121+
122+
:::incorrect
123+
124+
```ts
125+
/*eslint max-params: ["error", { "max": 2, "countVoidThis": true }]*/
126+
127+
function hasNoThis(this: void, first: string, second: string) {
128+
// ...
129+
}
130+
```
131+
132+
:::

lib/rules/max-params.js

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const { upperCaseFirst } = require("../shared/string-utils");
2020
module.exports = {
2121
meta: {
2222
type: "suggestion",
23+
dialects: ["typescript", "javascript"],
24+
language: "javascript",
2325

2426
docs: {
2527
description:
@@ -46,6 +48,11 @@ module.exports = {
4648
type: "integer",
4749
minimum: 0,
4850
},
51+
countVoidThis: {
52+
type: "boolean",
53+
description:
54+
"Whether to count a `this` declaration when the type is `void`.",
55+
},
4956
},
5057
additionalProperties: false,
5158
},
@@ -61,12 +68,16 @@ module.exports = {
6168
const sourceCode = context.sourceCode;
6269
const option = context.options[0];
6370
let numParams = 3;
71+
let countVoidThis = false;
6472

65-
if (
66-
typeof option === "object" &&
67-
(Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max"))
68-
) {
69-
numParams = option.maximum || option.max;
73+
if (typeof option === "object") {
74+
if (
75+
Object.hasOwn(option, "maximum") ||
76+
Object.hasOwn(option, "max")
77+
) {
78+
numParams = option.maximum || option.max;
79+
}
80+
countVoidThis = option.countVoidThis;
7081
}
7182
if (typeof option === "number") {
7283
numParams = option;
@@ -79,7 +90,19 @@ module.exports = {
7990
* @private
8091
*/
8192
function checkFunction(node) {
82-
if (node.params.length > numParams) {
93+
const hasVoidThisParam =
94+
node.params.length > 0 &&
95+
node.params[0].type === "Identifier" &&
96+
node.params[0].name === "this" &&
97+
node.params[0].typeAnnotation?.typeAnnotation.type ===
98+
"TSVoidKeyword";
99+
100+
const effectiveParamCount =
101+
hasVoidThisParam && !countVoidThis
102+
? node.params.length - 1
103+
: node.params.length;
104+
105+
if (effectiveParamCount > numParams) {
83106
context.report({
84107
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
85108
node,
@@ -88,7 +111,7 @@ module.exports = {
88111
name: upperCaseFirst(
89112
astUtils.getFunctionNameWithKind(node),
90113
),
91-
count: node.params.length,
114+
count: effectiveParamCount,
92115
max: numParams,
93116
},
94117
});
@@ -99,6 +122,8 @@ module.exports = {
99122
FunctionDeclaration: checkFunction,
100123
ArrowFunctionExpression: checkFunction,
101124
FunctionExpression: checkFunction,
125+
TSDeclareFunction: checkFunction,
126+
TSFunctionType: checkFunction,
102127
};
103128
},
104129
};

lib/types/rules.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1800,6 +1800,10 @@ export interface ESLintRules extends Linter.RulesRecord {
18001800
* @default 3
18011801
*/
18021802
max: number;
1803+
/**
1804+
* @default false
1805+
*/
1806+
countVoidThis: boolean;
18031807
}>
18041808
| number,
18051809
]

tests/lib/rules/max-params.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,166 @@ ruleTester.run("max-params", rule, {
151151
},
152152
],
153153
});
154+
155+
const ruleTesterTypeScript = new RuleTester({
156+
languageOptions: {
157+
parser: require("@typescript-eslint/parser"),
158+
},
159+
});
160+
161+
ruleTesterTypeScript.run("max-params", rule, {
162+
valid: [
163+
"function foo() {}",
164+
"const foo = function () {};",
165+
"const foo = () => {};",
166+
"function foo(a) {}",
167+
`
168+
class Foo {
169+
constructor(a) {}
170+
}
171+
`,
172+
`
173+
class Foo {
174+
method(this: void, a, b, c) {}
175+
}
176+
`,
177+
`
178+
class Foo {
179+
method(this: Foo, a, b) {}
180+
}
181+
`,
182+
{
183+
code: "function foo(a, b, c, d) {}",
184+
options: [{ max: 4 }],
185+
},
186+
{
187+
code: "function foo(a, b, c, d) {}",
188+
options: [{ maximum: 4 }],
189+
},
190+
{
191+
code: `
192+
class Foo {
193+
method(this: void) {}
194+
}
195+
`,
196+
options: [{ max: 0 }],
197+
},
198+
{
199+
code: `
200+
class Foo {
201+
method(this: void, a) {}
202+
}
203+
`,
204+
options: [{ max: 1 }],
205+
},
206+
{
207+
code: `
208+
class Foo {
209+
method(this: void, a) {}
210+
}
211+
`,
212+
options: [{ countVoidThis: true, max: 2 }],
213+
},
214+
{
215+
code: `function testD(this: void, a) {}`,
216+
options: [{ max: 1 }],
217+
},
218+
{
219+
code: `function testD(this: void, a) {}`,
220+
options: [{ countVoidThis: true, max: 2 }],
221+
},
222+
{
223+
code: `const testE = function (this: void, a) {}`,
224+
options: [{ max: 1 }],
225+
},
226+
{
227+
code: `const testE = function (this: void, a) {}`,
228+
options: [{ countVoidThis: true, max: 2 }],
229+
},
230+
{
231+
code: `
232+
declare function makeDate(m: number, d: number, y: number): Date;
233+
`,
234+
options: [{ max: 3 }],
235+
},
236+
{
237+
code: `
238+
type sum = (a: number, b: number) => number;
239+
`,
240+
options: [{ max: 2 }],
241+
},
242+
],
243+
invalid: [
244+
{
245+
code: "function foo(a, b, c, d) {}",
246+
errors: [{ messageId: "exceed" }],
247+
},
248+
{
249+
code: "const foo = function (a, b, c, d) {};",
250+
errors: [{ messageId: "exceed" }],
251+
},
252+
{
253+
code: "const foo = (a, b, c, d) => {};",
254+
errors: [{ messageId: "exceed" }],
255+
},
256+
{
257+
code: "const foo = a => {};",
258+
options: [{ max: 0 }],
259+
errors: [{ messageId: "exceed" }],
260+
},
261+
{
262+
code: `
263+
class Foo {
264+
method(this: void, a, b, c, d) {}
265+
}
266+
`,
267+
errors: [{ messageId: "exceed" }],
268+
},
269+
{
270+
code: `
271+
class Foo {
272+
method(this: void, a) {}
273+
}
274+
`,
275+
options: [{ countVoidThis: true, max: 1 }],
276+
errors: [{ messageId: "exceed" }],
277+
},
278+
{
279+
code: `function testD(this: void, a) {}`,
280+
options: [{ countVoidThis: true, max: 1 }],
281+
errors: [{ messageId: "exceed" }],
282+
},
283+
{
284+
code: `const testE = function (this: void, a) {}`,
285+
options: [{ countVoidThis: true, max: 1 }],
286+
errors: [{ messageId: "exceed" }],
287+
},
288+
{
289+
code: `function testFunction(test: void, a: number) {}`,
290+
options: [{ countVoidThis: false, max: 1 }],
291+
errors: [{ messageId: "exceed" }],
292+
},
293+
{
294+
code: `
295+
class Foo {
296+
method(this: Foo, a, b, c) {}
297+
}
298+
`,
299+
errors: [{ messageId: "exceed" }],
300+
},
301+
{
302+
code: `
303+
declare function makeDate(m: number, d: number, y: number): Date;
304+
`,
305+
options: [{ max: 1 }],
306+
errors: [{ messageId: "exceed" }],
307+
},
308+
{
309+
code: `
310+
type sum = (a: number, b: number) => number;
311+
`,
312+
options: [{ max: 1 }],
313+
errors: [{ messageId: "exceed" }],
314+
},
315+
],
316+
});

0 commit comments

Comments
 (0)