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

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