Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 48 additions & 35 deletions apps/website/content/docs/packages/kit.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ description: ESLint React's toolkit for building custom React rules with JavaScr
---

import { BskyPost } from "@/components/BskyPost";
import { Accordion, Accordions } from "fumadocs-ui/components/accordion";

<Callout type="warning">
This module is currently in **beta**. APIs may change in future releases.
Expand Down Expand Up @@ -100,47 +101,51 @@ function forbidElements({ forbidden }: ForbidElementsOptions): RuleFunction {
}
```

#### Anonymous Rules
<Accordions>
<Accordion title="Anonymous Rules">

When you use an **anonymous function** (arrow function without a name) with `.use()`, a random hex string is automatically generated as the rule name:
When you use an **anonymous function** (arrow function without a name) with `.use()`, a random hex string is automatically generated as the rule name:

```ts
// Registered as `@eslint-react/kit/a1b2c3d4e5f67890`
eslintReactKit().use(() => (context) => ({
CallExpression(node) {
// Critical check that cannot be easily disabled
},
}));
```
```ts
// Registered as `@eslint-react/kit/a1b2c3d4e5f67890`
eslintReactKit().use(() => (context) => ({
CallExpression(node) {
// Critical check that cannot be easily disabled
},
}));
```

Anonymous rules are ideal for checks that are **critical to code quality or security** and should never be bypassed via disable comments:
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) => ({
Property(node) {
// Prevent direct construction of '__html' objects in product code
if (node.key.type === "Identifier" && node.key.name === "__html") {
context.report({
node,
message: "Do not construct '__html' objects directly. Use a sanitization library or receive them from server-side code.",
});
}
},
}));
```
```ts
// This critical security check cannot be easily disabled by developers
eslintReactKit().use(() => (context) => ({
Property(node) {
// Prevent direct construction of '__html' objects in product code
if (node.key.type === "Identifier" && node.key.name === "__html") {
context.report({
node,
message: "Do not construct '__html' objects directly. Use a sanitization library or receive them from server-side code.",
});
}
},
}));
```

**Note:** Since the rule name is random and changes on every ESLint run, developers cannot use standard rules configs or disable comments like:
**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" } }
```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
```
// 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.

To disable an anonymous rule, developers must modify the ESLint configuration file directly, which provides an audit trail for policy violations.
</Accordion>
</Accordions>

### `Builder`

Expand Down Expand Up @@ -296,7 +301,13 @@ const isCreateRefCall = is.APICall("createRef");
isCreateRefCall(node);
```

<Callout type="info" title="Why `is.API` / `is.APICall` instead of a hand-written check?">
<Accordions>
<Accordion title="Why `is.API` / `is.APICall` instead of a hand-written check?">

<Callout type="warn" title="Under Construction">
This part is under construction.
</Callout>

Consider this single statement:

```ts
Expand All @@ -318,7 +329,9 @@ isCreateRefCall(node);
- matches on the **fully-qualified name**, so a single predicate covers both the bare `captureOwnerStack` and the namespaced `React.captureOwnerStack` (any name ending in `.captureOwnerStack`).

Use `is.API(name)` for the reference itself and `is.APICall(name)` for a call to it. Hand-rolling this means re-implementing unwrapping and member-expression resolution for **every** API — and breaking the moment someone adds a `?.`, an `as`, or a `React.` prefix. To additionally assert the symbol truly comes from React (and not a local same-named binding), pair it with [`is.APIFromReact`](#import-source).
</Callout>

</Accordion>
</Accordions>

#### Import source

Expand Down
Loading