Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var RulesOfHooksRule = rule.Rule{ Name: "react-hooks/rules-of-hooks", Run: func(ctx rule.RuleContext, options any) rule.RuleListeners { additionalRe := getAdditionalEffectHooks(ctx.Settings) sf := ctx.SourceFile registry := collectEffectEventBindings(sf) tc := ctx.TypeChecker reportedIdentifiers := map[int]bool{} report := func(node *ast.Node, msg string) { if hasFlowSuppression(sf, node) { return } ctx.ReportNode(node, rule.RuleMessage{Description: msg}) } return rule.RuleListeners{ ast.KindCallExpression: func(node *ast.Node) { call := node.AsCallExpression() callee := call.Expression if isUseEffectEventCallee(callee) { p := node.Parent if p != nil && p.Kind != ast.KindVariableDeclaration && p.Kind != ast.KindExpressionStatement { report(node, useEffectEventErrorMessage("", false)) } } if !isHookCallee(callee) { return } hookText := makeHookText(callee, sf) fn := findEnclosingFunction(node) isUse := isUseIdentifier(callee) if fn != nil && isPrecededByDirectTerminator(node, fn) { return } if isUse && isInsideTryCatchOfFunction(node, fn) { report(callee, fmt.Sprintf(`React Hook "%s" cannot be called in a try/catch block.`, hookText)) } inAnyLoop := false if !isUse { inAnyLoop = isInsideLoopOfFunction(node, fn) if inAnyLoop { report(callee, fmt.Sprintf( `React Hook "%s" may be executed more than once. Possibly because it is called in a loop. React Hooks must be called in the exact same order in every component render.`, hookText, )) } } if fn == nil { report(callee, fmt.Sprintf( `React Hook "%s" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function.`, hookText, )) return } if isComponentOrHookFn(fn) { if hasAsyncModifier(fn) { report(callee, fmt.Sprintf(`React Hook "%s" cannot be called in an async function.`, hookText)) return } if isUse || inAnyLoop { return } cond := isConditional(node, fn) early := hasEarlyReturnBefore(node, fn) labeled := hasLabeledBreakBefore(node, fn) tryWithPrior := isInsideTryBlockWithPriorStmt(node, fn) if cond || early || labeled || tryWithPrior { suffix := "" if early { suffix = " Did you accidentally call a React Hook after an early return?" } report(callee, fmt.Sprintf( `React Hook "%s" is called conditionally. React Hooks must be called in the exact same order in every component render.%s`, hookText, suffix, )) } return } if isClassMember(fn) { report(callee, fmt.Sprintf( `React Hook "%s" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.`, hookText, )) return } if name := getFunctionName(fn); name != nil { report(callee, fmt.Sprintf( `React Hook "%s" is called in function "%s" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".`, hookText, nameText(name, sf), )) return } if !isUse && isInsideComponentOrHook(node) { report(callee, fmt.Sprintf( `React Hook "%s" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.`, hookText, )) } }, ast.KindIdentifier: func(node *ast.Node) { if !isReferenceIdentifier(node) { return } name := node.AsIdentifier().Text container := resolveBindingViaSymbol(tc, node) if container == nil { container = registry.resolveContainer(node, name) } if container == nil { return } if isInsideEffectArgument(node, additionalRe) { return } called := false if p := node.Parent; p != nil && p.Kind == ast.KindCallExpression { if p.AsCallExpression().Expression == node { called = true } } if reportedIdentifiers[node.Pos()] { return } reportedIdentifiers[node.Pos()] = true if hasFlowSuppression(sf, node) { return } ctx.ReportNode(node, rule.RuleMessage{Description: useEffectEventErrorMessage(name, called)}) }, } }, }
RulesOfHooksRule is the rslint port of upstream `react-hooks/rules-of-hooks`.
Departure from upstream: rslint has no CodePathAnalyzer, so the conditional / loop / early-return / cycled-segment detections are AST-shape based rather than CFG-based. The implementation matches upstream observably for every test in upstream's `valid` and `invalid` suites that doesn't rely on BigInt-precise path counting (and we add a `hasLabeledBreakBefore` walk for the `label: { if (cond) break label; useFoo(); }` case).
Identifier resolution for `useEffectEvent` references uses the TypeChecker when available (correct under shadowing / aliasing); falls back to a per-container name registry when `ctx.TypeChecker` is nil.
`$FlowFixMe[react-rule-hook]` suppression on the preceding line is honored, mirroring upstream's `hasFlowSuppression` byte-for-byte.
Functions ¶
This section is empty.
Types ¶
This section is empty.