Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5117057
feat: port rule @typescript-eslint/max-params
ScriptedAlchemy Feb 20, 2026
703e7a6
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Feb 25, 2026
94ffd14
fix(typescript/max-params): align signature node parity
ScriptedAlchemy Feb 25, 2026
536b1db
fix: support max-params shorthand options
ScriptedAlchemy Feb 26, 2026
e45bebe
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Mar 3, 2026
e3207fc
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Mar 4, 2026
81aeb77
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Mar 5, 2026
5ce5760
Merge remote-tracking branch 'origin/main' into codex/ts-eslint-max-p…
ScriptedAlchemy Mar 11, 2026
a30a61d
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Mar 11, 2026
5fb9856
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Mar 12, 2026
4c41a9a
Merge remote-tracking branch 'origin/main' into codex/ts-eslint-max-p…
ScriptedAlchemy Mar 12, 2026
06ebc85
fix: cover ts signature nodes in max-params
ScriptedAlchemy Mar 12, 2026
bc7530d
Merge remote-tracking branch 'origin/main' into codex/ts-eslint-max-p…
ScriptedAlchemy Mar 12, 2026
566c0ab
Merge remote-tracking branch 'origin/main' into codex/ts-eslint-max-p…
ScriptedAlchemy Mar 12, 2026
999cf30
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Mar 12, 2026
c2c1093
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Mar 12, 2026
d4d2968
Merge remote-tracking branch 'origin/codex/ts-eslint-max-params-ac37'…
ScriptedAlchemy Mar 13, 2026
066aabd
fix: align max-params diagnostics with upstream
ScriptedAlchemy Mar 13, 2026
d7473aa
Merge remote-tracking branch 'origin/main' into codex/ts-eslint-max-p…
ScriptedAlchemy Mar 16, 2026
0826bbf
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Mar 17, 2026
98a7de3
Merge remote-tracking branch 'origin/main' into codex/ts-eslint-max-p…
ScriptedAlchemy Mar 19, 2026
a63a0ec
Merge branch 'main' into codex/ts-eslint-max-params-ac37
ScriptedAlchemy Mar 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/consistent_type_imports"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/default_param_last"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/dot_notation"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/max_params"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_array_constructor"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_array_delete"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_base_to_string"
Expand Down Expand Up @@ -391,6 +392,7 @@ func registerAllTypeScriptEslintPluginRules() {
GlobalRuleRegistry.Register("@typescript-eslint/consistent-type-imports", consistent_type_imports.ConsistentTypeImportsRule)
GlobalRuleRegistry.Register("@typescript-eslint/default-param-last", default_param_last.DefaultParamLastRule)
GlobalRuleRegistry.Register("@typescript-eslint/dot-notation", dot_notation.DotNotationRule)
GlobalRuleRegistry.Register("@typescript-eslint/max-params", max_params.MaxParamsRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-array-constructor", no_array_constructor.NoArrayConstructorRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-array-delete", no_array_delete.NoArrayDeleteRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-base-to-string", no_base_to_string.NoBaseToStringRule)
Expand Down
141 changes: 141 additions & 0 deletions internal/plugins/typescript/rules/max_params/max_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package max_params

import (
"fmt"

"github.com/microsoft/typescript-go/shim/ast"
"github.com/web-infra-dev/rslint/internal/rule"
)

type MaxParamsOptions struct {
Max int `json:"max"`
CountVoidThis bool `json:"countVoidThis"`
}

func parseNumericOption(value interface{}) (int, bool) {
switch v := value.(type) {
case int:
return v, true
case int32:
return int(v), true
case int64:
return int(v), true
case float32:
return int(v), true
case float64:
return int(v), true
default:
return 0, false
}
}

func parseOptions(options any) MaxParamsOptions {
opts := MaxParamsOptions{
Max: 3,
CountVoidThis: false,
}

if options == nil {
return opts
}

var optsMap map[string]interface{}
if arr, ok := options.([]interface{}); ok && len(arr) > 0 {
optsMap, _ = arr[0].(map[string]interface{})
} else {
optsMap, _ = options.(map[string]interface{})
}
Comment thread
ScriptedAlchemy marked this conversation as resolved.

if optsMap == nil {
return opts
}

hasMax := false
if value, ok := optsMap["max"]; ok {
if maxValue, ok := parseNumericOption(value); ok {
opts.Max = maxValue
hasMax = true
}
}
if !hasMax {
if value, ok := optsMap["maximum"]; ok {
if maxValue, ok := parseNumericOption(value); ok {
opts.Max = maxValue
}
}
}
if value, ok := optsMap["countVoidThis"]; ok {
if flag, ok := value.(bool); ok {
opts.CountVoidThis = flag
}
}

return opts
}

func isVoidThisParameter(param *ast.Node) bool {
if param == nil || !ast.IsParameter(param) {
return false
}

decl := param.AsParameterDeclaration()
if decl == nil {
return false
}

name := decl.Name()
if name == nil || name.Kind != ast.KindIdentifier {
return false
}

if name.AsIdentifier().Text != "this" {
return false
}

return decl.Type != nil && decl.Type.Kind == ast.KindVoidKeyword
}

func buildExceedMessage(count int, maxCount int) rule.RuleMessage {
return rule.RuleMessage{
Id: "exceed",
Description: fmt.Sprintf("Function has too many parameters (%d). Maximum allowed is %d.", count, maxCount),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original ESLint core max-params rule uses a dynamic name in the message — e.g. "Function 'foo'...", "Arrow function...", "Method 'bar'..." depending on the function type. Currently this is hardcoded as "Function" for all cases.

It might be worth generating the appropriate name based on the node kind to stay consistent with the upstream behavior. Something like:

  • KindFunctionDeclarationFunction 'name'
  • KindArrowFunctionArrow function
  • KindMethodDeclarationMethod 'name'
  • KindConstructorConstructor
  • KindGetAccessorGetter 'name'
  • KindSetAccessorSetter 'name'
  • KindFunctionTypeFunction type

Not a blocker, but would be nice to align!

}
}

var MaxParamsRule = rule.CreateRule(rule.Rule{
Name: "max-params",
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
opts := parseOptions(options)

checkParameters := func(node *ast.Node) {
params := node.Parameters()
if params == nil {
return
}

count := len(params)
if !opts.CountVoidThis && count > 0 && isVoidThisParameter(params[0]) {
count--
}

if count > opts.Max {
ctx.ReportNode(node, buildExceedMessage(count, opts.Max))
}
}

return rule.RuleListeners{
ast.KindFunctionDeclaration: checkParameters,
ast.KindFunctionExpression: checkParameters,
ast.KindArrowFunction: checkParameters,
ast.KindMethodDeclaration: checkParameters,
ast.KindMethodSignature: checkParameters,
ast.KindConstructor: checkParameters,
ast.KindGetAccessor: checkParameters,
ast.KindSetAccessor: checkParameters,
ast.KindCallSignature: checkParameters,
ast.KindConstructSignature: checkParameters,
ast.KindFunctionType: checkParameters,
Comment thread
ScriptedAlchemy marked this conversation as resolved.
ast.KindConstructorType: checkParameters,
}
},
})
29 changes: 29 additions & 0 deletions internal/plugins/typescript/rules/max_params/max_params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# max-params

## Rule Details

Enforce a maximum number of parameters in function-like declarations.

Comment thread
ScriptedAlchemy marked this conversation as resolved.
Examples of **incorrect** code for this rule:

```typescript
function foo(a, b, c, d) {}

class Foo {
method(this: Foo, a, b, c) {}
}
```

Examples of **correct** code for this rule:

```typescript
function foo(a, b, c) {}

class Foo {
method(this: void, a, b, c) {}
}
```

## Original Documentation

https://typescript-eslint.io/rules/max-params
157 changes: 157 additions & 0 deletions internal/plugins/typescript/rules/max_params/max_params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package max_params

import (
"testing"

"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/fixtures"
"github.com/web-infra-dev/rslint/internal/rule_tester"
)

func TestMaxParamsRule(t *testing.T) {
rule_tester.RunRuleTester(fixtures.GetRootDir(), "tsconfig.json", t, &MaxParamsRule, []rule_tester.ValidTestCase{
{Code: `function foo() {}`},
{Code: `const foo = function () {};`},
{Code: `const foo = () => {};`},
{Code: `function foo(a) {}`},
{
Code: `
class Foo {
constructor(a) {}
}
`,
},
{
Code: `
class Foo {
method(this: void, a, b, c) {}
}
`,
},
{
Code: `
class Foo {
method(this: Foo, a, b) {}
}
`,
},
{
Code: `function foo(a, b, c, d) {}`,
Options: []interface{}{map[string]interface{}{"max": 4}},
},
{
Code: `function foo(a, b, c, d) {}`,
Options: []interface{}{map[string]interface{}{"maximum": 4}},
},
{
Code: `
class Foo {
method(this: void) {}
}
`,
Options: []interface{}{map[string]interface{}{"max": 0}},
},
{
Code: `
class Foo {
method(this: void, a) {}
}
`,
Options: []interface{}{map[string]interface{}{"max": 1}},
},
{
Code: `
class Foo {
method(this: void, a) {}
}
`,
Options: []interface{}{map[string]interface{}{"countVoidThis": true, "max": 2}},
},
{
Code: `
declare function makeDate(m: number, d: number, y: number): Date;
`,
Options: []interface{}{map[string]interface{}{"max": 3}},
},
{
Code: `
type sum = (a: number, b: number) => number;
`,
Options: []interface{}{map[string]interface{}{"max": 2}},
},
}, []rule_tester.InvalidTestCase{
{
Code: `function foo(a, b, c, d) {}`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "exceed", Line: 1, Column: 1, EndLine: 1, EndColumn: 28},
},
},
{
Code: `const foo = function (a, b, c, d) {};`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "exceed", Line: 1, Column: 13, EndLine: 1, EndColumn: 37},
},
},
{
Code: `const foo = (a, b, c, d) => {};`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "exceed", Line: 1, Column: 13, EndLine: 1, EndColumn: 31},
},
},
{
Code: `const foo = a => {};`,
Options: []interface{}{map[string]interface{}{"max": 0}},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "exceed", Line: 1, Column: 13, EndLine: 1, EndColumn: 20},
},
},
{
Code: `
class Foo {
method(this: void, a, b, c, d) {}
}
`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "exceed", Line: 3, Column: 3, EndLine: 3, EndColumn: 36},
},
},
{
Code: `
class Foo {
method(this: void, a) {}
}
`,
Options: []interface{}{map[string]interface{}{"countVoidThis": true, "max": 1}},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "exceed", Line: 3, Column: 3, EndLine: 3, EndColumn: 27},
},
},
{
Code: `
class Foo {
method(this: Foo, a, b, c) {}
}
`,
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "exceed", Line: 3, Column: 3, EndLine: 3, EndColumn: 32},
},
},
{
Code: `
declare function makeDate(m: number, d: number, y: number): Date;
`,
Options: []interface{}{map[string]interface{}{"max": 1}},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "exceed", Line: 2, Column: 1, EndLine: 2, EndColumn: 66},
},
},
{
Code: `
type sum = (a: number, b: number) => number;
`,
Options: []interface{}{map[string]interface{}{"max": 1}},
Errors: []rule_tester.InvalidTestCaseError{
{MessageId: "exceed", Line: 2, Column: 12, EndLine: 2, EndColumn: 44},
},
},
})
}
2 changes: 1 addition & 1 deletion packages/rslint-test-tools/rstest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default defineConfig({
// './tests/typescript-eslint/rules/explicit-member-accessibility.test.ts',
// './tests/typescript-eslint/rules/explicit-module-boundary-types.test.ts',
// './tests/typescript-eslint/rules/init-declarations.test.ts',
// './tests/typescript-eslint/rules/max-params.test.ts',
'./tests/typescript-eslint/rules/max-params.test.ts',
// './tests/typescript-eslint/rules/member-ordering.test.ts',
// './tests/typescript-eslint/rules/member-ordering/member-ordering-alphabetically-case-insensitive-order.test.ts',
// './tests/typescript-eslint/rules/member-ordering/member-ordering-alphabetically-order.test.ts',
Expand Down
Loading
Loading