eslint-plugin-react-dom 3.0.0-beta.7 → 3.0.0-beta.71

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/dist/index.d.ts CHANGED
@@ -1,26 +1,9 @@
1
- import * as _eslint_react_shared0 from "@eslint-react/shared";
1
+ import { ESLint, Linter } from "eslint";
2
2
 
3
3
  //#region src/index.d.ts
4
- declare const _default: {
5
- configs: {
6
- recommended: {
7
- plugins: {};
8
- name?: string;
9
- rules?: Record<string, _eslint_react_shared0.RuleConfig>;
10
- settings?: _eslint_react_shared0.SettingsConfig;
11
- };
12
- strict: {
13
- plugins: {};
14
- name?: string;
15
- rules?: Record<string, _eslint_react_shared0.RuleConfig>;
16
- settings?: _eslint_react_shared0.SettingsConfig;
17
- };
18
- };
19
- meta: {
20
- name: string;
21
- version: string;
22
- };
23
- rules: Record<string, _eslint_react_shared0.CompatibleRule>;
4
+ type ConfigName = "recommended" | "strict";
5
+ declare const finalPlugin: ESLint.Plugin & {
6
+ configs: Record<ConfigName, Linter.Config>;
24
7
  };
25
8
  //#endregion
26
- export { _default as default };
9
+ export { finalPlugin as default };
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
- import { DEFAULT_ESLINT_REACT_SETTINGS, RE_JAVASCRIPT_PROTOCOL, WEBSITE_URL, getConfigAdapters, getSettingsFromContext } from "@eslint-react/shared";
2
- import * as core from "@eslint-react/core";
1
+ import { DEFAULT_ESLINT_REACT_SETTINGS, RE_JAVASCRIPT_PROTOCOL, WEBSITE_URL, defineRuleListener, getSettingsFromContext } from "@eslint-react/shared";
2
+ import { JsxInspector } from "@eslint-react/core";
3
3
  import { ESLintUtils } from "@typescript-eslint/utils";
4
4
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
5
5
  import { compare } from "compare-versions";
6
+ import "ts-pattern";
6
7
 
7
8
  //#region \0rolldown/runtime.js
8
9
  var __defProp = Object.defineProperty;
@@ -23,7 +24,7 @@ var __exportAll = (all, no_symbols) => {
23
24
  //#endregion
24
25
  //#region package.json
25
26
  var name$2 = "eslint-plugin-react-dom";
26
- var version = "3.0.0-beta.7";
27
+ var version = "3.0.0-beta.71";
27
28
 
28
29
  //#endregion
29
30
  //#region src/utils/create-jsx-element-resolver.ts
@@ -40,21 +41,19 @@ var version = "3.0.0-beta.7";
40
41
  */
41
42
  function createJsxElementResolver(context) {
42
43
  const { polymorphicPropName } = getSettingsFromContext(context);
44
+ const jsx = JsxInspector.from(context);
43
45
  return { resolve(node) {
44
- const elementName = core.getJsxElementType(context, node);
46
+ const elementName = jsx.getElementType(node);
45
47
  const result = {
46
48
  domElementType: elementName,
47
49
  jsxElementType: elementName
48
50
  };
49
51
  if (elementName === elementName.toLowerCase() || polymorphicPropName == null) return result;
50
- const polymorphicProp = core.getJsxAttribute(context, node)(polymorphicPropName);
51
- if (polymorphicProp != null) {
52
- const staticValue = core.resolveJsxAttributeValue(context, polymorphicProp).toStatic(polymorphicPropName);
53
- if (typeof staticValue === "string") return {
54
- ...result,
55
- domElementType: staticValue
56
- };
57
- }
52
+ const polyPropValue = jsx.getAttributeValue(node, polymorphicPropName)?.toStatic();
53
+ if (typeof polyPropValue === "string") return {
54
+ ...result,
55
+ domElementType: polyPropValue
56
+ };
58
57
  return result;
59
58
  } };
60
59
  }
@@ -67,72 +66,73 @@ function getDocsUrl(ruleName) {
67
66
  const createRule = ESLintUtils.RuleCreator(getDocsUrl);
68
67
 
69
68
  //#endregion
70
- //#region src/rules/no-dangerously-set-innerhtml.ts
71
- const RULE_NAME$17 = "no-dangerously-set-innerhtml";
72
- const DSIH$1 = "dangerouslySetInnerHTML";
73
- var no_dangerously_set_innerhtml_default = createRule({
69
+ //#region src/rules/no-dangerously-set-innerhtml-with-children/no-dangerously-set-innerhtml-with-children.ts
70
+ const RULE_NAME$17 = "no-dangerously-set-innerhtml-with-children";
71
+ var no_dangerously_set_innerhtml_with_children_default = createRule({
74
72
  meta: {
75
73
  type: "problem",
76
- docs: { description: "Disallows DOM elements from using 'dangerouslySetInnerHTML'." },
77
- messages: { default: "Using 'dangerouslySetInnerHTML' may have security implications." },
74
+ docs: { description: "Disallows DOM elements from using 'dangerouslySetInnerHTML' and 'children' at the same time." },
75
+ messages: { default: "A DOM component cannot use both children and 'dangerouslySetInnerHTML'." },
78
76
  schema: []
79
77
  },
80
78
  name: RULE_NAME$17,
81
79
  create: create$17,
82
80
  defaultOptions: []
83
81
  });
82
+ const DSIH$1 = "dangerouslySetInnerHTML";
83
+ /**
84
+ * Check if a JSX child node is considered significant (i.e., not just whitespace for formatting)
85
+ * @param node The JSX child node to check
86
+ * @returns `true` if the node is significant, `false` otherwise
87
+ */
88
+ function isSignificantChildren(node) {
89
+ if (!JsxInspector.isJsxText(node)) return true;
90
+ return !(node.raw.trim() === "" && node.raw.includes("\n"));
91
+ }
84
92
  function create$17(context) {
85
93
  if (!context.sourceCode.text.includes(DSIH$1)) return {};
86
- return { JSXElement(node) {
87
- const dsihProp = core.getJsxAttribute(context, node)(DSIH$1);
88
- if (dsihProp == null) return;
94
+ const jsx = JsxInspector.from(context);
95
+ return defineRuleListener({ JSXElement(node) {
96
+ if (!jsx.hasAttribute(node, DSIH$1)) return;
97
+ const childrenPropOrNode = jsx.findAttribute(node, "children") ?? node.children.find(isSignificantChildren);
98
+ if (childrenPropOrNode == null) return;
89
99
  context.report({
90
100
  messageId: "default",
91
- node: dsihProp
101
+ node: childrenPropOrNode
92
102
  });
93
- } };
103
+ } });
94
104
  }
95
105
 
96
106
  //#endregion
97
- //#region src/rules/no-dangerously-set-innerhtml-with-children.ts
98
- const RULE_NAME$16 = "no-dangerously-set-innerhtml-with-children";
99
- var no_dangerously_set_innerhtml_with_children_default = createRule({
107
+ //#region src/rules/no-dangerously-set-innerhtml/no-dangerously-set-innerhtml.ts
108
+ const RULE_NAME$16 = "no-dangerously-set-innerhtml";
109
+ const DSIH = "dangerouslySetInnerHTML";
110
+ var no_dangerously_set_innerhtml_default = createRule({
100
111
  meta: {
101
112
  type: "problem",
102
- docs: { description: "Disallows DOM elements from using 'dangerouslySetInnerHTML' and 'children' at the same time." },
103
- messages: { default: "A DOM component cannot use both children and 'dangerouslySetInnerHTML'." },
113
+ docs: { description: "Disallows DOM elements from using 'dangerouslySetInnerHTML'." },
114
+ messages: { default: "Using 'dangerouslySetInnerHTML' may have security implications." },
104
115
  schema: []
105
116
  },
106
117
  name: RULE_NAME$16,
107
118
  create: create$16,
108
119
  defaultOptions: []
109
120
  });
110
- const DSIH = "dangerouslySetInnerHTML";
111
- /**
112
- * Check if a JSX child node is considered significant (i.e., not just whitespace for formatting)
113
- * @param node The JSX child node to check
114
- * @returns `true` if the node is significant, `false` otherwise
115
- */
116
- function isSignificantChildren(node) {
117
- if (!core.isJsxText(node)) return true;
118
- return !(node.raw.trim() === "" && node.raw.includes("\n"));
119
- }
120
121
  function create$16(context) {
121
122
  if (!context.sourceCode.text.includes(DSIH)) return {};
122
- return { JSXElement(node) {
123
- const findJsxAttribute = core.getJsxAttribute(context, node);
124
- if (findJsxAttribute(DSIH) == null) return;
125
- const childrenPropOrNode = findJsxAttribute("children") ?? node.children.find(isSignificantChildren);
126
- if (childrenPropOrNode == null) return;
123
+ const jsx = JsxInspector.from(context);
124
+ return defineRuleListener({ JSXElement(node) {
125
+ const dsihProp = jsx.findAttribute(node, DSIH);
126
+ if (dsihProp == null) return;
127
127
  context.report({
128
128
  messageId: "default",
129
- node: childrenPropOrNode
129
+ node: dsihProp
130
130
  });
131
- } };
131
+ } });
132
132
  }
133
133
 
134
134
  //#endregion
135
- //#region src/rules/no-find-dom-node.ts
135
+ //#region src/rules/no-find-dom-node/no-find-dom-node.ts
136
136
  const RULE_NAME$15 = "no-find-dom-node";
137
137
  var no_find_dom_node_default = createRule({
138
138
  meta: {
@@ -148,7 +148,7 @@ var no_find_dom_node_default = createRule({
148
148
  const findDOMNode = "findDOMNode";
149
149
  function create$15(context) {
150
150
  if (!context.sourceCode.text.includes(findDOMNode)) return {};
151
- return { CallExpression(node) {
151
+ return defineRuleListener({ CallExpression(node) {
152
152
  const { callee } = node;
153
153
  switch (callee.type) {
154
154
  case AST_NODE_TYPES.Identifier:
@@ -164,11 +164,11 @@ function create$15(context) {
164
164
  });
165
165
  return;
166
166
  }
167
- } };
167
+ } });
168
168
  }
169
169
 
170
170
  //#endregion
171
- //#region src/rules/no-flush-sync.ts
171
+ //#region src/rules/no-flush-sync/no-flush-sync.ts
172
172
  const RULE_NAME$14 = "no-flush-sync";
173
173
  var no_flush_sync_default = createRule({
174
174
  meta: {
@@ -184,7 +184,7 @@ var no_flush_sync_default = createRule({
184
184
  const flushSync = "flushSync";
185
185
  function create$14(context) {
186
186
  if (!context.sourceCode.text.includes(flushSync)) return {};
187
- return { CallExpression(node) {
187
+ return defineRuleListener({ CallExpression(node) {
188
188
  const { callee } = node;
189
189
  switch (callee.type) {
190
190
  case AST_NODE_TYPES.Identifier:
@@ -200,11 +200,11 @@ function create$14(context) {
200
200
  });
201
201
  return;
202
202
  }
203
- } };
203
+ } });
204
204
  }
205
205
 
206
206
  //#endregion
207
- //#region src/rules/no-hydrate.ts
207
+ //#region src/rules/no-hydrate/no-hydrate.ts
208
208
  const RULE_NAME$13 = "no-hydrate";
209
209
  var no_hydrate_default = createRule({
210
210
  meta: {
@@ -224,21 +224,21 @@ function create$13(context) {
224
224
  if (compare(getSettingsFromContext(context).version, "18.0.0", "<")) return {};
225
225
  const reactDomNames = /* @__PURE__ */ new Set();
226
226
  const hydrateNames = /* @__PURE__ */ new Set();
227
- return {
227
+ return defineRuleListener({
228
228
  CallExpression(node) {
229
229
  switch (true) {
230
230
  case node.callee.type === AST_NODE_TYPES.Identifier && hydrateNames.has(node.callee.name):
231
231
  context.report({
232
+ fix: getFix$2(context, node),
232
233
  messageId: "default",
233
- node,
234
- fix: getFix$2(context, node)
234
+ node
235
235
  });
236
236
  return;
237
237
  case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === hydrate && reactDomNames.has(node.callee.object.name):
238
238
  context.report({
239
+ fix: getFix$2(context, node),
239
240
  messageId: "default",
240
- node,
241
- fix: getFix$2(context, node)
241
+ node
242
242
  });
243
243
  return;
244
244
  }
@@ -257,7 +257,7 @@ function create$13(context) {
257
257
  continue;
258
258
  }
259
259
  }
260
- };
260
+ });
261
261
  }
262
262
  function getFix$2(context, node) {
263
263
  const getText = (n) => context.sourceCode.getText(n);
@@ -269,7 +269,7 @@ function getFix$2(context, node) {
269
269
  }
270
270
 
271
271
  //#endregion
272
- //#region src/rules/no-missing-button-type.ts
272
+ //#region src/rules/no-missing-button-type/no-missing-button-type.ts
273
273
  const RULE_NAME$12 = "no-missing-button-type";
274
274
  const BUTTON_TYPES = [
275
275
  "button",
@@ -293,23 +293,24 @@ var no_missing_button_type_default = createRule({
293
293
  });
294
294
  function create$12(context) {
295
295
  const resolver = createJsxElementResolver(context);
296
- return { JSXElement(node) {
296
+ const jsx = JsxInspector.from(context);
297
+ return defineRuleListener({ JSXElement(node) {
297
298
  if (resolver.resolve(node).domElementType !== "button") return;
298
- if (core.getJsxAttribute(context, node)("type") != null) return;
299
+ if (jsx.hasAttribute(node, "type")) return;
299
300
  context.report({
300
301
  messageId: "missingTypeAttribute",
301
302
  node: node.openingElement,
302
303
  suggest: BUTTON_TYPES.map((type) => ({
303
- messageId: "addTypeAttribute",
304
304
  data: { type },
305
- fix: (fixer) => fixer.insertTextAfter(node.openingElement.name, ` type="${type}"`)
305
+ fix: (fixer) => fixer.insertTextAfter(node.openingElement.name, ` type="${type}"`),
306
+ messageId: "addTypeAttribute"
306
307
  }))
307
308
  });
308
- } };
309
+ } });
309
310
  }
310
311
 
311
312
  //#endregion
312
- //#region src/rules/no-missing-iframe-sandbox.ts
313
+ //#region src/rules/no-missing-iframe-sandbox/no-missing-iframe-sandbox.ts
313
314
  const RULE_NAME$11 = "no-missing-iframe-sandbox";
314
315
  var no_missing_iframe_sandbox_default = createRule({
315
316
  meta: {
@@ -329,43 +330,45 @@ var no_missing_iframe_sandbox_default = createRule({
329
330
  });
330
331
  function create$11(context) {
331
332
  const resolver = createJsxElementResolver(context);
332
- return { JSXElement(node) {
333
+ const jsx = JsxInspector.from(context);
334
+ return defineRuleListener({ JSXElement(node) {
333
335
  const { domElementType } = resolver.resolve(node);
334
336
  if (domElementType !== "iframe") return;
335
- const sandboxProp = core.getJsxAttribute(context, node)("sandbox");
337
+ const sandboxProp = jsx.findAttribute(node, "sandbox");
336
338
  if (sandboxProp == null) {
337
339
  context.report({
338
340
  messageId: "missingSandboxAttribute",
339
341
  node: node.openingElement,
340
342
  suggest: [{
341
- messageId: "addSandboxAttribute",
342
343
  data: { value: "" },
343
344
  fix(fixer) {
344
345
  return fixer.insertTextAfter(node.openingElement.name, ` sandbox=""`);
345
- }
346
+ },
347
+ messageId: "addSandboxAttribute"
346
348
  }]
347
349
  });
348
350
  return;
349
351
  }
350
- const sandboxValue = core.resolveJsxAttributeValue(context, sandboxProp);
351
- if (typeof sandboxValue.toStatic("sandbox") === "string") return;
352
+ const sandboxValue = jsx.resolveAttributeValue(sandboxProp);
353
+ if (typeof sandboxValue.toStatic() === "string") return;
354
+ if (sandboxValue.kind === "spreadProps" && typeof sandboxValue.getProperty("sandbox") === "string") return;
352
355
  context.report({
353
356
  messageId: "missingSandboxAttribute",
354
357
  node: sandboxValue.node ?? sandboxProp,
355
358
  suggest: [{
356
- messageId: "addSandboxAttribute",
357
359
  data: { value: "" },
358
360
  fix(fixer) {
359
361
  if (sandboxValue.kind.startsWith("spread")) return null;
360
362
  return fixer.replaceText(sandboxProp, `sandbox=""`);
361
- }
363
+ },
364
+ messageId: "addSandboxAttribute"
362
365
  }]
363
366
  });
364
- } };
367
+ } });
365
368
  }
366
369
 
367
370
  //#endregion
368
- //#region src/rules/no-namespace.ts
371
+ //#region src/rules/no-namespace/no-namespace.ts
369
372
  const RULE_NAME$10 = "no-namespace";
370
373
  var no_namespace_default = createRule({
371
374
  meta: {
@@ -379,26 +382,33 @@ var no_namespace_default = createRule({
379
382
  defaultOptions: []
380
383
  });
381
384
  function create$10(context) {
382
- return { JSXElement(node) {
383
- const name = core.getJsxElementType(context, node);
385
+ const jsx = JsxInspector.from(context);
386
+ return defineRuleListener({ JSXElement(node) {
387
+ const name = jsx.getElementType(node);
384
388
  if (typeof name !== "string" || !name.includes(":")) return;
385
389
  context.report({
390
+ data: { name },
386
391
  messageId: "default",
387
- node: node.openingElement.name,
388
- data: { name }
392
+ node: node.openingElement.name
389
393
  });
390
- } };
394
+ } });
391
395
  }
392
396
 
393
397
  //#endregion
394
- //#region src/rules/no-render.ts
395
- const RULE_NAME$9 = "no-render";
396
- var no_render_default = createRule({
398
+ //#region src/rules/no-render-return-value/no-render-return-value.ts
399
+ const RULE_NAME$9 = "no-render-return-value";
400
+ const banParentTypes = [
401
+ AST_NODE_TYPES.VariableDeclarator,
402
+ AST_NODE_TYPES.Property,
403
+ AST_NODE_TYPES.ReturnStatement,
404
+ AST_NODE_TYPES.ArrowFunctionExpression,
405
+ AST_NODE_TYPES.AssignmentExpression
406
+ ];
407
+ var no_render_return_value_default = createRule({
397
408
  meta: {
398
409
  type: "problem",
399
- docs: { description: "Replaces usage of 'ReactDOM.render()' with 'createRoot(node).render()'." },
400
- fixable: "code",
401
- messages: { default: "[Deprecated] Use 'createRoot(node).render()' instead." },
410
+ docs: { description: "Disallows the return value of 'ReactDOM.render'." },
411
+ messages: { default: "Do not depend on the return value from 'ReactDOM.render'." },
402
412
  schema: []
403
413
  },
404
414
  name: RULE_NAME$9,
@@ -406,25 +416,21 @@ var no_render_default = createRule({
406
416
  defaultOptions: []
407
417
  });
408
418
  function create$9(context) {
409
- if (!context.sourceCode.text.includes("render")) return {};
410
- if (compare(getSettingsFromContext(context).version, "18.0.0", "<")) return {};
411
419
  const reactDomNames = new Set(["ReactDOM", "ReactDOM"]);
412
420
  const renderNames = /* @__PURE__ */ new Set();
413
- return {
421
+ return defineRuleListener({
414
422
  CallExpression(node) {
415
423
  switch (true) {
416
- case node.callee.type === AST_NODE_TYPES.Identifier && renderNames.has(node.callee.name):
424
+ case node.callee.type === AST_NODE_TYPES.Identifier && renderNames.has(node.callee.name) && banParentTypes.includes(node.parent.type):
417
425
  context.report({
418
426
  messageId: "default",
419
- node,
420
- fix: getFix$1(context, node)
427
+ node
421
428
  });
422
429
  return;
423
- case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "render" && reactDomNames.has(node.callee.object.name):
430
+ case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "render" && reactDomNames.has(node.callee.object.name) && banParentTypes.includes(node.parent.type):
424
431
  context.report({
425
432
  messageId: "default",
426
- node,
427
- fix: getFix$1(context, node)
433
+ node
428
434
  });
429
435
  return;
430
436
  }
@@ -443,38 +449,18 @@ function create$9(context) {
443
449
  continue;
444
450
  }
445
451
  }
446
- };
447
- }
448
- /**
449
- * Provides a fixer function to replace `render(app, container)` with `createRoot(container).render(app)`
450
- * @param context The rule context
451
- * @param node The `CallExpression` node to fix
452
- * @returns A fixer function or null if the fix cannot be applied
453
- */
454
- function getFix$1(context, node) {
455
- const getText = (n) => context.sourceCode.getText(n);
456
- return (fixer) => {
457
- const [arg0, arg1] = node.arguments;
458
- if (arg0 == null || arg1 == null) return null;
459
- return [fixer.insertTextBefore(context.sourceCode.ast, "import { createRoot } from \"react-dom/client\";\n"), fixer.replaceText(node, `createRoot(${getText(arg1)}).render(${getText(arg0)})`)];
460
- };
452
+ });
461
453
  }
462
454
 
463
455
  //#endregion
464
- //#region src/rules/no-render-return-value.ts
465
- const RULE_NAME$8 = "no-render-return-value";
466
- const banParentTypes = [
467
- AST_NODE_TYPES.VariableDeclarator,
468
- AST_NODE_TYPES.Property,
469
- AST_NODE_TYPES.ReturnStatement,
470
- AST_NODE_TYPES.ArrowFunctionExpression,
471
- AST_NODE_TYPES.AssignmentExpression
472
- ];
473
- var no_render_return_value_default = createRule({
456
+ //#region src/rules/no-render/no-render.ts
457
+ const RULE_NAME$8 = "no-render";
458
+ var no_render_default = createRule({
474
459
  meta: {
475
460
  type: "problem",
476
- docs: { description: "Disallows the return value of 'ReactDOM.render'." },
477
- messages: { default: "Do not depend on the return value from 'ReactDOM.render'." },
461
+ docs: { description: "Replaces usage of 'ReactDOM.render()' with 'createRoot(node).render()'." },
462
+ fixable: "code",
463
+ messages: { default: "[Deprecated] Use 'createRoot(node).render()' instead." },
478
464
  schema: []
479
465
  },
480
466
  name: RULE_NAME$8,
@@ -482,19 +468,23 @@ var no_render_return_value_default = createRule({
482
468
  defaultOptions: []
483
469
  });
484
470
  function create$8(context) {
471
+ if (!context.sourceCode.text.includes("render")) return {};
472
+ if (compare(getSettingsFromContext(context).version, "18.0.0", "<")) return {};
485
473
  const reactDomNames = new Set(["ReactDOM", "ReactDOM"]);
486
474
  const renderNames = /* @__PURE__ */ new Set();
487
- return {
475
+ return defineRuleListener({
488
476
  CallExpression(node) {
489
477
  switch (true) {
490
- case node.callee.type === AST_NODE_TYPES.Identifier && renderNames.has(node.callee.name) && banParentTypes.includes(node.parent.type):
478
+ case node.callee.type === AST_NODE_TYPES.Identifier && renderNames.has(node.callee.name):
491
479
  context.report({
480
+ fix: getFix$1(context, node),
492
481
  messageId: "default",
493
482
  node
494
483
  });
495
484
  return;
496
- case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "render" && reactDomNames.has(node.callee.object.name) && banParentTypes.includes(node.parent.type):
485
+ case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "render" && reactDomNames.has(node.callee.object.name):
497
486
  context.report({
487
+ fix: getFix$1(context, node),
498
488
  messageId: "default",
499
489
  node
500
490
  });
@@ -515,11 +505,25 @@ function create$8(context) {
515
505
  continue;
516
506
  }
517
507
  }
508
+ });
509
+ }
510
+ /**
511
+ * Provides a fixer function to replace `render(app, container)` with `createRoot(container).render(app)`
512
+ * @param context The rule context
513
+ * @param node The `CallExpression` node to fix
514
+ * @returns A fixer function or null if the fix cannot be applied
515
+ */
516
+ function getFix$1(context, node) {
517
+ const getText = (n) => context.sourceCode.getText(n);
518
+ return (fixer) => {
519
+ const [arg0, arg1] = node.arguments;
520
+ if (arg0 == null || arg1 == null) return null;
521
+ return [fixer.insertTextBefore(context.sourceCode.ast, "import { createRoot } from \"react-dom/client\";\n"), fixer.replaceText(node, `createRoot(${getText(arg1)}).render(${getText(arg0)})`)];
518
522
  };
519
523
  }
520
524
 
521
525
  //#endregion
522
- //#region src/rules/no-script-url.ts
526
+ //#region src/rules/no-script-url/no-script-url.ts
523
527
  const RULE_NAME$7 = "no-script-url";
524
528
  var no_script_url_default = createRule({
525
529
  meta: {
@@ -533,18 +537,19 @@ var no_script_url_default = createRule({
533
537
  defaultOptions: []
534
538
  });
535
539
  function create$7(context) {
536
- return { JSXAttribute(node) {
540
+ const jsx = JsxInspector.from(context);
541
+ return defineRuleListener({ JSXAttribute(node) {
537
542
  if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.value == null) return;
538
- const value = core.resolveJsxAttributeValue(context, node).toStatic();
543
+ const value = jsx.resolveAttributeValue(node).toStatic();
539
544
  if (typeof value === "string" && RE_JAVASCRIPT_PROTOCOL.test(value)) context.report({
540
545
  messageId: "default",
541
546
  node: node.value
542
547
  });
543
- } };
548
+ } });
544
549
  }
545
550
 
546
551
  //#endregion
547
- //#region src/rules/no-string-style-prop.ts
552
+ //#region src/rules/no-string-style-prop/no-string-style-prop.ts
548
553
  const RULE_NAME$6 = "no-string-style-prop";
549
554
  var no_string_style_prop_default = createRule({
550
555
  meta: {
@@ -558,20 +563,21 @@ var no_string_style_prop_default = createRule({
558
563
  defaultOptions: []
559
564
  });
560
565
  function create$6(context) {
561
- return { JSXElement(node) {
562
- if (!core.isJsxHostElement(context, node)) return;
563
- const styleProp = core.getJsxAttribute(context, node)("style");
566
+ const jsx = JsxInspector.from(context);
567
+ return defineRuleListener({ JSXElement(node) {
568
+ if (!jsx.isHostElement(node)) return;
569
+ const styleProp = jsx.findAttribute(node, "style");
564
570
  if (styleProp == null) return;
565
- const styleValue = core.resolveJsxAttributeValue(context, styleProp);
571
+ const styleValue = jsx.resolveAttributeValue(styleProp);
566
572
  if (typeof styleValue.toStatic() === "string") context.report({
567
573
  messageId: "default",
568
574
  node: styleValue.node ?? styleProp
569
575
  });
570
- } };
576
+ } });
571
577
  }
572
578
 
573
579
  //#endregion
574
- //#region src/rules/no-unknown-property.ts
580
+ //#region src/rules/no-unknown-property/no-unknown-property.ts
575
581
  const RULE_NAME$5 = "no-unknown-property";
576
582
  const DEFAULTS = {
577
583
  ignore: [],
@@ -679,7 +685,6 @@ const SVGDOM_ATTRIBUTE_NAMES = {
679
685
  * Map of attributes that are only valid on specific HTML tags
680
686
  */
681
687
  const ATTRIBUTE_TAGS_MAP = {
682
- as: ["link"],
683
688
  abbr: ["th", "td"],
684
689
  align: [
685
690
  "applet",
@@ -698,6 +703,7 @@ const ATTRIBUTE_TAGS_MAP = {
698
703
  "tr"
699
704
  ],
700
705
  allowFullScreen: ["iframe", "video"],
706
+ as: ["link"],
701
707
  autoPictureInPicture: ["video"],
702
708
  charset: ["meta"],
703
709
  checked: ["input"],
@@ -1562,9 +1568,9 @@ function tagNameHasDot(node) {
1562
1568
  * @returns Standard name or undefined
1563
1569
  */
1564
1570
  function getStandardName(name, context) {
1565
- if (has(DOM_ATTRIBUTE_NAMES, name)) return DOM_ATTRIBUTE_NAMES[name];
1566
- if (has(SVGDOM_ATTRIBUTE_NAMES, name)) return SVGDOM_ATTRIBUTE_NAMES[name];
1567
- return getDOMPropertyNames(context).find((element) => element.toLowerCase() === name.toLowerCase());
1571
+ if (has(DOM_ATTRIBUTE_NAMES, name)) return DOM_ATTRIBUTE_NAMES[name] ?? null;
1572
+ if (has(SVGDOM_ATTRIBUTE_NAMES, name)) return SVGDOM_ATTRIBUTE_NAMES[name] ?? null;
1573
+ return getDOMPropertyNames(context).find((element) => element.toLowerCase() === name.toLowerCase()) ?? null;
1568
1574
  }
1569
1575
  /**
1570
1576
  * Check if an object has a property
@@ -1646,20 +1652,20 @@ function create$5(context) {
1646
1652
  function getRequireDataLowercase() {
1647
1653
  return context.options[0]?.requireDataLowercase ?? DEFAULTS.requireDataLowercase;
1648
1654
  }
1649
- return { JSXAttribute(node) {
1655
+ return defineRuleListener({ JSXAttribute(node) {
1650
1656
  const ignoreNames = getIgnoreConfig();
1651
1657
  const actualName = getText(context, node.name);
1652
- if (ignoreNames.indexOf(actualName) >= 0) return;
1658
+ if (ignoreNames.includes(actualName)) return;
1653
1659
  const name = normalizeAttributeCase(actualName);
1654
1660
  if (tagNameHasDot(node)) return;
1655
1661
  if (isValidDataAttribute(name)) {
1656
1662
  if (getRequireDataLowercase() && hasUpperCaseCharacter(name)) context.report({
1657
- messageId: "dataLowercaseRequired",
1658
- node,
1659
1663
  data: {
1660
1664
  name: actualName,
1661
1665
  lowerCaseName: actualName.toLowerCase()
1662
- }
1666
+ },
1667
+ messageId: "dataLowercaseRequired",
1668
+ node
1663
1669
  });
1664
1670
  return;
1665
1671
  }
@@ -1669,14 +1675,14 @@ function create$5(context) {
1669
1675
  if (!isValidHTMLTagInJSX(node)) return;
1670
1676
  const allowedTags = has(ATTRIBUTE_TAGS_MAP, name) ? ATTRIBUTE_TAGS_MAP[name] : null;
1671
1677
  if (tagName != null && allowedTags != null) {
1672
- if (allowedTags.indexOf(tagName) === -1) context.report({
1673
- messageId: "invalidPropOnTag",
1674
- node,
1678
+ if (!allowedTags.includes(tagName)) context.report({
1675
1679
  data: {
1676
1680
  name: actualName,
1677
1681
  allowedTags: allowedTags.join(", "),
1678
1682
  tagName
1679
- }
1683
+ },
1684
+ messageId: "invalidPropOnTag",
1685
+ node
1680
1686
  });
1681
1687
  return;
1682
1688
  }
@@ -1685,28 +1691,28 @@ function create$5(context) {
1685
1691
  if (standardName != null && standardName === name) return;
1686
1692
  if (hasStandardNameButIsNotUsed) {
1687
1693
  context.report({
1688
- messageId: "unknownPropWithStandardName",
1689
- node,
1690
1694
  data: {
1691
1695
  name: actualName,
1692
1696
  standardName
1693
1697
  },
1694
1698
  fix(fixer) {
1695
1699
  return fixer.replaceText(node.name, standardName);
1696
- }
1700
+ },
1701
+ messageId: "unknownPropWithStandardName",
1702
+ node
1697
1703
  });
1698
1704
  return;
1699
1705
  }
1700
1706
  context.report({
1707
+ data: { name: actualName },
1701
1708
  messageId: "unknownProp",
1702
- node,
1703
- data: { name: actualName }
1709
+ node
1704
1710
  });
1705
- } };
1711
+ } });
1706
1712
  }
1707
1713
 
1708
1714
  //#endregion
1709
- //#region src/rules/no-unsafe-iframe-sandbox.ts
1715
+ //#region src/rules/no-unsafe-iframe-sandbox/no-unsafe-iframe-sandbox.ts
1710
1716
  const RULE_NAME$4 = "no-unsafe-iframe-sandbox";
1711
1717
  const UNSAFE_SANDBOX_VALUES = ["allow-scripts", "allow-same-origin"];
1712
1718
  /**
@@ -1733,20 +1739,21 @@ var no_unsafe_iframe_sandbox_default = createRule({
1733
1739
  });
1734
1740
  function create$4(context) {
1735
1741
  const resolver = createJsxElementResolver(context);
1736
- return { JSXElement(node) {
1742
+ const jsx = JsxInspector.from(context);
1743
+ return defineRuleListener({ JSXElement(node) {
1737
1744
  if (resolver.resolve(node).domElementType !== "iframe") return;
1738
- const sandboxProp = core.getJsxAttribute(context, node)("sandbox");
1745
+ const sandboxProp = jsx.findAttribute(node, "sandbox");
1739
1746
  if (sandboxProp == null) return;
1740
- const sandboxValue = core.resolveJsxAttributeValue(context, sandboxProp);
1741
- if (isUnsafeSandboxCombination(sandboxValue.toStatic("sandbox"))) context.report({
1747
+ const sandboxValue = jsx.resolveAttributeValue(sandboxProp);
1748
+ if (isUnsafeSandboxCombination(sandboxValue.kind === "spreadProps" ? sandboxValue.getProperty("sandbox") : sandboxValue.toStatic())) context.report({
1742
1749
  messageId: "default",
1743
1750
  node: sandboxValue.node ?? sandboxProp
1744
1751
  });
1745
- } };
1752
+ } });
1746
1753
  }
1747
1754
 
1748
1755
  //#endregion
1749
- //#region src/rules/no-unsafe-target-blank.ts
1756
+ //#region src/rules/no-unsafe-target-blank/no-unsafe-target-blank.ts
1750
1757
  const RULE_NAME$3 = "no-unsafe-target-blank";
1751
1758
  /**
1752
1759
  * Check if a value appears to be an external link.
@@ -1786,46 +1793,50 @@ var no_unsafe_target_blank_default = createRule({
1786
1793
  });
1787
1794
  function create$3(context) {
1788
1795
  const resolver = createJsxElementResolver(context);
1789
- return { JSXElement(node) {
1796
+ const jsx = JsxInspector.from(context);
1797
+ return defineRuleListener({ JSXElement(node) {
1790
1798
  const { domElementType } = resolver.resolve(node);
1791
1799
  if (domElementType !== "a") return;
1792
- const findAttribute = core.getJsxAttribute(context, node);
1800
+ const findAttribute = (name) => jsx.findAttribute(node, name);
1793
1801
  const targetProp = findAttribute("target");
1794
1802
  if (targetProp == null) return;
1795
- if (core.resolveJsxAttributeValue(context, targetProp).toStatic("target") !== "_blank") return;
1803
+ const targetValue = jsx.resolveAttributeValue(targetProp);
1804
+ if ((targetValue.kind === "spreadProps" ? targetValue.getProperty("target") : targetValue.toStatic()) !== "_blank") return;
1796
1805
  const hrefProp = findAttribute("href");
1797
1806
  if (hrefProp == null) return;
1798
- if (!isExternalLinkLike(core.resolveJsxAttributeValue(context, hrefProp).toStatic("href"))) return;
1807
+ const hrefValue = jsx.resolveAttributeValue(hrefProp);
1808
+ if (!isExternalLinkLike(hrefValue.kind === "spreadProps" ? hrefValue.getProperty("href") : hrefValue.toStatic())) return;
1799
1809
  const relProp = findAttribute("rel");
1800
1810
  if (relProp == null) {
1801
1811
  context.report({
1802
1812
  messageId: "default",
1803
1813
  node: node.openingElement,
1804
1814
  suggest: [{
1805
- messageId: "addRelNoreferrerNoopener",
1806
1815
  fix(fixer) {
1807
1816
  return fixer.insertTextAfter(node.openingElement.name, ` rel="noreferrer noopener"`);
1808
- }
1817
+ },
1818
+ messageId: "addRelNoreferrerNoopener"
1809
1819
  }]
1810
1820
  });
1811
1821
  return;
1812
1822
  }
1813
- if (isSafeRel(core.resolveJsxAttributeValue(context, relProp).toStatic("rel"))) return;
1823
+ const relValue = jsx.resolveAttributeValue(relProp);
1824
+ if (isSafeRel(relValue.kind === "spreadProps" ? relValue.getProperty("rel") : relValue.toStatic())) return;
1814
1825
  context.report({
1815
1826
  messageId: "default",
1816
1827
  node: relProp,
1817
1828
  suggest: [{
1818
- messageId: "addRelNoreferrerNoopener",
1819
1829
  fix(fixer) {
1820
1830
  return fixer.replaceText(relProp, `rel="noreferrer noopener"`);
1821
- }
1831
+ },
1832
+ messageId: "addRelNoreferrerNoopener"
1822
1833
  }]
1823
1834
  });
1824
- } };
1835
+ } });
1825
1836
  }
1826
1837
 
1827
1838
  //#endregion
1828
- //#region src/rules/no-use-form-state.ts
1839
+ //#region src/rules/no-use-form-state/no-use-form-state.ts
1829
1840
  const RULE_NAME$2 = "no-use-form-state";
1830
1841
  var no_use_form_state_default = createRule({
1831
1842
  meta: {
@@ -1844,21 +1855,21 @@ function create$2(context) {
1844
1855
  if (compare(getSettingsFromContext(context).version, "19.0.0", "<")) return {};
1845
1856
  const reactDomNames = /* @__PURE__ */ new Set();
1846
1857
  const useFormStateNames = /* @__PURE__ */ new Set();
1847
- return {
1858
+ return defineRuleListener({
1848
1859
  CallExpression(node) {
1849
1860
  switch (true) {
1850
1861
  case node.callee.type === AST_NODE_TYPES.Identifier && useFormStateNames.has(node.callee.name):
1851
1862
  context.report({
1863
+ fix: getFix(context, node),
1852
1864
  messageId: "default",
1853
- node,
1854
- fix: getFix(context, node)
1865
+ node
1855
1866
  });
1856
1867
  return;
1857
1868
  case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.object.type === AST_NODE_TYPES.Identifier && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "useFormState" && reactDomNames.has(node.callee.object.name):
1858
1869
  context.report({
1870
+ fix: getFix(context, node),
1859
1871
  messageId: "default",
1860
- node,
1861
- fix: getFix(context, node)
1872
+ node
1862
1873
  });
1863
1874
  return;
1864
1875
  }
@@ -1877,7 +1888,7 @@ function create$2(context) {
1877
1888
  continue;
1878
1889
  }
1879
1890
  }
1880
- };
1891
+ });
1881
1892
  }
1882
1893
  function getFix(context, node) {
1883
1894
  const { importSource } = getSettingsFromContext(context);
@@ -1887,7 +1898,7 @@ function getFix(context, node) {
1887
1898
  }
1888
1899
 
1889
1900
  //#endregion
1890
- //#region src/rules/no-void-elements-with-children.ts
1901
+ //#region src/rules/no-void-elements-with-children/no-void-elements-with-children.ts
1891
1902
  const RULE_NAME$1 = "no-void-elements-with-children";
1892
1903
  const voidElements = new Set([
1893
1904
  "area",
@@ -1920,26 +1931,26 @@ var no_void_elements_with_children_default = createRule({
1920
1931
  });
1921
1932
  function create$1(context) {
1922
1933
  const resolver = createJsxElementResolver(context);
1923
- return { JSXElement(node) {
1934
+ const jsx = JsxInspector.from(context);
1935
+ return defineRuleListener({ JSXElement(node) {
1924
1936
  const { domElementType } = resolver.resolve(node);
1925
1937
  if (!voidElements.has(domElementType)) return;
1926
- const findJsxAttribute = core.getJsxAttribute(context, node);
1927
- const hasChildrenProp = findJsxAttribute("children") != null;
1928
- const hasDangerouslySetInnerHTML = findJsxAttribute("dangerouslySetInnerHTML") != null;
1938
+ const hasChildrenProp = jsx.hasAttribute(node, "children");
1939
+ const hasDangerouslySetInnerHTML = jsx.hasAttribute(node, "dangerouslySetInnerHTML");
1929
1940
  if (node.children.length > 0 || hasChildrenProp || hasDangerouslySetInnerHTML) context.report({
1941
+ data: { elementType: domElementType },
1930
1942
  messageId: "default",
1931
- node,
1932
- data: { elementType: domElementType }
1943
+ node
1933
1944
  });
1934
- } };
1945
+ } });
1935
1946
  }
1936
1947
 
1937
1948
  //#endregion
1938
- //#region src/rules/prefer-namespace-import.ts
1949
+ //#region src/rules/prefer-namespace-import/prefer-namespace-import.ts
1939
1950
  const RULE_NAME = "prefer-namespace-import";
1940
1951
  var prefer_namespace_import_default = createRule({
1941
1952
  meta: {
1942
- type: "problem",
1953
+ type: "suggestion",
1943
1954
  docs: { description: "Enforces importing React DOM via a namespace import." },
1944
1955
  fixable: "code",
1945
1956
  messages: { default: "Prefer importing React DOM via a namespace import." },
@@ -1955,13 +1966,11 @@ const importSources = [
1955
1966
  "react-dom/server"
1956
1967
  ];
1957
1968
  function create(context) {
1958
- return { [`ImportDeclaration ImportDefaultSpecifier`](node) {
1969
+ return defineRuleListener({ [`ImportDeclaration ImportDefaultSpecifier`](node) {
1959
1970
  const importSource = node.parent.source.value;
1960
1971
  if (!importSources.includes(importSource)) return;
1961
1972
  const hasOtherSpecifiers = node.parent.specifiers.length > 1;
1962
1973
  context.report({
1963
- messageId: "default",
1964
- node: hasOtherSpecifiers ? node : node.parent,
1965
1974
  data: { importSource },
1966
1975
  fix(fixer) {
1967
1976
  const importDeclarationText = context.sourceCode.getText(node.parent);
@@ -1972,9 +1981,11 @@ function create(context) {
1972
1981
  if (!hasOtherSpecifiers) return fixer.replaceText(node.parent, `${importStringPrefix} * as ${node.local.name} from ${importSourceQuoted}${semi}`);
1973
1982
  const specifiers = importDeclarationText.slice(importDeclarationText.indexOf("{"), importDeclarationText.indexOf("}") + 1);
1974
1983
  return fixer.replaceText(node.parent, [`${importStringPrefix} * as ${node.local.name} from ${importSourceQuoted}${semi}`, `${importStringPrefix} ${specifiers} from ${importSourceQuoted}${semi}`].join("\n"));
1975
- }
1984
+ },
1985
+ messageId: "default",
1986
+ node: hasOtherSpecifiers ? node : node.parent
1976
1987
  });
1977
- } };
1988
+ } });
1978
1989
  }
1979
1990
 
1980
1991
  //#endregion
@@ -2052,14 +2063,13 @@ const settings = { ...settings$1 };
2052
2063
 
2053
2064
  //#endregion
2054
2065
  //#region src/index.ts
2055
- const { toFlatConfig } = getConfigAdapters("react-dom", plugin);
2056
- var src_default = {
2066
+ const finalPlugin = {
2057
2067
  ...plugin,
2058
2068
  configs: {
2059
- ["recommended"]: toFlatConfig(recommended_exports),
2060
- ["strict"]: toFlatConfig(strict_exports)
2069
+ ["recommended"]: recommended_exports,
2070
+ ["strict"]: strict_exports
2061
2071
  }
2062
2072
  };
2063
2073
 
2064
2074
  //#endregion
2065
- export { src_default as default };
2075
+ export { finalPlugin as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-dom",
3
- "version": "3.0.0-beta.7",
3
+ "version": "3.0.0-beta.71",
4
4
  "description": "ESLint React's ESLint plugin for DOM related rules.",
5
5
  "keywords": [
6
6
  "react",
@@ -43,16 +43,17 @@
43
43
  "@typescript-eslint/utils": "canary",
44
44
  "compare-versions": "^6.1.1",
45
45
  "ts-pattern": "^5.9.0",
46
- "@eslint-react/ast": "3.0.0-beta.7",
47
- "@eslint-react/core": "3.0.0-beta.7",
48
- "@eslint-react/eff": "3.0.0-beta.7",
49
- "@eslint-react/shared": "3.0.0-beta.7",
50
- "@eslint-react/var": "3.0.0-beta.7"
46
+ "@eslint-react/ast": "3.0.0-beta.71",
47
+ "@eslint-react/eff": "3.0.0-beta.71",
48
+ "@eslint-react/core": "3.0.0-beta.71",
49
+ "@eslint-react/shared": "3.0.0-beta.71",
50
+ "@eslint-react/var": "3.0.0-beta.71"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/react": "^19.2.14",
54
54
  "@types/react-dom": "^19.2.3",
55
- "tsdown": "^0.20.3",
55
+ "eslint": "^10.0.2",
56
+ "tsdown": "^0.21.0-beta.2",
56
57
  "@local/configs": "0.0.0"
57
58
  },
58
59
  "peerDependencies": {
@@ -68,6 +69,6 @@
68
69
  "scripts": {
69
70
  "build": "tsdown",
70
71
  "lint:publish": "publint",
71
- "lint:ts": "tsc --noEmit"
72
+ "lint:ts": "tsl"
72
73
  }
73
74
  }