Skip to content

Commit

Permalink
feat(eslint-plugin): [max-params] don't count this parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
StyleShit committed Sep 27, 2023
1 parent 91a3e0c commit 2779fe8
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 0 deletions.
10 changes: 10 additions & 0 deletions packages/eslint-plugin/docs/rules/max-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
description: 'Enforce a maximum number of parameters in function definitions'
---

> 🛑 This file is source code, not the primary documentation location! 🛑
>
> See **https://typescript-eslint.io/rules/max-params** for documentation.
This rule extends the base [`eslint/max-params`](https://eslint.org/docs/rules/max-params) rule.
This version adds support for TypeScript `this` parameter so it won't be counted as a parameter.
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export = {
'@typescript-eslint/lines-around-comment': 'error',
'lines-between-class-members': 'off',
'@typescript-eslint/lines-between-class-members': 'error',
'max-params': 'off',
'@typescript-eslint/max-params': 'error',
'@typescript-eslint/member-delimiter-style': 'error',
'@typescript-eslint/member-ordering': 'error',
'@typescript-eslint/method-signature-style': 'error',
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import keySpacing from './key-spacing';
import keywordSpacing from './keyword-spacing';
import linesAroundComment from './lines-around-comment';
import linesBetweenClassMembers from './lines-between-class-members';
import maxParams from './max-params';
import memberDelimiterStyle from './member-delimiter-style';
import memberOrdering from './member-ordering';
import methodSignatureStyle from './method-signature-style';
Expand Down Expand Up @@ -163,6 +164,7 @@ export default {
'keyword-spacing': keywordSpacing,
'lines-around-comment': linesAroundComment,
'lines-between-class-members': linesBetweenClassMembers,
'max-params': maxParams,
'member-delimiter-style': memberDelimiterStyle,
'member-ordering': memberOrdering,
'method-signature-style': methodSignatureStyle,
Expand Down
99 changes: 99 additions & 0 deletions packages/eslint-plugin/src/rules/max-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema';

import * as util from '../util';
import { getESLintCoreRule } from '../util/getESLintCoreRule';

type FunctionLike =
| TSESTree.FunctionDeclaration
| TSESTree.FunctionExpression
| TSESTree.ArrowFunctionExpression;

type FunctionRuleListener<T extends FunctionLike> = (node: T) => void;

const baseRule = getESLintCoreRule('max-params');

export type Options = util.InferOptionsTypeFromRule<typeof baseRule>;
export type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;

const schema = Object.values(
util.deepMerge(
{ ...baseRule.meta.schema },
{
0: {
oneOf: [
baseRule.meta.schema[0].oneOf[0],
{
...baseRule.meta.schema[0].oneOf[1],
properties: {
...baseRule.meta.schema[0].oneOf[1].properties,
countVoidThis: {
type: 'boolean',
},
},
},
],
},
},
),
) as JSONSchema4[];

export default util.createRule<Options, MessageIds>({
name: 'max-params',
meta: {
type: 'suggestion',
docs: {
description:
'Enforce a maximum number of parameters in function definitions',
extendsBaseRule: true,
},
schema,
messages: baseRule.meta.messages,
},
defaultOptions: [{ max: 3, countVoidThis: false }],

create(context, [{ countVoidThis }]) {
const baseRules = baseRule.create(context);

if (countVoidThis === true) {
return baseRules;
}

const removeVoidThisParam = <T extends FunctionLike>(node: T): T => {
if (node.params.length === 0) {
return node;
}

const params = [...node.params];

if (
params[0] &&
params[0].type === AST_NODE_TYPES.Identifier &&
params[0].name === 'this' &&
params[0].typeAnnotation?.typeAnnotation.type ===
AST_NODE_TYPES.TSVoidKeyword
) {
params.shift();
}

return {
...node,
params,
};
};

const wrapListener = <T extends FunctionLike>(
listener: FunctionRuleListener<T>,
): FunctionRuleListener<T> => {
return (node: T): void => {
listener(removeVoidThisParam(node));
};
};

return {
ArrowFunctionExpression: wrapListener(baseRules.ArrowFunctionExpression),
FunctionDeclaration: wrapListener(baseRules.FunctionDeclaration),
FunctionExpression: wrapListener(baseRules.FunctionExpression),
};
},
});
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/util/getESLintCoreRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface RuleMap {
'keyword-spacing': typeof import('eslint/lib/rules/keyword-spacing');
'lines-around-comment': typeof import('eslint/lib/rules/lines-around-comment');
'lines-between-class-members': typeof import('eslint/lib/rules/lines-between-class-members');
'max-params': typeof import('eslint/lib/rules/max-params');
'no-dupe-args': typeof import('eslint/lib/rules/no-dupe-args');
'no-dupe-class-members': typeof import('eslint/lib/rules/no-dupe-class-members');
'no-empty-function': typeof import('eslint/lib/rules/no-empty-function');
Expand Down
104 changes: 104 additions & 0 deletions packages/eslint-plugin/tests/rules/max-params.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { RuleTester } from '@typescript-eslint/rule-tester';

import rule from '../../src/rules/max-params';

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
});

ruleTester.run('max-params', rule, {
valid: [
'function foo() {}',
'const foo = function () {};',
'const foo = () => {};',
'function foo(a) {}',
`
class Foo {
constructor(a) {}
}
`,
`
class Foo {
method(this: void, a, b, c) {}
}
`,
`
class Foo {
method(this: Foo, a, b) {}
}
`,
{
code: 'function foo(a, b, c, d) {}',
options: [{ max: 4 }],
},
{
code: 'function foo(a, b, c, d) {}',
options: [{ maximum: 4 }],
},
{
code: `
class Foo {
method(this: void) {}
}
`,
options: [{ max: 0 }],
},
{
code: `
class Foo {
method(this: void, a) {}
}
`,
options: [{ max: 1 }],
},
{
code: `
class Foo {
method(this: void, a) {}
}
`,
options: [{ max: 2, countVoidThis: true }],
},
],
invalid: [
{ code: 'function foo(a, b, c, d) {}', errors: [{ messageId: 'exceed' }] },
{
code: 'const foo = function (a, b, c, d) {};',
errors: [{ messageId: 'exceed' }],
},
{
code: 'const foo = (a, b, c, d) => {};',
errors: [{ messageId: 'exceed' }],
},
{
code: 'const foo = a => {};',
options: [{ max: 0 }],
errors: [{ messageId: 'exceed' }],
},
{
code: `
class Foo {
method(this: void, a, b, c, d) {}
}
`,
errors: [{ messageId: 'exceed' }],
},
{
code: `
class Foo {
method(this: void, a) {}
}
`,
options: [{ max: 1, countVoidThis: true }],
errors: [{ messageId: 'exceed' }],
},
{
code: `
class Foo {
method(this: Foo, a, b, c) {}
}
`,
errors: [{ messageId: 'exceed' }],
},
],
});
18 changes: 18 additions & 0 deletions packages/eslint-plugin/typings/eslint-rules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,24 @@ declare module 'eslint/lib/rules/keyword-spacing' {
export = rule;
}

declare module 'eslint/lib/rules/max-params' {
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';

const rule: TSESLint.RuleModule<
'exceed',
(
| { max: number; countVoidThis?: boolean }
| { maximum: number; countVoidThis?: boolean }
)[],
{
FunctionDeclaration(node: TSESTree.FunctionDeclaration): void;
FunctionExpression(node: TSESTree.FunctionExpression): void;
ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression): void;
}
>;
export = rule;
}

declare module 'eslint/lib/rules/no-dupe-class-members' {
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';

Expand Down

0 comments on commit 2779fe8

Please sign in to comment.