destructuring_assignment

package
v0.5.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 14, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DestructuringAssignmentRule = rule.Rule{
	Name: "react/destructuring-assignment",
	Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
		opts := parseOptions(options)
		pragma := reactutil.GetReactPragma(ctx.Settings)
		createClass := reactutil.GetReactCreateClass(ctx.Settings)
		wrappers := reactutil.GetComponentWrapperFunctions(ctx.Settings, pragma)
		stack := &sfcParamsStack{}

		reportUseDestruct := func(node *ast.Node, t string) {
			ctx.ReportNode(node, rule.RuleMessage{
				Id:          "useDestructAssignment",
				Description: "Must use destructuring " + t + " assignment",
				Data:        map[string]string{"type": t},
			})
		}

		reportNoDestruct := func(node *ast.Node, t string) {
			ctx.ReportNode(node, rule.RuleMessage{
				Id:          "noDestructAssignment",
				Description: "Must never use destructuring " + t + " assignment",
				Data:        map[string]string{"type": t},
			})
		}

		handleStatelessComponent := func(node *ast.Node) {
			if !reactutil.IsStatelessReactComponentWithWrappers(node, pragma, nil, wrappers) {
				return
			}
			params := evalParams(reactutil.FunctionParameters(node), ctx.TypeChecker)
			stack.push(params)
			if opts.configuration != "never" {
				return
			}
			if len(params) > 0 && params[0].destructuring {
				ctx.ReportNode(node, rule.RuleMessage{
					Id:          "noDestructPropsInSFCArg",
					Description: "Must never use destructuring props assignment in SFC argument",
				})
			} else if len(params) > 1 && params[1].destructuring {
				ctx.ReportNode(node, rule.RuleMessage{
					Id:          "noDestructContextInSFCArg",
					Description: "Must never use destructuring context assignment in SFC argument",
				})
			}
		}

		handleStatelessComponentExit := func(node *ast.Node) {
			if !reactutil.IsStatelessReactComponentWithWrappers(node, pragma, nil, wrappers) {
				return
			}
			stack.pop()
		}

		matchesSFCParam := buildSFCMatcher(ctx.TypeChecker)

		handleSFCUsage := func(node *ast.Node) {
			propsName := stack.propsName()
			contextName := stack.contextName()
			var objNode *ast.Node
			switch node.Kind {
			case ast.KindPropertyAccessExpression:
				objNode = node.AsPropertyAccessExpression().Expression
			case ast.KindElementAccessExpression:
				objNode = node.AsElementAccessExpression().Expression
			default:
				return
			}
			obj := ast.SkipParentheses(objNode)
			if obj.Kind != ast.KindIdentifier {
				return
			}
			objName := obj.AsIdentifier().Text

			matched := false
			if propsName != "" && objName == propsName &&
				matchesSFCParam(obj, stack.propsSymbol()) {
				matched = true
			} else if contextName != "" && objName == contextName &&
				matchesSFCParam(obj, stack.contextSymbol()) {
				matched = true
			}
			if !matched {
				return
			}
			if isAssignmentLHS(node) {
				return
			}
			if opts.configuration != "always" || isOptionalMember(node) {
				return
			}
			reportUseDestruct(node, objName)
		}

		handleClassUsage := func(node *ast.Node) {
			var objNode *ast.Node
			switch node.Kind {
			case ast.KindPropertyAccessExpression:
				objNode = node.AsPropertyAccessExpression().Expression
			case ast.KindElementAccessExpression:
				objNode = node.AsElementAccessExpression().Expression
			default:
				return
			}
			obj := ast.SkipParentheses(objNode)
			if obj.Kind != ast.KindPropertyAccessExpression {
				return
			}
			inner := obj.AsPropertyAccessExpression()
			if ast.SkipParentheses(inner.Expression).Kind != ast.KindThisKeyword {
				return
			}
			name := reactutil.EsTreeName(inner.Name())
			if name != "props" && name != "state" && name != "context" {
				return
			}
			if isAssignmentLHS(node) {
				return
			}
			if opts.configuration != "always" {
				return
			}
			if opts.ignoreClassFields && isInClassProperty(node) {
				return
			}
			reportUseDestruct(node, name)
		}

		memberExprListener := func(node *ast.Node) {
			if reactutil.GetParentStatelessComponent(node, pragma, wrappers) != nil {
				handleSFCUsage(node)
			}
			if reactutil.GetParentReactComponentScopeBasedOrStateless(node, pragma, createClass, wrappers) != nil {
				handleClassUsage(node)
			}
		}

		return rule.RuleListeners{

			ast.KindFunctionDeclaration:                      handleStatelessComponent,
			ast.KindFunctionExpression:                       handleStatelessComponent,
			ast.KindArrowFunction:                            handleStatelessComponent,
			ast.KindMethodDeclaration:                        handleStatelessComponent,
			rule.ListenerOnExit(ast.KindFunctionDeclaration): handleStatelessComponentExit,
			rule.ListenerOnExit(ast.KindFunctionExpression):  handleStatelessComponentExit,
			rule.ListenerOnExit(ast.KindArrowFunction):       handleStatelessComponentExit,
			rule.ListenerOnExit(ast.KindMethodDeclaration):   handleStatelessComponentExit,

			ast.KindPropertyAccessExpression: memberExprListener,
			ast.KindElementAccessExpression:  memberExprListener,

			ast.KindQualifiedName: func(node *ast.Node) {
				if opts.configuration != "always" {
					return
				}
				qn := node.AsQualifiedName()
				if qn.Left == nil || qn.Left.Kind != ast.KindIdentifier {
					return
				}
				propsName := stack.propsName()
				if propsName == "" || qn.Left.AsIdentifier().Text != propsName {
					return
				}
				if !findEnclosingTypeQuery(node) {
					return
				}
				if reactutil.GetParentStatelessComponent(node, pragma, wrappers) == nil {
					return
				}
				reportUseDestruct(node, "props")
			},

			ast.KindVariableDeclaration: func(node *ast.Node) {
				vd := node.AsVariableDeclaration()
				if vd.Initializer == nil {
					return
				}
				name := vd.Name()
				if name == nil || name.Kind != ast.KindObjectBindingPattern {
					return
				}
				init := ast.SkipParentheses(vd.Initializer)

				var sfcType string
				var classType string
				switch init.Kind {
				case ast.KindIdentifier:
					nm := init.AsIdentifier().Text
					if nm == "props" || nm == "context" {
						sfcType = nm
					}
				case ast.KindPropertyAccessExpression:
					pa := init.AsPropertyAccessExpression()
					if ast.SkipParentheses(pa.Expression).Kind == ast.KindThisKeyword {
						propName := reactutil.EsTreeName(pa.Name())
						if propName == "props" || propName == "context" || propName == "state" {
							classType = propName
						}
					}
				}

				sfcComp := getEnclosingSFCComponent(node, pragma, wrappers)

				classComp := reactutil.GetParentReactComponentScopeBasedOrStateless(node, pragma, createClass, wrappers)

				if opts.configuration == "never" {
					if sfcComp != nil && sfcType != "" {
						reportNoDestruct(node, sfcType)
					}
					if classComp != nil && classType != "" {
						if !opts.ignoreClassFields || !isParentClassProperty(node) {
							reportNoDestruct(node, classType)
						}
					}
				}

				if sfcComp != nil &&
					sfcType == "props" &&
					opts.configuration == "always" &&
					opts.destructureInSignature == "always" {
					params := reactutil.FunctionParameters(sfcComp)
					if len(params) == 0 {
						return
					}
					param := params[0]
					if param == nil || param.Kind != ast.KindParameter {
						return
					}
					pd := param.AsParameterDeclaration()
					paramName := pd.Name()

					if paramName == nil || paramName.Kind != ast.KindIdentifier ||
						paramName.AsIdentifier().Text != "props" {
						return
					}

					if countPropsRefsExcludingDecl(sfcComp, vd.Initializer, ctx.TypeChecker, paramName) > 0 {
						return
					}

					replaceRange := utils.TrimNodeTextRange(ctx.SourceFile, paramName)
					patternText := utils.TrimmedNodeText(ctx.SourceFile, name)

					removeTarget := node
					if node.Parent != nil && node.Parent.Kind == ast.KindVariableDeclarationList {
						list := node.Parent
						if list.Parent != nil && list.Parent.Kind == ast.KindVariableStatement {
							removeTarget = list.Parent
						}
					}
					ctx.ReportNodeWithFixes(node, rule.RuleMessage{
						Id:          "destructureInSignature",
						Description: "Must destructure props in the function signature.",
					},
						rule.RuleFixReplaceRange(replaceRange, patternText),
						rule.RuleFixRemove(ctx.SourceFile, removeTarget),
					)
				}
			},
		}
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL