eslint-react

repository·main·Indexed 20 days ago

https://github.com/rel1cx/eslint-react

A monorepo providing composable, high-performance ESLint rules for React, React Server Components, React DOM, and Web APIs. It includes @eslint-react/ast for TSESTree node validation, structural comparison, extraction, and traversal, as well as @eslint-react/core for semantic analysis of React constructs such as function components, hooks, and API calls.

Tokens
269.4K
Snippets
781
Records
1.1K
Agent score
68%

What's inside eslint-react

  1. New packages in Milestone 4.0

    main

    Milestone 4.0 introduced several new specialized packages:

    • eslint-plugin-react-jsx: A dedicated plugin for React-flavored JSX rules.
    • @eslint-react/jsx: A utility module for static analysis of JSX patterns in TSESTree ASTs.
    • @eslint-react/kit: A utility module for building custom ESLint rules with React awareness.
  2. Use @eslint-react/eslint for custom rule development

    main

    The @eslint-react/eslint package provides core building blocks for creating ESLint rules specifically tailored for React environments. It includes specialized interfaces, type aliases, and utility functions to streamline rule implementation and documentation.

    Core Building Blocks

    Interfaces

    • RuleFix: Defines the structure for a rule fix.
    • RuleFixer: Provides the API for applying fixes to code.

    Type Aliases

    • RuleContext: Represents the standard ESLint rule context, providing access to the source code, scope, and reporting mechanisms.
    • RuleFeature: Represents specific features or capabilities of a rule.
    • RuleListener: Defines the structure for rule listeners (visitors).
    • ReportFixFunction: A type for functions that report rule violations and suggest fixes.

    Utilities

    • createRule: A rule creator utility that automatically generates documentation URLs for your ESLint React rules.
    • merge: A utility function used to merge multiple visitor objects into a single visitor object, which is useful when composing rules from multiple sources.
  3. Use the `rules-of-hooks` rule

    main

    The rules-of-hooks rule enforces the official Rules of Hooks from React. This rule is a verbatim port from eslint-plugin-react-hooks with specific local adaptations to improve compatibility with TypeScript and custom settings.

    Local Adaptations

    • TypeScript Support: The rule uses Extract.unwrap() on CallExpression callees. This ensures that hook calls wrapped in TypeScript type expressions (e.g., (useState as any)() or useRef<HTMLDivElement>()!) are correctly recognized and reported.
    • Custom Effect Hooks: You can define additional hooks to be treated as effect hooks via ESLint settings.
  4. What is a term-based rule?

    main

    A term-based rule targets specific React, React DOM, or Web API identifiers (such as hooks, lifecycle methods, global constructors, or JSX attributes).

    To optimize performance, these rules use a precheck: a cheap substring scan of the source code text. If the required term is not found, the rule bails out immediately, skipping expensive AST (Abstract Syntax Tree) traversal.

    Note: A passing precheck is necessary but not sufficient. It only proves the text exists; the actual semantic validation is still performed by AST visitors using helpers (e.g., core.isForwardRefCall).

  5. Understand the `purity` rule

    main

    The purity rule validates that React components and custom hooks are pure functions. A component is considered impure if it calls functions that return different values for the same inputs during the render phase. This can lead to hydration mismatches, broken memoization, and unpredictable UI behavior.

    Commonly flagged impure APIs include:

    • Math.random()
    • Date.now() / new Date()
    • crypto.randomUUID() / crypto.getRandomValues()
    • performance.now()

    Note: This is an evaluation implementation and may contain false positives or negatives. Review reports carefully before applying fixes.

  6. Class Component support is deprecated

    main

    Support for Class Components is being phased out.

    • Core Package: All Class Component-related detection functions (e.g., isClassComponent, isPureComponent, and lifecycle checkers) are marked as @deprecated. They are maintained only for minimal compatibility with existing rules.
    • Web API Rules: Rules in eslint-plugin-react-web-api (such as no-leaked-event-listener, no-leaked-interval, and no-leaked-timeout) no longer detect Class Component lifecycles (componentDidMount / componentWillUnmount). They now only report on Hook Effects (e.g., useEffect).
  7. Understand the `set-state-in-effect` rule behavior

    main

    The set-state-in-effect rule is designed to detect state setter calls within React effect hooks (like useEffect). It aims to align with the React Compiler's ValidateNoSetStateInEffects validation logic, though there are specific implementation differences regarding how it identifies and reports these calls.

    Key Behaviors

    • Direct Calls & IIFEs: The rule reports direct setter calls and syntactic Immediately Invoked Function Expressions (IIFEs) within an effect. Note that the rule only reports these if the setter has at least one argument.
    • Async & Promises: Async functions and .then callbacks are explicitly classified as deferred. They are generally not treated as synchronous effect-body calls.
    • Nested Callbacks: Ordinary nested callbacks (e.g., inside a setTimeout or an event listener) are not classified as immediate. The rule does not infer scheduling semantics; it primarily allows ordinary nested callbacks unless the function is directly called from the effect setup.
    • Setter Aliases: The rule recognizes direct useState-like definitions, tuple index access (e.g., setCount[0]), and .at(1) access. However, it does not follow general variable aliases (e.g., const alias = setState; alias();).
    • Transitive Wrappers: The rule can detect setters inside a single wrapper function (e.g., a hook-wrapped callback). However, it does not recursively chase multi-hop wrapper-to-wrapper chains.
    • useEffectEvent Support: The rule tracks hooks generically. It detects direct invocation of an event returned by useEffectEvent inside an effect, which is invalid. However, because of how it collects nested callbacks, it may report some valid React patterns (like passing an event to setTimeout) differently than the React Compiler.
    • Ref-derived Exemptions: The rule includes unconditional heuristic support for state setters derived from useRef or ref-like member access, including within ref-gated if statements.

    Diagnostic Reporting

    When a violation is found, the rule reports the location of the resolved internal setter call itself, rather than the wrapper invocation in the effect (which is how the React Compiler reports it).

  8. Compare `static-components` IMPL vs React Compiler SPEC

    main

    When comparing the ESLint rule implementation (IMPL) to the React Compiler's validation pass (SPEC), note the following differences in execution models:

    FeatureESLint Rule (IMPL)React Compiler (SPEC)
    InputScans ESLint ASTScans lowered HIRFunction
    Dynamic ValuesAST expressions (arrow, class, function, calls, new) are marked dynamicHIR results from FunctionExpression, NewExpression, MethodCall, or CallExpression are marked dynamic
    Local FlowTraces variable definitions, initializers, ternaries, and simple assignmentsUses LoadLocal, StoreLocal, and phi operands to propagate identifiers
    Detection BoundaryRequires an uppercase JSXIdentifier inside a recognized component boundaryChecks JsxExpression tags whose lowered tag kind is Identifier
  9. Understand and use repo path aliases

    main

    The monorepo uses TypeScript paths aliases to prevent deep relative imports. There are two primary alias types used for different scopes:

    1. @/: Points to the current package's source tree (./src/*). Use this for internal package imports.
    2. #/: Points to the workspace root (../../*). This is intended strictly for test helpers and build scripts.

    Important Rules:

    • Cross-package imports: Always use the actual package name (e.g., @eslint-react/ast). Do not use aliases to import from other packages.
    • Published packages: Aliases are resolved and inlined by the bundler; they are not exposed in published packages.
    // Inside a package's src/
    import { createRule } from "@/utils/create-rule"; // 🟢 Preferred
    
    // Inside a test or script
    import { ruleTester } from "#/testing/helpers"; // 🟢 Preferred
    
    // Cross-package import (Use real names, not aliases)
    import { someType } from "@eslint-react/ast";