Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var NoAccessStateInSetstateRule = rule.Rule{ Name: "react/no-access-state-in-setstate", Run: func(ctx rule.RuleContext, options any) rule.RuleListeners { pragma := reactutil.GetReactPragma(ctx.Settings) createClass := reactutil.GetReactCreateClass(ctx.Settings) isClassComponent := func(node *ast.Node) bool { return reactutil.GetParentReactComponentScopeBased(node, pragma, createClass) != nil } report := func(node *ast.Node) { ctx.ReportNode(node, rule.RuleMessage{ Id: "useCallback", Description: "Use callback in setState when referencing the previous state.", }) } type methodEntry struct { methodName string node *ast.Node } // varEntry stores one tracked binding. // // - symbol: non-nil when the binding was resolved through the type // checker. Use-site matching then compares symbol identity, which // correctly distinguishes `let`/`const` bindings declared in // sibling blocks of the same function (ESLint's block-scope // semantics — a strict upstream match). // - scope / variableName: fallback match used when either the // binding or the use site couldn't be resolved to a symbol // (e.g. plain-JS files with no TypeChecker available). Coarser // than block-scope resolution; preserves the previous function- // granular behavior rather than silently under-reporting. type varEntry struct { node *ast.Node symbol *ast.Symbol scope *ast.Node variableName string } // methods accumulates function containers whose body reads `this.state` // (directly, or transitively via another tracked method). vars // accumulates local bindings initialized with / destructured from // `this.state` / `this`. Both persist across the whole file walk, // matching upstream's closure variables. var methods []methodEntry var vars []varEntry symbolAt := func(node *ast.Node) *ast.Symbol { if ctx.TypeChecker == nil || node == nil { return nil } return ctx.TypeChecker.GetSymbolAtLocation(node) } return rule.RuleListeners{ ast.KindPropertyAccessExpression: func(node *ast.Node) { if !isThisStateMember(node) { return } if !isClassComponent(node) { return } for current := node; current != nil; current = current.Parent { if isFirstArgumentInSetStateCall(current, node) { report(node) return } if kind := methodContainerKind(current); kind != containerNone { methods = append(methods, methodEntry{ methodName: containerKeyName(current, kind), node: node, }) return } if current.Kind == ast.KindVariableDeclaration { declName := current.AsVariableDeclaration().Name() vars = append(vars, varEntry{ node: node, symbol: symbolAt(declName), scope: utils.FindEnclosingScope(node), variableName: identifierOrEmpty(declName), }) return } } }, ast.KindCallExpression: func(node *ast.Node) { if !isClassComponent(node) { return } call := node.AsCallExpression() calleeName := "" switch call.Expression.Kind { case ast.KindIdentifier: calleeName = call.Expression.AsIdentifier().Text case ast.KindPrivateIdentifier: calleeName = strings.TrimPrefix(call.Expression.AsPrivateIdentifier().Text, "#") } if calleeName != "" { n := len(methods) for i := range n { method := methods[i] if method.methodName != calleeName { continue } if enclosing := findEnclosingClassMethod(node.Parent); enclosing != nil { methods = append(methods, methodEntry{ methodName: containerKeyName(enclosing, containerClass), node: method.node, }) } } } for current := node.Parent; current != nil; current = current.Parent { if !isFirstArgumentInSetStateCall(current, node) { continue } if calleeName != "" { for _, method := range methods { if method.methodName == calleeName { report(method.node) } } } return } }, ast.KindIdentifier: func(node *ast.Node) { if !isValueOrObjectPosition(node) { return } start := walkUpBinary(node) useName := node.AsIdentifier().Text useSymbol := utils.GetReferenceSymbol(node, ctx.TypeChecker) var useScope *ast.Node // lazily computed for fallback for current := start; current != nil; current = current.Parent { if !isFirstArgumentInSetStateCall(current, node) { continue } for _, v := range vars { var matched bool if v.symbol != nil && useSymbol != nil { matched = v.symbol == useSymbol } else { if useScope == nil { useScope = utils.FindEnclosingScope(node) } matched = v.scope == useScope && v.variableName == useName } if matched { report(v.node) } } } }, ast.KindObjectBindingPattern: func(node *ast.Node) { parent := node.Parent if parent == nil || parent.Kind != ast.KindVariableDeclaration { return } init := parent.AsVariableDeclaration().Initializer if init == nil || !isThisReceiver(init) { return } scope := utils.FindEnclosingScope(node) pat := node.AsBindingPattern() if pat == nil || pat.Elements == nil { return } for _, elem := range pat.Elements.Nodes { if elem.Kind != ast.KindBindingElement { continue } if objectBindingPatternPropertyName(elem) != "state" { continue } be := elem.AsBindingElement() keyNode := be.PropertyName if keyNode == nil { keyNode = be.Name() } // Only capture a symbol for shorthand destructuring. // Renamed form `{state: aliased}` deliberately goes through // the name-based fallback: upstream's variableName='state' // will never match the user-typed 'aliased' at the use // site, so binding the local 'aliased' symbol here would // divergently "fix" a latent upstream quirk. Keeping the // renamed path symbol-less preserves upstream parity. var sym *ast.Symbol if be.PropertyName == nil { sym = symbolAt(be.Name()) } vars = append(vars, varEntry{ node: keyNode, symbol: sym, scope: scope, variableName: "state", }) } }, } }, }
Functions ¶
This section is empty.
Types ¶
This section is empty.
Click to show internal directories.
Click to hide internal directories.