@eslint-react/kit
Advanced tools
+1
-1
@@ -9,3 +9,3 @@ import * as core from "@eslint-react/core"; | ||
| var name = "@eslint-react/kit"; | ||
| var version = "5.2.4-beta.2"; | ||
| var version = "5.2.4-beta.3"; | ||
@@ -12,0 +12,0 @@ //#endregion |
+7
-7
| { | ||
| "name": "@eslint-react/kit", | ||
| "version": "5.2.4-beta.2", | ||
| "version": "5.2.4-beta.3", | ||
| "description": "ESLint React's utility module for building custom React rules with JavaScript functions.", | ||
@@ -41,4 +41,4 @@ "keywords": [ | ||
| "string-ts": "^2.3.1", | ||
| "@eslint-react/core": "5.2.4-beta.2", | ||
| "@eslint-react/shared": "5.2.4-beta.2" | ||
| "@eslint-react/core": "5.2.4-beta.3", | ||
| "@eslint-react/shared": "5.2.4-beta.3" | ||
| }, | ||
@@ -48,6 +48,6 @@ "devDependencies": { | ||
| "tsdown": "^0.21.9", | ||
| "@eslint-react/ast": "5.2.4-beta.2", | ||
| "@local/eff": "3.0.0-beta.72", | ||
| "@eslint-react/eslint": "5.2.4-beta.2", | ||
| "@local/configs": "0.0.0" | ||
| "@eslint-react/ast": "5.2.4-beta.3", | ||
| "@eslint-react/eslint": "5.2.4-beta.3", | ||
| "@local/configs": "0.0.0", | ||
| "@local/eff": "3.0.0-beta.72" | ||
| }, | ||
@@ -54,0 +54,0 @@ "peerDependencies": { |
+1
-656
| # @eslint-react/kit | ||
| ESLint React's toolkit for building custom React rules with JavaScript functions right inside your `eslint.config.ts`. | ||
| ## Index | ||
| - [Installation](#installation) | ||
| - [Quick Start](#quick-start) | ||
| - [API Reference](#api-reference) | ||
| - [`eslintReactKit` (default export)](#eslintreactkit-default-export) | ||
| - [`RuleFunction`](#rulefunction) | ||
| - [`Builder`](#builder) | ||
| - [`getConfig`](#getconfig) | ||
| - [`getPlugin`](#getplugin) | ||
| - [`merge`](#merge) | ||
| - [`RuleToolkit` — the toolkit object](#ruletoolkit--the-toolkit-object) | ||
| - [`collect`](#collect) — Semantic collectors | ||
| - [`is`](#is) — Predicates | ||
| - [`hint`](#hint) — Detection hint bit-flags | ||
| - [`flag`](#flag) — Component characteristic flags | ||
| - [`settings`](#settings) — Normalized ESLint React settings | ||
| - [Examples](#examples) | ||
| - [Simple: Ban `forwardRef`](#simple-ban-forwardref) | ||
| - [Component: Destructure component props](#component-destructure-component-props) | ||
| - [Hooks: Warn on custom hooks that don't call other hooks](#hooks-warn-on-custom-hooks-that-dont-call-other-hooks) | ||
| - [Multiple Collectors: No component/hook factories](#multiple-collectors-no-componenthook-factories) | ||
| - [Override Config: Using spread syntax with `getConfig`](#override-config-using-spread-syntax-with-getconfig) | ||
| - [Advanced Config: Using `getPlugin` for custom plugin namespace](#advanced-config-using-getplugin-for-custom-plugin-namespace) | ||
| - [More Examples](#more-examples) | ||
| ## Installation | ||
| ```sh | ||
| npm install --save-dev @eslint-react/kit | ||
| ``` | ||
| ## Quick Start | ||
| ```ts | ||
| import eslintReact from "@eslint-react/eslint-plugin"; | ||
| import eslintReactKit, { merge } from "@eslint-react/kit"; | ||
| import type { RuleFunction } from "@eslint-react/kit"; | ||
| import eslintJs from "@eslint/js"; | ||
| import { defineConfig } from "eslint/config"; | ||
| import tseslint from "typescript-eslint"; | ||
| /** Enforce function declarations for function components. */ | ||
| function functionComponentDefinition(): RuleFunction { | ||
| return (context, { collect }) => { | ||
| const { query, visitor } = collect.components(context); | ||
| return merge( | ||
| visitor, | ||
| { | ||
| "Program:exit"(program) { | ||
| for (const { node } of query.all(program)) { | ||
| if (node.type === "FunctionDeclaration") continue; | ||
| context.report({ | ||
| node, | ||
| message: "Function components must be defined with function declarations.", | ||
| }); | ||
| } | ||
| }, | ||
| }, | ||
| ); | ||
| }; | ||
| } | ||
| export default defineConfig( | ||
| { | ||
| files: ["**/*.{ts,tsx}"], | ||
| extends: [ | ||
| eslintJs.configs.recommended, | ||
| tseslint.configs.recommended, | ||
| eslintReact.configs["recommended-typescript"], | ||
| eslintReactKit() | ||
| .use(functionComponentDefinition) | ||
| .getConfig(), | ||
| ], | ||
| }, | ||
| ); | ||
| ``` | ||
| The rule name is derived automatically from the function name (`functionComponentDefinition` → `function-component-definition`), and registered as `@eslint-react/kit/function-component-definition` at `"error"` severity. | ||
| ## API Reference | ||
| ### `eslintReactKit` (default export) | ||
| ```ts | ||
| import eslintReactKit from "@eslint-react/kit"; | ||
| eslintReactKit(): Builder | ||
| ``` | ||
| Creates a `Builder` instance for registering custom rules via the chainable `.use()` API. | ||
| ### `RuleFunction` | ||
| ```ts | ||
| import type { RuleFunction } from "@eslint-react/kit"; | ||
| type RuleFunction = (context: RuleContext, toolkit: RuleToolkit) => RuleListener; | ||
| ``` | ||
| A function that receives the ESLint rule context and the structured `RuleToolkit` toolkit, and returns a `RuleListener` (AST visitor object). | ||
| Rules are defined as **named functions** that return a `RuleFunction`. The function name is automatically converted to kebab-case and used as the rule name under the `@eslint-react/kit` plugin namespace. | ||
| ```ts | ||
| // Function name `noForwardRef` → rule name `no-forward-ref` | ||
| // Registered as `@eslint-react/kit/no-forward-ref` | ||
| function noForwardRef(): RuleFunction { | ||
| return (context, { is }) => ({ ... }); | ||
| } | ||
| // Functions that accept options work the same way | ||
| function forbidElements({ forbidden }: ForbidElementsOptions): RuleFunction { | ||
| return (context) => ({ ... }); | ||
| } | ||
| ``` | ||
| #### Anonymous Rules | ||
| When you use an **anonymous function** (arrow function without a name) with `.use()`, a random ULID is automatically generated as the rule name: | ||
| ```ts | ||
| // Anonymous rule → random ULID name like "01KNE2WSJ8011D2HXE3A6H717C" | ||
| // Registered as `@eslint-react/kit/01KNE2WSJ8011D2HXE3A6H717C` | ||
| eslintReactKit().use(() => (context) => ({ | ||
| JSXOpeningElement(node) { | ||
| // Critical check that cannot be easily disabled | ||
| }, | ||
| })); | ||
| ``` | ||
| > **Note:** The rule names are ULIDs generated randomly on each ESLint run. The examples above illustrate the format — actual values will differ every time. | ||
| Anonymous rules are ideal for checks that are **critical to code quality or security** and should never be bypassed via disable comments: | ||
| ```ts | ||
| // This critical security check cannot be easily disabled by developers | ||
| eslintReactKit().use(() => (context, { is }) => ({ | ||
| CallExpression(node) { | ||
| // Prevent dangerous API calls that could lead to XSS | ||
| if (is.createElementCall(node) && isUnsafeArguments(node.arguments)) { | ||
| context.report({ | ||
| node, | ||
| message: "Potential XSS vulnerability detected. This issue must be fixed.", | ||
| }); | ||
| } | ||
| }, | ||
| })); | ||
| ``` | ||
| **Note:** Since the rule name is random and changes on every ESLint run, developers cannot use standard rules configs or disable comments like: | ||
| ```ts | ||
| // This will NOT work - the rule name is random! | ||
| { rules: { "@eslint-react/kit/01KNE2WSJ8011D2HXE3A6H717C": "off" } } | ||
| // This will NOT work - the rule name is random! | ||
| // eslint-disable-next-line @eslint-react/kit/01KNE2WSJ8011D2HXE3A6H717C | ||
| ``` | ||
| To disable an anonymous rule, developers must modify the ESLint configuration file directly, which provides an audit trail for policy violations. | ||
| ### `Builder` | ||
| ```ts | ||
| interface Builder { | ||
| use<F extends (...args: any[]) => RuleFunction>(factory: F, ...args: Parameters<F>): Builder; | ||
| getConfig(): Linter.Config; | ||
| getPlugin(): ESLint.Plugin; | ||
| } | ||
| ``` | ||
| A chainable builder for registering custom rules. | ||
| | Method | Description | | ||
| | ----------- | -------------------------------------------------------------------------------------------------------------------------- | | ||
| | `use` | Registers a rule factory. The rule name is `kebabCase(factory.name)`. Options type is inferred from the factory signature. | | ||
| | `getConfig` | Returns a `Linter.Config` with all registered rules enabled at `"error"` severity. | | ||
| | `getPlugin` | Returns an `ESLint.Plugin` containing the registered rules and plugin metadata. | | ||
| #### `getConfig` | ||
| Returns a flat `Linter.Config` object with all registered rules set to `"error"`. This is a convenience wrapper that calls `getPlugin()` internally and adds the plugin plus rule entries to the config. | ||
| ```ts | ||
| eslintReactKit() | ||
| .use(noForwardRef) // no-arg factory | ||
| .use(version, "19") // factory with inferred options | ||
| .getConfig(); | ||
| ``` | ||
| #### `getPlugin` | ||
| Returns an `ESLint.Plugin` object containing the registered rules and plugin metadata (`name` and `version`). Use this when you need finer-grained control over how the plugin is integrated into your ESLint configuration — for example, when you want to choose the plugin namespace, set per-rule severities, or compose the plugin with other configs manually. | ||
| ```ts | ||
| const kit = eslintReactKit() | ||
| .use(noForwardRef) | ||
| .use(version, "19"); | ||
| // Retrieve the raw plugin object | ||
| const plugin = kit.getPlugin(); | ||
| // Use it in a custom flat config with your own namespace and severity | ||
| export default [ | ||
| { | ||
| files: ["**/*.{ts,tsx}"], | ||
| plugins: { | ||
| react: plugin, | ||
| }, | ||
| rules: { | ||
| "react/version": "error", | ||
| "react/no-forward-ref": "error", | ||
| }, | ||
| }, | ||
| ]; | ||
| ``` | ||
| ### `merge` | ||
| ```ts | ||
| import { merge } from "@eslint-react/kit"; | ||
| merge(...listeners: RuleListener[]): RuleListener | ||
| ``` | ||
| Merges multiple `RuleListener` (visitor) objects into a single listener. When two or more listeners define the same visitor key, the handlers are chained and execute in order. | ||
| This is essential for combining a collector's `visitor` with your own inspection logic. | ||
| ### `RuleToolkit` — the toolkit object | ||
| The second argument passed to the `RuleFunction` function is a structured `RuleToolkit` object: | ||
| ``` | ||
| kit | ||
| ├── collect -> Semantic collectors (components, hooks) | ||
| ├── is -> All predicates (component, hook, React API, import source) | ||
| ├── hint -> Detection hint bit-flags | ||
| ├── flag -> Component characteristic bit-flags | ||
| ├── settings -> Normalized ESLint React settings | ||
| ``` | ||
| --- | ||
| #### `collect` | ||
| Collector factories create a `{ query, visitor }` pair. The `visitor` must be merged into your rule listener via `merge()`. After traversal completes, `query.all(program)` yields all detected semantic nodes. | ||
| | Method | Returns | Description | | ||
| | ------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------- | | ||
| | `components(context, options?)` | `CollectorWithContext<FunctionComponentSemanticNode>` | Detects function components. Options: `{ hint?: bigint, collectDisplayName?: boolean }` | | ||
| | `hooks(context)` | `CollectorWithContext<HookSemanticNode>` | Detects custom hook definitions. | | ||
| **`CollectorWithContext`** extends `Collector` with contextual queries: | ||
| | Query | Description | | ||
| | -------------------- | ----------------------------------------- | | ||
| | `query.all(program)` | All collected semantic nodes in the file. | | ||
| --- | ||
| #### `is` | ||
| All predicates live under `kit.is` — organized into four sub-sections. | ||
| ##### Component | ||
| | Predicate | Signature | Description | | ||
| | -------------------------- | ------------------------- | --------------------------------------------------------------------------- | | ||
| | `componentDecl` | `(node, hint) -> boolean` | Whether a function node is a component. (context pre-bound) | | ||
| | `componentName` | `(name) -> boolean` | Strict PascalCase component name check. | | ||
| | `componentNameLoose` | `(name) -> boolean` | Loose component name check. | | ||
| | `componentWrapperCall` | `(node) -> boolean` | Whether a node is a `memo(…)` or `forwardRef(…)` call. (context pre-bound) | | ||
| | `componentWrapperCallback` | `(node) -> boolean` | Whether a function is the callback passed to a wrapper. (context pre-bound) | | ||
| ##### Hook | ||
| General hook predicates: | ||
| | Predicate | Signature | Description | | ||
| | -------------------------- | ------------------------------------- | ------------------------------------------------------------ | | ||
| | `hookDecl` | `(node) -> boolean` | Whether a function node is a hook (by name). | | ||
| | `hookCall` | `(node) -> boolean` | Whether a node is a hook call. | | ||
| | `hookName` | `(name) -> boolean` | Whether a string matches the `use[A-Z]` convention. | | ||
| | `useEffectLikeCall` | `(node, additionalHooks?) -> boolean` | Whether a node is a `useEffect`/`useLayoutEffect`-like call. | | ||
| | `useStateLikeCall` | `(node, additionalHooks?) -> boolean` | Whether a node is a `useState`-like call. | | ||
| | `useEffectSetupCallback` | `(node) -> boolean` | Whether a node is a useEffect setup function. | | ||
| | `useEffectCleanupCallback` | `(node) -> boolean` | Whether a node is a useEffect cleanup function. | | ||
| Specific hook call predicates (context pre-bound): | ||
| | Predicate | Description | | ||
| | -------------------------- | ------------------------------------------------ | | ||
| | `useActionStateCall` | Whether a node is a `useActionState` call. | | ||
| | `useCallbackCall` | Whether a node is a `useCallback` call. | | ||
| | `useContextCall` | Whether a node is a `useContext` call. | | ||
| | `useDebugValueCall` | Whether a node is a `useDebugValue` call. | | ||
| | `useDeferredValueCall` | Whether a node is a `useDeferredValue` call. | | ||
| | `useEffectCall` | Whether a node is a `useEffect` call. | | ||
| | `useFormStatusCall` | Whether a node is a `useFormStatus` call. | | ||
| | `useIdCall` | Whether a node is a `useId` call. | | ||
| | `useImperativeHandleCall` | Whether a node is a `useImperativeHandle` call. | | ||
| | `useInsertionEffectCall` | Whether a node is a `useInsertionEffect` call. | | ||
| | `useLayoutEffectCall` | Whether a node is a `useLayoutEffect` call. | | ||
| | `useMemoCall` | Whether a node is a `useMemo` call. | | ||
| | `useOptimisticCall` | Whether a node is a `useOptimistic` call. | | ||
| | `useReducerCall` | Whether a node is a `useReducer` call. | | ||
| | `useRefCall` | Whether a node is a `useRef` call. | | ||
| | `useStateCall` | Whether a node is a `useState` call. | | ||
| | `useSyncExternalStoreCall` | Whether a node is a `useSyncExternalStore` call. | | ||
| | `useTransitionCall` | Whether a node is a `useTransition` call. | | ||
| ##### React API | ||
| Factory functions (context pre-bound): | ||
| | Predicate | Signature | Description | | ||
| | --------- | -------------------------------- | ---------------------------------------------------------------------------- | | ||
| | `API` | `(apiName) -> (node) -> boolean` | Factory: creates a predicate for a React API identifier. (context pre-bound) | | ||
| | `APICall` | `(apiName) -> (node) -> boolean` | Factory: creates a predicate for a React API call. (context pre-bound) | | ||
| Pre-built call predicates (context pre-bound): | ||
| - `captureOwnerStackCall` | ||
| - `childrenCountCall` | ||
| - `childrenForEachCall` | ||
| - `childrenMapCall` | ||
| - `childrenOnlyCall` | ||
| - `childrenToArrayCall` | ||
| - `cloneElementCall` | ||
| - `createContextCall` | ||
| - `createElementCall` | ||
| - `createRefCall` | ||
| - `forwardRefCall` | ||
| - `lazyCall` | ||
| - `memoCall` | ||
| - `useCall` | ||
| All React API predicates and factories have `context` pre-bound — no need to pass the rule context manually: | ||
| ```ts | ||
| // Direct check | ||
| is.memoCall(node); | ||
| // Useful in filter/find | ||
| nodes.filter(is.memoCall); | ||
| // Factory for any API name | ||
| const isCreateRefCall = is.APICall("createRef"); | ||
| isCreateRefCall(node); | ||
| ``` | ||
| ##### Import source | ||
| | Predicate | Signature | Description | | ||
| | -------------------- | ----------------------------------------- | ---------------------------------------------------- | | ||
| | `APIFromReact` | `(name, scope, importSource?) -> boolean` | Whether a variable comes from a React import. | | ||
| | `APIFromReactNative` | `(name, scope, importSource?) -> boolean` | Whether a variable comes from a React Native import. | | ||
| --- | ||
| #### `hint` | ||
| Bit-flags that control what the component collector considers a "component". Combine with bitwise OR (`|`) and remove with bitwise AND-NOT (`& ~`). | ||
| ```ts | ||
| // The default hint used when none is specified | ||
| hint.component.Default; | ||
| // All available flags | ||
| hint.component.DoNotIncludeFunctionDefinedAsObjectMethod; | ||
| hint.component.DoNotIncludeFunctionDefinedAsClassMethod; | ||
| hint.component.DoNotIncludeFunctionDefinedAsArrayMapCallback; | ||
| hint.component.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback; | ||
| // … and more (inherits all JsxDetectionHint flags) | ||
| ``` | ||
| **Customization example:** | ||
| ```ts | ||
| const { query, visitor } = collect.components(context, { | ||
| // Also treat object methods as components (remove the exclusion flag) | ||
| hint: hint.component.Default & ~hint.component.DoNotIncludeFunctionDefinedAsObjectMethod, | ||
| }); | ||
| ``` | ||
| --- | ||
| #### `flag` | ||
| Bit-flags indicating component characteristics. Check with bitwise AND (`&`). | ||
| ```ts | ||
| flag.component.None; // 0n — no flags | ||
| flag.component.Memo; // wrapped in React.memo | ||
| flag.component.ForwardRef; // wrapped in React.forwardRef | ||
| ``` | ||
| **Usage:** | ||
| ```ts | ||
| for (const component of query.all(program)) { | ||
| if (component.flag & flag.component.Memo) { | ||
| // This component is memoized | ||
| } | ||
| } | ||
| ``` | ||
| #### `settings` | ||
| Exposes the normalized `react-x` settings from the ESLint shared configuration (`context.settings["react-x"]`). This lets your custom rules read and react to the same project-level settings used by the built-in rules. | ||
| | Property | Type | Default | Description | | ||
| | ----------------------- | ------------------------------------------------------- | ----------- | --------------------------------------------------------- | | ||
| | `version` | `string` | auto-detect | Resolved React version (e.g. `"19.2.4"`). | | ||
| | `importSource` | `string` | `"react"` | The module React is imported from (e.g. `"@pika/react"`). | | ||
| | `compilationMode` | `"infer" \| "annotation" \| "syntax" \| "all" \| "off"` | `"off"` | The React Compiler compilation mode the project uses. | | ||
| | `polymorphicPropName` | `string \| null` | `"as"` | Prop name used for polymorphic components. | | ||
| | `additionalStateHooks` | `RegExpLike` | — | Pattern matching custom hooks treated as state hooks. | | ||
| | `additionalEffectHooks` | `RegExpLike` | — | Pattern matching custom hooks treated as effect hooks. | | ||
| `RegExpLike` is an object with a `test(s: string) => boolean` method (same interface as `RegExp`). | ||
| **Usage:** | ||
| ```ts | ||
| import type { RuleFunction } from "@eslint-react/kit"; | ||
| function version(major = "19"): RuleFunction { | ||
| return (context, { settings }) => ({ | ||
| Program(program) { | ||
| if (!settings.version.startsWith(`${major}.`)) { | ||
| context.report({ | ||
| node: program, | ||
| message: `This project requires React ${major}, but detected version ${settings.version}.`, | ||
| }); | ||
| } | ||
| }, | ||
| }); | ||
| } | ||
| ``` | ||
| --- | ||
| ## Examples | ||
| ### Simple: Ban `forwardRef` | ||
| This is a simplified kit reimplementation of the built-in [`react-x/no-forwardRef`](https://eslint-react.xyz/docs/rules/no-forward-ref) rule. | ||
| ```ts | ||
| import type { RuleFunction } from "@eslint-react/kit"; | ||
| function noForwardRef(): RuleFunction { | ||
| return (context, { is }) => ({ | ||
| CallExpression(node) { | ||
| if (is.forwardRefCall(node)) { | ||
| context.report({ node, message: "forwardRef is deprecated in React 19. Pass ref as a prop instead." }); | ||
| } | ||
| }, | ||
| }); | ||
| } | ||
| // Usage | ||
| eslintReactKit() | ||
| .use(noForwardRef) | ||
| .getConfig(); | ||
| ``` | ||
| ### Component: Destructure component props | ||
| This is a simplified kit reimplementation of the built-in [`react-x/prefer-destructuring-assignment`](https://eslint-react.xyz/docs/rules/prefer-destructuring-assignment) rule. | ||
| ```ts | ||
| import type { RuleFunction } from "@eslint-react/kit"; | ||
| import { merge } from "@eslint-react/kit"; | ||
| function destructureComponentProps(): RuleFunction { | ||
| return (context, { collect }) => { | ||
| const { query, visitor } = collect.components(context); | ||
| return merge(visitor, { | ||
| "Program:exit"(program) { | ||
| for (const { node } of query.all(program)) { | ||
| const [props] = node.params; | ||
| if (props == null) continue; | ||
| if (props.type !== "Identifier") continue; | ||
| const propName = props.name; | ||
| const propVariable = context.sourceCode.getScope(node).variables.find((v) => v.name === propName); | ||
| const propReferences = propVariable?.references ?? []; | ||
| for (const ref of propReferences) { | ||
| const { parent } = ref.identifier; | ||
| if (parent.type !== "MemberExpression") continue; | ||
| context.report({ | ||
| message: "Use destructuring assignment for component props.", | ||
| node: parent, | ||
| }); | ||
| } | ||
| } | ||
| }, | ||
| }); | ||
| }; | ||
| } | ||
| // Usage | ||
| eslintReactKit() | ||
| .use(destructureComponentProps) | ||
| .getConfig(); | ||
| ``` | ||
| ### Hooks: Warn on custom hooks that don't call other hooks | ||
| This is a simplified kit reimplementation of the built-in [`react-x/no-unnecessary-use-prefix`](https://eslint-react.xyz/docs/rules/no-unnecessary-use-prefix) rule. | ||
| ```ts | ||
| import type { RuleFunction } from "@eslint-react/kit"; | ||
| import { merge } from "@eslint-react/kit"; | ||
| function noUnnecessaryUsePrefix(): RuleFunction { | ||
| return (context, { collect }) => { | ||
| const { query, visitor } = collect.hooks(context); | ||
| return merge(visitor, { | ||
| "Program:exit"(program) { | ||
| for (const { node, hookCalls } of query.all(program)) { | ||
| if (hookCalls.length === 0) { | ||
| context.report({ | ||
| node, | ||
| message: "A custom hook should use at least one hook, otherwise it's just a regular function.", | ||
| }); | ||
| } | ||
| } | ||
| }, | ||
| }); | ||
| }; | ||
| } | ||
| // Usage | ||
| eslintReactKit() | ||
| .use(noUnnecessaryUsePrefix) | ||
| .getConfig(); | ||
| ``` | ||
| ### Multiple Collectors: No component/hook factories | ||
| Disallow defining components or hooks inside other functions (factory pattern). | ||
| This is a simplified kit reimplementation of the built-in [`react-x/component-hook-factories`](https://eslint-react.xyz/docs/rules/component-hook-factories) rule. | ||
| ```ts | ||
| import type { RuleFunction } from "@eslint-react/kit"; | ||
| import { merge } from "@eslint-react/kit"; | ||
| import type { TSESTree } from "@typescript-eslint/types"; | ||
| function componentHookFactories(): RuleFunction { | ||
| function findParent({ parent }: TSESTree.Node, test: (n: TSESTree.Node) => boolean): TSESTree.Node | null { | ||
| if (parent == null) return null; | ||
| if (test(parent)) return parent; | ||
| if (parent.type === "Program") return null; | ||
| return findParent(parent, test); | ||
| } | ||
| function isFunction({ type }: TSESTree.Node) { | ||
| return type === "FunctionDeclaration" || type === "FunctionExpression" || type === "ArrowFunctionExpression"; | ||
| } | ||
| return (context, { collect }) => { | ||
| const fc = collect.components(context); | ||
| const hk = collect.hooks(context); | ||
| return merge( | ||
| fc.visitor, | ||
| hk.visitor, | ||
| { | ||
| "Program:exit"(program) { | ||
| const comps = fc.query.all(program); | ||
| const hooks = hk.query.all(program); | ||
| for (const { name, node, kind } of [...comps, ...hooks]) { | ||
| if (name == null) continue; | ||
| if (findParent(node, isFunction) == null) continue; | ||
| context.report({ | ||
| node, | ||
| message: `Don't define ${kind} "${name}" inside a function. Move it to the module level.`, | ||
| }); | ||
| } | ||
| }, | ||
| }, | ||
| ); | ||
| }; | ||
| } | ||
| // Usage | ||
| eslintReactKit() | ||
| .use(componentHookFactories) | ||
| .getConfig(); | ||
| ``` | ||
| ### Override Config: Using spread syntax with `getConfig` | ||
| `getConfig()` returns a plain config object. You can spread it into a new object to override or supplement its properties — for example, to scope the config to specific files or add extra settings alongside your kit rules. | ||
| ```ts | ||
| import eslintReactKit from "@eslint-react/kit"; | ||
| import type { RuleFunction } from "@eslint-react/kit"; | ||
| // Spread the config into a new object to add or override properties like `files`: | ||
| export default [ | ||
| { | ||
| ...eslintReactKit() | ||
| .use(noForwardRef) | ||
| .use(version, "19") | ||
| .getConfig(), | ||
| // Override `name` so it shows your own label in config inspector tools | ||
| name: "react-custom-rules", | ||
| // Override `files` to scope the kit rules to specific source files | ||
| files: ["src/**/*.ts", "src/**/*.tsx"], | ||
| }, | ||
| ]; | ||
| ``` | ||
| This pattern is especially useful when composing kit configs alongside other ESLint configs (e.g. `typescript-eslint`, `eslint-plugin-react-hooks`) via `defineConfig` — you can nest the spread object inside `extends` arrays and attach shared properties like `files` or `languageOptions` at the same level. | ||
| ### Advanced Config: Using `getPlugin` for custom plugin namespace | ||
| Use `getPlugin()` when you want full control over the plugin namespace and rule severities instead of the all-in-one `getConfig()`. | ||
| ```ts | ||
| import eslintReactKit from "@eslint-react/kit"; | ||
| import type { RuleFunction } from "@eslint-react/kit"; | ||
| const kit = eslintReactKit() | ||
| .use(noForwardRef) | ||
| .use(version, "19"); | ||
| // Instead of kit.getConfig(), use kit.getPlugin() for full control: | ||
| const plugin = kit.getPlugin(); | ||
| export default [ | ||
| { | ||
| files: ["**/*.{ts,tsx}"], | ||
| plugins: { | ||
| // Choose your own namespace | ||
| "react-custom-rules": plugin, | ||
| }, | ||
| rules: { | ||
| // Set individual severities | ||
| "react-custom-rules/no-forward-ref": "error", | ||
| "react-custom-rules/version": "error", | ||
| }, | ||
| }, | ||
| ]; | ||
| ``` | ||
| ## More Examples | ||
| Please check the [Rule Recipes](https://www.eslint-react.xyz/docs/recipes) in the documentation site. | ||
| For full documentation, see [https://beta.eslint-react.xyz/docs/packages/kit](https://beta.eslint-react.xyz/docs/packages/kit). |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
94649
-21.33%4
-99.39%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed