eslint-plugin-kerfjs 5.0.0-beta.16 → 5.0.0-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,13 +7,13 @@ ESLint rules that enforce kerf's hard rules. Catches AI-shaped bugs at edit time
7
7
 
8
8
  This plugin sits alongside two other defense layers shipped by [`kerfjs`](https://brianwestphal.github.io/kerf/):
9
9
 
10
- | Layer | Catches | When |
11
- |---|---|---|
12
- | `tsc --noEmit` with strict typings | Hard Rules 8 (partial-set), most type errors | Build time |
13
- | Opt-in dev-warns (`KERF_DEV_WARN_*`) | Hard Rules 4 (rebuilt listeners), 7 (untracked signals), 8 (narrow set) | Runtime |
14
- | **This plugin** | Hard Rules 2, 5, 6, 10, 12 — AST-shaped antipatterns; plus rename-safety / `raw()`-audit nudges and a project-hygiene check for the bundled AI-assistant configs | **Edit time** |
10
+ | Layer | Catches | When |
11
+ | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
12
+ | `tsc --noEmit` with strict typings | Hard Rules 8 (partial-set), most type errors | Build time |
13
+ | Opt-in dev-warns (`KERF_DEV_WARN_*`) | Hard Rules 4 (rebuilt listeners), 7 (untracked signals), 8 (narrow set) | Runtime |
14
+ | **This plugin** | Hard Rules 2, 5, 6, 10, 12 — AST-shaped antipatterns; plus rename-safety / `raw()`-audit nudges and a project-hygiene check for the bundled AI-assistant configs | **Edit time** |
15
15
 
16
- All but one rule are AST-only — no `@typescript-eslint/parser` *service* dependency is required by the plugin (consumers configure their own parser). The exception, `ai-assistant-configs`, reads the filesystem instead of the AST and runs once per lint pass.
16
+ All but one rule are AST-only — no `@typescript-eslint/parser` _service_ dependency is required by the plugin (consumers configure their own parser). The exception, `ai-assistant-configs`, reads the filesystem instead of the AST and runs once per lint pass.
17
17
 
18
18
  ## Install
19
19
 
@@ -33,12 +33,12 @@ A version outside the range is not "probably fine" — it is one nobody has run.
33
33
 
34
34
  ```js
35
35
  // eslint.config.js
36
- import kerfjs from 'eslint-plugin-kerfjs';
37
- import tsParser from '@typescript-eslint/parser';
36
+ import kerfjs from "eslint-plugin-kerfjs";
37
+ import tsParser from "@typescript-eslint/parser";
38
38
 
39
39
  export default [
40
40
  {
41
- files: ['**/*.ts', '**/*.tsx'],
41
+ files: ["**/*.ts", "**/*.tsx"],
42
42
  languageOptions: {
43
43
  parser: tsParser,
44
44
  parserOptions: { ecmaFeatures: { jsx: true } },
@@ -50,23 +50,22 @@ export default [
50
50
 
51
51
  ## Legacy `.eslintrc` configs are not supported
52
52
 
53
- The package is ESM-only. ESLint's `.eslintrc` system loads plugins with `require()`, which cannot load an ESM package, so `extends: ["plugin:kerfjs/legacy-recommended"]` fails with *"couldn't find the config to extend from"* on ESLint 8 **and** on ESLint 9's legacy mode.
53
+ The package is ESM-only. ESLint's `.eslintrc` system loads plugins with `require()`, which cannot load an ESM package, so `extends: ["plugin:kerfjs/legacy-recommended"]` fails with _"couldn't find the config to extend from"_ on ESLint 8 **and** on ESLint 9's legacy mode.
54
54
 
55
55
  Use flat config (`eslint.config.js`), shown above. The `legacy-recommended` export still exists in the package but is unreachable through any config system — treat it as deprecated.
56
56
 
57
-
58
57
  ## Rules
59
58
 
60
- | Rule | Hard Rule | Severity (recommended) |
61
- |---|---|---|
62
- | [`no-inline-jsx-event-handlers`](docs/rules/no-inline-jsx-event-handlers.md) | 10 — use `data-action` + `delegate()` | `error` |
63
- | [`require-data-key-in-each`](docs/rules/require-data-key-in-each.md) | 2 — `data-key` per item | `error` |
64
- | [`require-delegate-disposer`](docs/rules/require-delegate-disposer.md) | 5 — capture `delegate()` disposers when scope < page | `warn` |
65
- | [`no-nested-mount`](docs/rules/no-nested-mount.md) | 6 — one `mount()` per root | `error` |
66
- | [`prefer-module-jsx-augmentation`](docs/rules/prefer-module-jsx-augmentation.md) | 12 — augment `kerfjs/jsx-runtime`, not global | `error` |
67
- | [`prefer-attr-selector`](docs/rules/prefer-attr-selector.md) | — (rename-safety nudge for `delegate()` selectors) | `warn` |
68
- | [`no-raw-with-dynamic-arg`](docs/rules/no-raw-with-dynamic-arg.md) | — (XSS audit trail) | `warn` |
69
- | [`ai-assistant-configs`](docs/rules/ai-assistant-configs.md) | — (project hygiene) | `warn` |
59
+ | Rule | Hard Rule | Severity (recommended) |
60
+ | -------------------------------------------------------------------------------- | ---------------------------------------------------- | ---------------------- |
61
+ | [`no-inline-jsx-event-handlers`](docs/rules/no-inline-jsx-event-handlers.md) | 10 — use `data-action` + `delegate()` | `error` |
62
+ | [`require-data-key-in-each`](docs/rules/require-data-key-in-each.md) | 2 — `data-key` per item | `error` |
63
+ | [`require-delegate-disposer`](docs/rules/require-delegate-disposer.md) | 5 — capture `delegate()` disposers when scope < page | `warn` |
64
+ | [`no-nested-mount`](docs/rules/no-nested-mount.md) | 6 — one `mount()` per root | `error` |
65
+ | [`prefer-module-jsx-augmentation`](docs/rules/prefer-module-jsx-augmentation.md) | 12 — augment `kerfjs/jsx-runtime`, not global | `error` |
66
+ | [`prefer-attr-selector`](docs/rules/prefer-attr-selector.md) | — (rename-safety nudge for `delegate()` selectors) | `warn` |
67
+ | [`no-raw-with-dynamic-arg`](docs/rules/no-raw-with-dynamic-arg.md) | — (XSS audit trail) | `warn` |
68
+ | [`ai-assistant-configs`](docs/rules/ai-assistant-configs.md) | — (project hygiene) | `warn` |
70
69
 
71
70
  The "Hard Rule" column refers to the numbered rules in [`docs/ai/usage-guide.md`](../docs/ai/usage-guide.md) on the main kerf repo. `no-raw-with-dynamic-arg` and `ai-assistant-configs` don't map to numbered Hard Rules — the former creates an audit trail for every dynamic `raw()` call site (potential XSS); the latter checks that the bundled AI-assistant configs are installed and current. See [`docs/12-ai-assistant-configs.md`](../docs/12-ai-assistant-configs.md) on the main kerf repo for the AI-configs design.
72
71
 
@@ -10,10 +10,10 @@ Maps to design doc [`docs/12-ai-assistant-configs.md`](https://github.com/brianw
10
10
 
11
11
  The rule resolves `kerfjs/ai/manifest.json` from the consumer's installed `kerfjs` (silently no-ops if `kerfjs` isn't a dep). For each bundled file, it picks a trigger heuristic — "is this project using the tool this file is for?" — and only nudges the consumer when the trigger says yes:
12
12
 
13
- | Bundled file | Triggers when | Installed to |
14
- |---|---|---|
15
- | `ai/skill.md` (Claude Code) | `.claude/` directory exists at the project root | `.claude/skills/kerf-app/SKILL.md` |
16
- | `ai/cursorrules` (Cursor) | `.cursorrules` file or `.cursor/` directory exists | `.cursorrules` |
13
+ | Bundled file | Triggers when | Installed to |
14
+ | --------------------------- | -------------------------------------------------- | ---------------------------------- |
15
+ | `ai/skill.md` (Claude Code) | `.claude/` directory exists at the project root | `.claude/skills/kerf-app/SKILL.md` |
16
+ | `ai/cursorrules` (Cursor) | `.cursorrules` file or `.cursor/` directory exists | `.cursorrules` |
17
17
 
18
18
  A triggered file is then classified into one of three reported states (a fourth, "up-to-date," is silent):
19
19
 
@@ -78,4 +78,4 @@ Both `claude` and `cursor` default to `true`. Setting either to `false` makes th
78
78
  - It does not install the configs at `npm install` time — there are no postinstall scripts. The first lint pass after `npm install kerfjs` surfaces the recommendation.
79
79
  - It does not write to disk under plain `eslint` (no `--fix`). The warning is emitted; nothing changes on disk.
80
80
  - It does not push Claude Code or Cursor on consumers who haven't signalled they use them — no `.claude/` or `.cursor*` ⇒ silent.
81
- - It does not validate the *content* of the canonical files in the bundle. That's the kerf maintainer's responsibility (and the `check:ai-bundle-in-sync` gate in the kerf repo).
81
+ - It does not validate the _content_ of the canonical files in the bundle. That's the kerf maintainer's responsibility (and the `check:ai-bundle-in-sync` gate in the kerf repo).
@@ -40,8 +40,8 @@ delegate(rootEl, 'submit', ACTIONS.submit.selector, submit);
40
40
  String literals still work for ad-hoc fixed selectors:
41
41
 
42
42
  ```tsx
43
- <button data-action="save">Save</button>
44
- delegate(rootEl, 'click', '[data-action="save"]', save);
43
+ <button data-action="save">Save</button>;
44
+ delegate(rootEl, "click", '[data-action="save"]', save);
45
45
  ```
46
46
 
47
47
  ## Why this rule is AST-only
@@ -24,7 +24,12 @@ mount(bodyRoot, () => <Body />);
24
24
 
25
25
  // Composition via plain functions:
26
26
  const Header = () => <h1>{title.value}</h1>;
27
- const Body = () => <main><Header /><div>{count.value}</div></main>;
27
+ const Body = () => (
28
+ <main>
29
+ <Header />
30
+ <div>{count.value}</div>
31
+ </main>
32
+ );
28
33
  mount(root, () => <Body />);
29
34
  ```
30
35
 
@@ -9,21 +9,21 @@ Warn when `raw()` is called with a dynamic argument (any expression that is not
9
9
  ## ❌ Incorrect
10
10
 
11
11
  ```ts
12
- raw(userInput) // variable reference
13
- raw(fetchedHtml()) // function call
14
- raw(`<b>${title}</b>`) // template literal with expressions
15
- raw(isAdmin ? adminHtml : guestHtml) // conditional expression
12
+ raw(userInput); // variable reference
13
+ raw(fetchedHtml()); // function call
14
+ raw(`<b>${title}</b>`); // template literal with expressions
15
+ raw(isAdmin ? adminHtml : guestHtml); // conditional expression
16
16
  ```
17
17
 
18
18
  ## ✅ Correct
19
19
 
20
20
  ```ts
21
- raw('<p>Static markup</p>') // string literal — no warning
22
- raw(`<p>Static template</p>`) // expression-free template literal — no warning
21
+ raw("<p>Static markup</p>"); // string literal — no warning
22
+ raw(`<p>Static template</p>`); // expression-free template literal — no warning
23
23
 
24
24
  // Dynamic but audited — suppress with eslint-disable
25
25
  // eslint-disable-next-line kerfjs/no-raw-with-dynamic-arg
26
- raw(DOMPurify.sanitize(marked(userMarkdown)))
26
+ raw(DOMPurify.sanitize(marked(userMarkdown)));
27
27
  ```
28
28
 
29
29
  ## Why `warn` and not `error`
@@ -7,8 +7,8 @@ Prefer `attr('name', 'value').selector` over a literal `[name="value"]` selector
7
7
  ## ❌ Discouraged
8
8
 
9
9
  ```tsx
10
- <button data-action="toggle">Toggle</button>
11
- delegate(root, 'click', '[data-action="toggle"]', handler);
10
+ <button data-action="toggle">Toggle</button>;
11
+ delegate(root, "click", '[data-action="toggle"]', handler);
12
12
  ```
13
13
 
14
14
  The JSX attribute and the selector string are two independent literals; renaming the action key in JSX leaves the delegate selector unchanged, and the handler silently stops firing.
@@ -16,14 +16,14 @@ The JSX attribute and the selector string are two independent literals; renaming
16
16
  ## ✅ Recommended
17
17
 
18
18
  ```tsx
19
- import { attr, delegate, type AttrSpec } from 'kerfjs';
19
+ import { attr, delegate, type AttrSpec } from "kerfjs";
20
20
 
21
21
  const ACTIONS = {
22
- toggle: attr('data-action', 'toggle'),
23
- } as const satisfies Record<string, AttrSpec<'data-action'>>;
22
+ toggle: attr("data-action", "toggle"),
23
+ } as const satisfies Record<string, AttrSpec<"data-action">>;
24
24
 
25
- <button {...ACTIONS.toggle.attrs}>Toggle</button>
26
- delegate(root, 'click', ACTIONS.toggle.selector, handler);
25
+ <button {...ACTIONS.toggle.attrs}>Toggle</button>;
26
+ delegate(root, "click", ACTIONS.toggle.selector, handler);
27
27
  ```
28
28
 
29
29
  Rename `'toggle'` to `'on'` in the `ACTIONS` map and both the rendered attribute and the delegate selector update — no string-grep migration required.
@@ -10,7 +10,7 @@ Maps to **kerf Hard Rule 11** — kerf's JSX runtime looks up custom-element typ
10
10
  declare global {
11
11
  namespace JSX {
12
12
  interface IntrinsicElements {
13
- 'my-tag': { foo?: string };
13
+ "my-tag": { foo?: string };
14
14
  }
15
15
  }
16
16
  }
@@ -19,10 +19,10 @@ declare global {
19
19
  ## ✅ Correct
20
20
 
21
21
  ```ts
22
- declare module 'kerfjs/jsx-runtime' {
22
+ declare module "kerfjs/jsx-runtime" {
23
23
  namespace JSX {
24
24
  interface IntrinsicElements {
25
- 'my-tag': KerfCustomElement & { foo?: string };
25
+ "my-tag": KerfCustomElement & { foo?: string };
26
26
  }
27
27
  }
28
28
  }
@@ -31,7 +31,11 @@ declare module 'kerfjs/jsx-runtime' {
31
31
  Import the building-block types from `kerfjs/jsx-runtime`:
32
32
 
33
33
  ```ts
34
- import type { KerfCustomElement, KerfBaseAttrs, AttrLike } from 'kerfjs/jsx-runtime';
34
+ import type {
35
+ KerfCustomElement,
36
+ KerfBaseAttrs,
37
+ AttrLike,
38
+ } from "kerfjs/jsx-runtime";
35
39
  ```
36
40
 
37
41
  ## What this rule does NOT catch
@@ -7,24 +7,24 @@ Maps to **kerf Hard Rule 2** — the keyed reconciler matches items by `id` firs
7
7
  ## ❌ Incorrect
8
8
 
9
9
  ```tsx
10
- each(items, (item) => <li>{item.name}</li>)
10
+ each(items, (item) => <li>{item.name}</li>);
11
11
 
12
12
  each(items, (item) => {
13
13
  return <li class="row">{item.name}</li>;
14
- })
14
+ });
15
15
 
16
- each(items, (item) => <>{item.name}</>)
16
+ each(items, (item) => <>{item.name}</>);
17
17
  ```
18
18
 
19
19
  ## ✅ Correct
20
20
 
21
21
  ```tsx
22
- each(items, (item) => <li data-key={item.id}>{item.name}</li>)
22
+ each(items, (item) => <li data-key={item.id}>{item.name}</li>);
23
23
 
24
- each(items, (item) => <li id={item.id}>{item.name}</li>)
24
+ each(items, (item) => <li id={item.id}>{item.name}</li>);
25
25
 
26
26
  // Spread attributes are conservatively allowed — they may include the key.
27
- each(items, (item) => <li {...item.attrs}>{item.name}</li>)
27
+ each(items, (item) => <li {...item.attrs}>{item.name}</li>);
28
28
  ```
29
29
 
30
30
  ## What this rule does NOT catch
@@ -2,7 +2,7 @@
2
2
 
3
3
  Require capturing the disposer returned by `delegate()` and `delegateCapture()`.
4
4
 
5
- Both helpers install a single listener on the root element and return a `() => void` disposer — the *only* way to remove the listener once it's attached. Discarding the return value is safe in exactly one case: the registration is genuinely page-lifetime (root is `document.body` or another never-torn-down element, attached once at startup, never re-registered). In every other case the listener closure pins `rootEl`, `handler`, and everything the handler closes over (stores, signals, app state) — so an undisposed delegate on a transient root leaks both the listener and the app graph it references, and re-mount cycles stack listeners linearly. `mount()`'s own disposer does NOT remove delegates for you.
5
+ Both helpers install a single listener on the root element and return a `() => void` disposer — the _only_ way to remove the listener once it's attached. Discarding the return value is safe in exactly one case: the registration is genuinely page-lifetime (root is `document.body` or another never-torn-down element, attached once at startup, never re-registered). In every other case the listener closure pins `rootEl`, `handler`, and everything the handler closes over (stores, signals, app state) — so an undisposed delegate on a transient root leaks both the listener and the app graph it references, and re-mount cycles stack listeners linearly. `mount()`'s own disposer does NOT remove delegates for you.
6
6
 
7
7
  See [kerf docs §5.3 — Disposers](https://brianwestphal.github.io/kerf/docs/5-event-delegation/#53-disposers) for the full rationale.
8
8
 
@@ -49,8 +49,8 @@ Or collect into a disposer array:
49
49
  ```ts
50
50
  const disposers: Array<() => void> = [];
51
51
  disposers.push(mount(host, render));
52
- disposers.push(delegate(host, 'click', '[data-action]', onAction));
53
- disposers.push(delegate(host, 'keydown', '[data-edit]', onEdit));
52
+ disposers.push(delegate(host, "click", "[data-action]", onAction));
53
+ disposers.push(delegate(host, "keydown", "[data-edit]", onEdit));
54
54
 
55
55
  function teardown() {
56
56
  for (const off of disposers) off();
@@ -64,13 +64,18 @@ If the registration is attached once at module load and the root never tears dow
64
64
 
65
65
  ```ts
66
66
  // 1. The `void` operator as an explicit-discard sigil.
67
- void delegate(document.body, 'click', ACTIONS.inc.selector, () => count.value++);
67
+ void delegate(
68
+ document.body,
69
+ "click",
70
+ ACTIONS.inc.selector,
71
+ () => count.value++,
72
+ );
68
73
  ```
69
74
 
70
75
  ```ts
71
76
  // 2. Standard eslint-disable for one-off cases.
72
77
  // eslint-disable-next-line kerfjs/require-delegate-disposer
73
- delegate(document.body, 'click', ACTIONS.inc.selector, () => count.value++);
78
+ delegate(document.body, "click", ACTIONS.inc.selector, () => count.value++);
74
79
  ```
75
80
 
76
81
  `void` is the lower-friction option when the file has many page-lifetime registrations clustered together; eslint-disable carries the rule name with it, which makes it easier to grep for.
package/index.js CHANGED
@@ -8,7 +8,7 @@ import requireDataKeyInEach from './lib/rules/require-data-key-in-each.js';
8
8
  import requireDelegateDisposer from './lib/rules/require-delegate-disposer.js';
9
9
 
10
10
  const plugin = {
11
- meta: { name: 'eslint-plugin-kerfjs', version: '5.0.0-beta.16' },
11
+ meta: { name: 'eslint-plugin-kerfjs', version: '5.0.0-beta.18' },
12
12
  rules: {
13
13
  'no-inline-jsx-event-handlers': noInlineJsxEventHandlers,
14
14
  'no-raw-with-dynamic-arg': noRawWithDynamicArg,
@@ -106,8 +106,10 @@ export function classifyFile(file, bundleDir, cwd) {
106
106
  }
107
107
 
108
108
  const markerIdx = findMarkerIndex(consumer);
109
- if (markerIdx === -1) return { state: 'forked', reason: 'no canonical-end marker' };
110
- if (markerIdx === -2) return { state: 'forked', reason: 'multiple canonical-end markers' };
109
+ if (markerIdx === -1)
110
+ return { state: 'forked', reason: 'no canonical-end marker' };
111
+ if (markerIdx === -2)
112
+ return { state: 'forked', reason: 'multiple canonical-end markers' };
111
113
 
112
114
  const canonicalEnd = canonicalEndOffset(consumer);
113
115
  const consumerCanonical = consumer.slice(0, canonicalEnd);
@@ -134,7 +136,10 @@ export function classifyFile(file, bundleDir, cwd) {
134
136
  };
135
137
  }
136
138
  if (consumerCanonicalHash !== historicalHash) {
137
- return { state: 'forked', reason: 'content above marker has been edited' };
139
+ return {
140
+ state: 'forked',
141
+ reason: 'content above marker has been edited',
142
+ };
138
143
  }
139
144
  return {
140
145
  state: 'stale',
@@ -164,7 +169,9 @@ function loadBundle(cwd) {
164
169
  function isTriggered(name, cwd) {
165
170
  if (name === 'skill') return existsSync(join(cwd, '.claude'));
166
171
  if (name === 'cursorrules') {
167
- return existsSync(join(cwd, '.cursorrules')) || existsSync(join(cwd, '.cursor'));
172
+ return (
173
+ existsSync(join(cwd, '.cursorrules')) || existsSync(join(cwd, '.cursor'))
174
+ );
168
175
  }
169
176
  return false;
170
177
  }
@@ -220,7 +227,7 @@ const meta = {
220
227
  stale:
221
228
  '{{tool}} drop-in at `{{dest}}` is stale (have {{consumerVersion}}, latest is {{bundledVersion}}). Run `eslint --fix` to update the canonical section above the `KERF-APP-CANONICAL-END` marker; your customizations below the marker are preserved.',
222
229
  forked:
223
- '{{tool}} drop-in at `{{dest}}` is forked: {{reason}}. Restore the canonical layout (one `KERF-APP-CANONICAL-END` marker, no edits above it) or disable this rule with `\'kerfjs/ai-assistant-configs\': \'off\'`.',
230
+ "{{tool}} drop-in at `{{dest}}` is forked: {{reason}}. Restore the canonical layout (one `KERF-APP-CANONICAL-END` marker, no edits above it) or disable this rule with `'kerfjs/ai-assistant-configs': 'off'`.",
224
231
  },
225
232
  // Mark as fixable so the CLI exposes this rule under `--fix`. The callback
226
233
  // writes to a separate file and returns null, so ESLint applies no edit to
@@ -247,7 +254,10 @@ function create(context) {
247
254
  if (file.name === 'cursorrules' && !cursorEnabled) continue;
248
255
  if (result.state === 'ok') continue;
249
256
 
250
- const tool = file.name === 'skill' ? 'Claude Code kerf-app skill' : 'Cursor kerf rules';
257
+ const tool =
258
+ file.name === 'skill'
259
+ ? 'Claude Code kerf-app skill'
260
+ : 'Cursor kerf rules';
251
261
  const bundle = file.bundle; // e.g. 'ai/skill.md'
252
262
  const data = { tool, dest: file.dest, bundle };
253
263
 
@@ -291,9 +301,9 @@ function create(context) {
291
301
 
292
302
  function contextCwd(context) {
293
303
  return (
294
- (typeof context.cwd === 'string' && context.cwd)
295
- || (typeof context.getCwd === 'function' && context.getCwd())
296
- || process.cwd()
304
+ (typeof context.cwd === 'string' && context.cwd) ||
305
+ (typeof context.getCwd === 'function' && context.getCwd()) ||
306
+ process.cwd()
297
307
  );
298
308
  }
299
309
 
@@ -7,7 +7,7 @@ const meta = {
7
7
  type: 'problem',
8
8
  docs: {
9
9
  description:
10
- "Disallow inline `onClick`-style JSX event handler attributes; use `data-action` + `delegate()` instead.",
10
+ 'Disallow inline `onClick`-style JSX event handler attributes; use `data-action` + `delegate()` instead.',
11
11
  url: 'https://github.com/brianwestphal/kerf/blob/main/eslint-plugin/docs/rules/no-inline-jsx-event-handlers.md',
12
12
  },
13
13
  schema: [],
@@ -34,7 +34,8 @@ function create(context) {
34
34
  let p = node.parent;
35
35
  while (p) {
36
36
  if (
37
- (p.type === 'ArrowFunctionExpression' || p.type === 'FunctionExpression') &&
37
+ (p.type === 'ArrowFunctionExpression' ||
38
+ p.type === 'FunctionExpression') &&
38
39
  isMountCall(p.parent) &&
39
40
  p.parent.arguments.includes(p)
40
41
  ) {
@@ -22,13 +22,13 @@ const meta = {
22
22
  type: 'problem',
23
23
  docs: {
24
24
  description:
25
- "Disallow `raw()` with dynamic arguments; `raw()` bypasses HTML escaping and passing dynamic values is an XSS vector.",
25
+ 'Disallow `raw()` with dynamic arguments; `raw()` bypasses HTML escaping and passing dynamic values is an XSS vector.',
26
26
  url: 'https://github.com/brianwestphal/kerf/blob/main/eslint-plugin/docs/rules/no-raw-with-dynamic-arg.md',
27
27
  },
28
28
  schema: [],
29
29
  messages: {
30
30
  dynamic:
31
- "`raw()` bypasses HTML auto-escaping. Passing a dynamic value is an XSS risk unless the input has been sanitized. Sanitize first (`DOMPurify.sanitize(...)`) then pass to `raw()`, or add `// eslint-disable-next-line kerfjs/no-raw-with-dynamic-arg` to mark a call as intentionally safe.",
31
+ '`raw()` bypasses HTML auto-escaping. Passing a dynamic value is an XSS risk unless the input has been sanitized. Sanitize first (`DOMPurify.sanitize(...)`) then pass to `raw()`, or add `// eslint-disable-next-line kerfjs/no-raw-with-dynamic-arg` to mark a call as intentionally safe.',
32
32
  },
33
33
  };
34
34
 
@@ -47,10 +47,11 @@ function create(context) {
47
47
  const callee = node.callee;
48
48
  if (!callee) return;
49
49
  // Match bare `raw(...)` and `kerfjs.raw(...)` / `kerf.raw(...)` shapes.
50
- const isRaw = (callee.type === 'Identifier' && callee.name === 'raw')
51
- || (callee.type === 'MemberExpression'
52
- && callee.property.type === 'Identifier'
53
- && callee.property.name === 'raw');
50
+ const isRaw =
51
+ (callee.type === 'Identifier' && callee.name === 'raw') ||
52
+ (callee.type === 'MemberExpression' &&
53
+ callee.property.type === 'Identifier' &&
54
+ callee.property.name === 'raw');
54
55
  if (!isRaw) return;
55
56
  if (node.arguments.length === 0) return;
56
57
  const arg = node.arguments[0];
@@ -10,7 +10,8 @@ function findGlobalJSXIntrinsics(moduleBlock) {
10
10
  for (const m of moduleBlock.body) {
11
11
  if (m.type !== 'TSModuleDeclaration') continue;
12
12
  const idNode = m.id;
13
- if (!idNode || idNode.type !== 'Identifier' || idNode.name !== 'JSX') continue;
13
+ if (!idNode || idNode.type !== 'Identifier' || idNode.name !== 'JSX')
14
+ continue;
14
15
  const inner = m.body;
15
16
  if (!inner || inner.type !== 'TSModuleBlock') continue;
16
17
  for (const member of inner.body) {
@@ -31,7 +32,7 @@ const meta = {
31
32
  type: 'problem',
32
33
  docs: {
33
34
  description:
34
- "Declaration-merge `JSX.IntrinsicElements` into `kerfjs/jsx-runtime`, not the global namespace.",
35
+ 'Declaration-merge `JSX.IntrinsicElements` into `kerfjs/jsx-runtime`, not the global namespace.',
35
36
  url: 'https://github.com/brianwestphal/kerf/blob/main/eslint-plugin/docs/rules/prefer-module-jsx-augmentation.md',
36
37
  },
37
38
  schema: [],
@@ -38,10 +38,15 @@ const meta = {
38
38
  function create(context) {
39
39
  return {
40
40
  CallExpression(node) {
41
- if (node.callee.type !== 'Identifier' || node.callee.name !== 'each') return;
41
+ if (node.callee.type !== 'Identifier' || node.callee.name !== 'each')
42
+ return;
42
43
  const cb = node.arguments[1];
43
44
  if (!cb) return;
44
- if (cb.type !== 'ArrowFunctionExpression' && cb.type !== 'FunctionExpression') return;
45
+ if (
46
+ cb.type !== 'ArrowFunctionExpression' &&
47
+ cb.type !== 'FunctionExpression'
48
+ )
49
+ return;
45
50
  const root = findRootJSXFromBody(cb.body);
46
51
  if (!root) return;
47
52
  if (root.type === 'JSXFragment') {
@@ -52,7 +52,8 @@ function create(context) {
52
52
  // the disposer is being routed somewhere reachable — accept.
53
53
  const parent = node.parent;
54
54
  if (!parent) return;
55
- if (parent.type === 'UnaryExpression' && parent.operator === 'void') return;
55
+ if (parent.type === 'UnaryExpression' && parent.operator === 'void')
56
+ return;
56
57
  if (parent.type !== 'ExpressionStatement') return;
57
58
 
58
59
  context.report({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-kerfjs",
3
- "version": "5.0.0-beta.16",
3
+ "version": "5.0.0-beta.18",
4
4
  "description": "ESLint rules that enforce kerf's hard rules — catches AI-shaped bugs at edit time.",
5
5
  "type": "module",
6
6
  "license": "MIT",