Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
)

type ClassMethodsUseThisOptions struct {
ExceptMethods []string `json:"exceptMethods"`
EnforceForClassFields bool `json:"enforceForClassFields"`
ExceptMethods []string `json:"exceptMethods"`
EnforceForClassFields bool `json:"enforceForClassFields"`
IgnoreClassesThatImplementAnInterface interface{} `json:"ignoreClassesThatImplementAnInterface"`
IgnoreOverrideMethods bool `json:"ignoreOverrideMethods"`
}

type scopeInfo struct {
Expand All @@ -23,6 +25,8 @@ var ClassMethodsUseThisRule = rule.CreateRule(rule.Rule{
opts := ClassMethodsUseThisOptions{
ExceptMethods: []string{},
EnforceForClassFields: true,
IgnoreClassesThatImplementAnInterface: false,
IgnoreOverrideMethods: false,
}

// Parse options
Expand All @@ -47,6 +51,12 @@ var ClassMethodsUseThisRule = rule.CreateRule(rule.Rule{
if enforceForClassFields, ok := optsMap["enforceForClassFields"].(bool); ok {
opts.EnforceForClassFields = enforceForClassFields
}
if ignoreClasses, ok := optsMap["ignoreClassesThatImplementAnInterface"]; ok {
opts.IgnoreClassesThatImplementAnInterface = ignoreClasses
}
if ignoreOverride, ok := optsMap["ignoreOverrideMethods"].(bool); ok {
opts.IgnoreOverrideMethods = ignoreOverride
}
}
}

Expand All @@ -72,6 +82,101 @@ var ClassMethodsUseThisRule = rule.CreateRule(rule.Rule{
return false
}

// Helper to get the parent class node
getParentClass := func(node *ast.Node) *ast.Node {
current := node.Parent
for current != nil {
if current.Kind == ast.KindClassDeclaration || current.Kind == ast.KindClassExpression {
return current
}
current = current.Parent
}
return nil
}

// Helper to check if a class implements an interface
classImplementsInterface := func(classNode *ast.Node) bool {
if classNode == nil {
return false
}

heritageClauses := utils.GetHeritageClauses(classNode)
if heritageClauses == nil || len(heritageClauses.Nodes) == 0 {
return false
}

for _, clauseNode := range heritageClauses.Nodes {
clause := clauseNode.AsHeritageClause()
if clause != nil && clause.Token == ast.KindImplementsKeyword {
return true
}
}
return false
}

// Helper to check if member should be ignored based on ignoreClassesThatImplementAnInterface option
shouldIgnoreInterfaceImpl := func(node *ast.Node) bool {
if opts.IgnoreClassesThatImplementAnInterface == nil || opts.IgnoreClassesThatImplementAnInterface == false {
return false
}

classNode := getParentClass(node)
if !classImplementsInterface(classNode) {
return false
}

// If option is true, ignore all members of classes that implement interfaces
if boolVal, ok := opts.IgnoreClassesThatImplementAnInterface.(bool); ok && boolVal {
return true
}

// If option is "public-fields", only ignore public members
if strVal, ok := opts.IgnoreClassesThatImplementAnInterface.(string); ok && strVal == "public-fields" {
// Check if the member is private or protected
hasPrivateModifier := ast.HasSyntacticModifier(node, ast.ModifierFlagsPrivate)
hasProtectedModifier := ast.HasSyntacticModifier(node, ast.ModifierFlagsProtected)
isPrivateName := false

// Check if it's a private name (starts with #)
if node.Kind == ast.KindMethodDeclaration {
if method := node.AsMethodDeclaration(); method != nil && method.Name() != nil {
_, nameType := utils.GetNameFromMember(ctx.SourceFile, method.Name())
isPrivateName = nameType == utils.MemberNameTypePrivate
}
} else if node.Kind == ast.KindPropertyDeclaration {
if prop := node.AsPropertyDeclaration(); prop != nil && prop.Name() != nil {
_, nameType := utils.GetNameFromMember(ctx.SourceFile, prop.Name())
isPrivateName = nameType == utils.MemberNameTypePrivate
}
} else if node.Kind == ast.KindGetAccessor {
if accessor := node.AsGetAccessorDeclaration(); accessor != nil && accessor.Name() != nil {
_, nameType := utils.GetNameFromMember(ctx.SourceFile, accessor.Name())
isPrivateName = nameType == utils.MemberNameTypePrivate
}
} else if node.Kind == ast.KindSetAccessor {
if accessor := node.AsSetAccessorDeclaration(); accessor != nil && accessor.Name() != nil {
_, nameType := utils.GetNameFromMember(ctx.SourceFile, accessor.Name())
isPrivateName = nameType == utils.MemberNameTypePrivate
}
}

// If it's private or protected, don't ignore it (check it)
if hasPrivateModifier || hasProtectedModifier || isPrivateName {
return false
}

// It's a public member, so ignore it
return true
}

return false
}

// Helper to check if member has override modifier
hasOverrideModifier := func(node *ast.Node) bool {
return ast.HasSyntacticModifier(node, ast.ModifierFlagsOverride)
}

// Get method name for display
getMethodName := func(node *ast.Node) string {
if node.Kind == ast.KindMethodDeclaration {
Expand Down Expand Up @@ -178,6 +283,16 @@ var ClassMethodsUseThisRule = rule.CreateRule(rule.Rule{
return
}

// Skip if has override modifier and ignoreOverrideMethods is true
if opts.IgnoreOverrideMethods && hasOverrideModifier(node) {
return
}

// Skip if in a class that implements an interface and should be ignored
if shouldIgnoreInterfaceImpl(node) {
return
}

// Check if method is in except list
var methodName string
if node.Kind == ast.KindMethodDeclaration {
Expand Down Expand Up @@ -257,6 +372,16 @@ var ClassMethodsUseThisRule = rule.CreateRule(rule.Rule{
return
}

// Skip if has override modifier and ignoreOverrideMethods is true
if opts.IgnoreOverrideMethods && hasOverrideModifier(parent) {
return
}

// Skip if in a class that implements an interface and should be ignored
if shouldIgnoreInterfaceImpl(parent) {
return
}

// Check if property name is in except list
if prop.Name() != nil {
name, _ := utils.GetNameFromMember(ctx.SourceFile, prop.Name())
Expand Down
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 @@ -35,7 +35,7 @@ export default defineConfig({
'./tests/typescript-eslint/rules/ban-ts-comment.test.ts',
'./tests/typescript-eslint/rules/ban-tslint-comment.test.ts',
'./tests/typescript-eslint/rules/class-methods-use-this/class-methods-use-this-core.test.ts',
// './tests/typescript-eslint/rules/class-methods-use-this/class-methods-use-this.test.ts',
'./tests/typescript-eslint/rules/class-methods-use-this/class-methods-use-this.test.ts',
// './tests/typescript-eslint/rules/consistent-generic-constructors.test.ts',
// './tests/typescript-eslint/rules/consistent-indexed-object-style.test.ts',
// './tests/typescript-eslint/rules/consistent-return.test.ts',
Expand Down
Loading
Loading