eslint-plugin-react-x 5.3.0-next.2 → 5.3.1-next.0

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.
Files changed (2) hide show
  1. package/dist/index.js +182 -32
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -5,9 +5,9 @@ import { merge } from "@eslint-react/eslint";
5
5
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
6
6
  import { ESLintUtils } from "@typescript-eslint/utils";
7
7
  import { JsxDetectionHint, findParentAttribute, getElementFullType, hasAttribute, isJsxLike } from "@eslint-react/jsx";
8
+ import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
8
9
  import { computeObjectType, isAssignmentTargetEqual, resolve, resolveEnclosingAssignmentTarget } from "@eslint-react/var";
9
10
  import { DefinitionType } from "@typescript-eslint/scope-manager";
10
- import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
11
11
  import { P, isMatching, match } from "ts-pattern";
12
12
  import { compare } from "compare-versions";
13
13
  import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
@@ -93,6 +93,7 @@ const conflictingRules = [
93
93
  "react-hooks/rules-of-hooks",
94
94
  "react-hooks/component-hook-factories",
95
95
  "react-hooks/error-boundaries",
96
+ "react-hooks/globals",
96
97
  "react-hooks/immutability",
97
98
  "react-hooks/purity",
98
99
  "react-hooks/refs",
@@ -113,6 +114,7 @@ var disable_experimental_exports = /* @__PURE__ */ __exportAll({
113
114
  });
114
115
  const name$8 = "react-x/disable-experimental";
115
116
  const rules$8 = {
117
+ "react-x/globals": "off",
116
118
  "react-x/immutability": "off",
117
119
  "react-x/no-duplicate-key": "off",
118
120
  "react-x/no-implicit-children": "off",
@@ -142,7 +144,7 @@ const rules$7 = {
142
144
  //#endregion
143
145
  //#region package.json
144
146
  var name$6 = "eslint-plugin-react-x";
145
- var version = "5.3.0-next.2";
147
+ var version = "5.3.1-next.0";
146
148
 
147
149
  //#endregion
148
150
  //#region src/rules/component-hook-factories/lib.ts
@@ -198,7 +200,7 @@ const createRule = ESLintUtils.RuleCreator(getDocsUrl);
198
200
 
199
201
  //#endregion
200
202
  //#region src/rules/component-hook-factories/component-hook-factories.ts
201
- const RULE_NAME$48 = "component-hook-factories";
203
+ const RULE_NAME$49 = "component-hook-factories";
202
204
  var component_hook_factories_default = createRule({
203
205
  meta: {
204
206
  type: "problem",
@@ -209,11 +211,11 @@ var component_hook_factories_default = createRule({
209
211
  },
210
212
  schema: []
211
213
  },
212
- name: RULE_NAME$48,
213
- create: create$48,
214
+ name: RULE_NAME$49,
215
+ create: create$49,
214
216
  defaultOptions: []
215
217
  });
216
- function create$48(context) {
218
+ function create$49(context) {
217
219
  const hint = core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNumberValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNullValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithStringValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.FunctionComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx | core.FunctionComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback;
218
220
  const fc = core.getFunctionComponentCollector(context, { hint });
219
221
  const cc = core.getClassComponentCollector(context);
@@ -264,7 +266,7 @@ function create$48(context) {
264
266
 
265
267
  //#endregion
266
268
  //#region src/rules/error-boundaries/error-boundaries.ts
267
- const RULE_NAME$47 = "error-boundaries";
269
+ const RULE_NAME$48 = "error-boundaries";
268
270
  var error_boundaries_default = createRule({
269
271
  meta: {
270
272
  type: "problem",
@@ -275,11 +277,11 @@ var error_boundaries_default = createRule({
275
277
  },
276
278
  schema: []
277
279
  },
278
- name: RULE_NAME$47,
279
- create: create$47,
280
+ name: RULE_NAME$48,
281
+ create: create$48,
280
282
  defaultOptions: []
281
283
  });
282
- function create$47(context) {
284
+ function create$48(context) {
283
285
  if (!context.sourceCode.text.includes("try")) return {};
284
286
  const hint = JsxDetectionHint.DoNotIncludeJsxWithNullValue | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue | JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue;
285
287
  const fc = core.getFunctionComponentCollector(context);
@@ -1228,6 +1230,135 @@ function getUnknownDependenciesMessage(reactiveHookName) {
1228
1230
  return `React Hook ${reactiveHookName} received a function whose dependencies are unknown. Pass an inline function instead.`;
1229
1231
  }
1230
1232
 
1233
+ //#endregion
1234
+ //#region src/rules/globals/lib.ts
1235
+ /**
1236
+ * Array methods that mutate the array in place.
1237
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
1238
+ */
1239
+ const MUTATING_ARRAY_METHODS$1 = new Set([
1240
+ "copyWithin",
1241
+ "fill",
1242
+ "pop",
1243
+ "push",
1244
+ "reverse",
1245
+ "shift",
1246
+ "sort",
1247
+ "splice",
1248
+ "unshift"
1249
+ ]);
1250
+
1251
+ //#endregion
1252
+ //#region src/rules/globals/globals.ts
1253
+ const RULE_NAME$47 = "globals";
1254
+ var globals_default = createRule({
1255
+ meta: {
1256
+ type: "problem",
1257
+ docs: { description: "Validates against assignment/mutation of globals during render, part of ensuring that side effects must run outside of render." },
1258
+ messages: {
1259
+ mutatingGlobal: "Do not mutate '{{name}}' during render. Global variables exist outside React's control and make rendering impure.",
1260
+ mutatingGlobalArrayMethod: "Do not call '{{method}}()' on '{{name}}' during render. Mutating global arrays during render makes rendering impure.",
1261
+ mutatingGlobalProperty: "Do not mutate '{{name}}' during render. Modifying global objects during render makes rendering impure."
1262
+ },
1263
+ schema: []
1264
+ },
1265
+ name: RULE_NAME$47,
1266
+ create: create$47,
1267
+ defaultOptions: []
1268
+ });
1269
+ function create$47(context) {
1270
+ const hc = core.getHookCollector(context);
1271
+ const fc = core.getFunctionComponentCollector(context);
1272
+ const violations = [];
1273
+ /**
1274
+ * Return true when `id` refers to a variable defined in the global or module
1275
+ * scope (i.e. outside any function), or when it has no known definition.
1276
+ */
1277
+ function isGlobalOrModuleVariable(id) {
1278
+ const variable = findVariable(context.sourceCode.getScope(id), id);
1279
+ if (variable == null) return true;
1280
+ if (variable.defs.length === 0) return true;
1281
+ const scopeType = variable.scope.type;
1282
+ return scopeType === "global" || scopeType === "module";
1283
+ }
1284
+ /**
1285
+ * Return the nearest enclosing function for `node`.
1286
+ */
1287
+ function getEnclosingFunction(node) {
1288
+ return Traverse.findParent(node, Check.isFunction);
1289
+ }
1290
+ /**
1291
+ * Record a violation that will be filtered at Program:exit.
1292
+ */
1293
+ function recordViolation(node, messageId, data) {
1294
+ const func = getEnclosingFunction(node);
1295
+ if (func == null) return;
1296
+ violations.push({
1297
+ data,
1298
+ func,
1299
+ messageId,
1300
+ node
1301
+ });
1302
+ }
1303
+ return merge(hc.visitor, fc.visitor, {
1304
+ UpdateExpression(node) {
1305
+ const arg = Extract.unwrap(node.argument);
1306
+ if (arg.type === AST_NODE_TYPES.Identifier) {
1307
+ if (!isGlobalOrModuleVariable(arg)) return;
1308
+ recordViolation(node, "mutatingGlobal", { name: arg.name });
1309
+ return;
1310
+ }
1311
+ if (arg.type === AST_NODE_TYPES.MemberExpression) {
1312
+ const rootId = Extract.getRootIdentifier(arg);
1313
+ if (rootId == null) return;
1314
+ if (!isGlobalOrModuleVariable(rootId)) return;
1315
+ recordViolation(node, "mutatingGlobalProperty", { name: context.sourceCode.getText(arg) });
1316
+ }
1317
+ },
1318
+ AssignmentExpression(node) {
1319
+ const left = Extract.unwrap(node.left);
1320
+ if (left.type === AST_NODE_TYPES.Identifier) {
1321
+ if (!isGlobalOrModuleVariable(left)) return;
1322
+ recordViolation(node, "mutatingGlobal", { name: left.name });
1323
+ return;
1324
+ }
1325
+ if (left.type === AST_NODE_TYPES.MemberExpression) {
1326
+ const rootId = Extract.getRootIdentifier(left);
1327
+ if (rootId == null) return;
1328
+ if (!isGlobalOrModuleVariable(rootId)) return;
1329
+ recordViolation(node, "mutatingGlobalProperty", { name: context.sourceCode.getText(left) });
1330
+ }
1331
+ },
1332
+ CallExpression(node) {
1333
+ const callee = Extract.unwrap(node.callee);
1334
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
1335
+ const { object, property } = callee;
1336
+ if (property.type !== AST_NODE_TYPES.Identifier) return;
1337
+ if (!MUTATING_ARRAY_METHODS$1.has(property.name)) return;
1338
+ const rootId = Extract.getRootIdentifier(object);
1339
+ if (rootId == null) return;
1340
+ if (!isGlobalOrModuleVariable(rootId)) return;
1341
+ recordViolation(node, "mutatingGlobalArrayMethod", {
1342
+ name: rootId.name,
1343
+ method: property.name
1344
+ });
1345
+ },
1346
+ "Program:exit"(node) {
1347
+ const comps = fc.api.getAllComponents(node);
1348
+ const hooks = hc.api.getAllHooks(node);
1349
+ const funcs = [...comps, ...hooks];
1350
+ for (const { data, func, messageId, node } of violations) {
1351
+ if (!funcs.some((f) => f.node === func)) continue;
1352
+ context.report({
1353
+ data,
1354
+ messageId,
1355
+ node
1356
+ });
1357
+ }
1358
+ }
1359
+ });
1360
+ }
1361
+
1231
1362
  //#endregion
1232
1363
  //#region src/rules/immutability/lib.ts
1233
1364
  /**
@@ -1323,8 +1454,9 @@ function create$46(context) {
1323
1454
  }
1324
1455
  return merge(hc.visitor, fc.visitor, {
1325
1456
  CallExpression(node) {
1326
- if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return;
1327
- const { object, property } = node.callee;
1457
+ const callee = Extract.unwrap(node.callee);
1458
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
1459
+ const { object, property } = callee;
1328
1460
  if (property.type !== AST_NODE_TYPES.Identifier) return;
1329
1461
  if (!MUTATING_ARRAY_METHODS.has(property.name)) return;
1330
1462
  const rootId = Extract.getRootIdentifier(object);
@@ -1688,7 +1820,7 @@ function getIndexParamPosition(methodName) {
1688
1820
  }
1689
1821
  }
1690
1822
  function getMapIndexParamName(context, node) {
1691
- const { callee } = node;
1823
+ const callee = Extract.unwrap(node.callee);
1692
1824
  if (callee.type !== AST_NODE_TYPES.MemberExpression) return null;
1693
1825
  if (callee.property.type !== AST_NODE_TYPES.Identifier) return null;
1694
1826
  const { name } = callee.property;
@@ -3503,10 +3635,12 @@ function collectUsedPropKeysOfIdentifier(context, usedPropKeys, identifier) {
3503
3635
  return true;
3504
3636
  }
3505
3637
  function collectUsedPropKeysOfReference(context, usedPropKeys, identifier, ref) {
3506
- const { parent } = ref.identifier;
3638
+ let valueNode = ref.identifier;
3639
+ while (Check.isTypeExpression(valueNode.parent) || valueNode.parent.type === AST_NODE_TYPES.ChainExpression) valueNode = valueNode.parent;
3640
+ const parent = valueNode.parent;
3507
3641
  switch (parent.type) {
3508
3642
  case AST_NODE_TYPES.MemberExpression:
3509
- if (parent.object.type === AST_NODE_TYPES.Identifier && parent.object.name === identifier.name) {
3643
+ if (parent.object === valueNode) {
3510
3644
  const key = getKeyOfExpression(parent.property);
3511
3645
  if (key == null) return false;
3512
3646
  usedPropKeys.add(key);
@@ -3514,7 +3648,7 @@ function collectUsedPropKeysOfReference(context, usedPropKeys, identifier, ref)
3514
3648
  }
3515
3649
  break;
3516
3650
  case AST_NODE_TYPES.VariableDeclarator:
3517
- if (parent.id.type === AST_NODE_TYPES.ObjectPattern && parent.init === ref.identifier) return collectUsedPropKeysOfObjectPattern(context, usedPropKeys, parent.id);
3651
+ if (parent.id.type === AST_NODE_TYPES.ObjectPattern && parent.init === valueNode) return collectUsedPropKeysOfObjectPattern(context, usedPropKeys, parent.id);
3518
3652
  break;
3519
3653
  }
3520
3654
  return false;
@@ -4012,7 +4146,8 @@ function isRefCurrentNullCheck(test, refName) {
4012
4146
  const { left, right } = test;
4013
4147
  const checkSides = (a, b) => {
4014
4148
  a = Check.isTypeExpression(a) ? Extract.unwrap(a) : a;
4015
- return a.type === AST_NODE_TYPES.MemberExpression && a.object.type === AST_NODE_TYPES.Identifier && a.object.name === refName && b.type === AST_NODE_TYPES.Literal && b.value == null && Extract.getPropertyName(a.property) === "current";
4149
+ const obj = a.type === AST_NODE_TYPES.MemberExpression ? Extract.unwrap(a.object) : null;
4150
+ return a.type === AST_NODE_TYPES.MemberExpression && obj?.type === AST_NODE_TYPES.Identifier && obj.name === refName && b.type === AST_NODE_TYPES.Literal && b.value == null && Extract.getPropertyName(a.property) === "current";
4016
4151
  };
4017
4152
  return checkSides(left, right) || checkSides(right, left);
4018
4153
  }
@@ -4022,7 +4157,11 @@ function isInitializedFromRef$1(context, name, initialScope) {
4022
4157
  const init = node.init;
4023
4158
  if (init == null) continue;
4024
4159
  switch (true) {
4025
- case init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier && (init.object.name === "ref" || init.object.name.endsWith("Ref")): return true;
4160
+ case init.type === AST_NODE_TYPES.MemberExpression: {
4161
+ const initObj = Extract.unwrap(init.object);
4162
+ if (initObj.type === AST_NODE_TYPES.Identifier && (initObj.name === "ref" || initObj.name.endsWith("Ref"))) return true;
4163
+ break;
4164
+ }
4026
4165
  case init.type === AST_NODE_TYPES.CallExpression && core.isUseRefCall(context, init): return true;
4027
4166
  }
4028
4167
  }
@@ -4054,9 +4193,11 @@ function create$5(context) {
4054
4193
  return merge(hc.visitor, fc.visitor, {
4055
4194
  JSXAttribute(node) {
4056
4195
  switch (true) {
4057
- case node.name.type === AST_NODE_TYPES.JSXIdentifier && node.name.name === "ref" && node.value?.type === AST_NODE_TYPES.JSXExpressionContainer && node.value.expression.type === AST_NODE_TYPES.Identifier:
4058
- jsxRefIdentifiers.add(node.value.expression.name);
4196
+ case node.name.type === AST_NODE_TYPES.JSXIdentifier && node.name.name === "ref" && node.value?.type === AST_NODE_TYPES.JSXExpressionContainer: {
4197
+ const expr = Extract.unwrap(node.value.expression);
4198
+ if (expr.type === AST_NODE_TYPES.Identifier) jsxRefIdentifiers.add(expr.name);
4059
4199
  return;
4200
+ }
4060
4201
  }
4061
4202
  },
4062
4203
  MemberExpression(node) {
@@ -4076,7 +4217,7 @@ function create$5(context) {
4076
4217
  const funcs = new Set([...comps.map((c) => c.node), ...hooks.map((h) => h.node)]);
4077
4218
  const isCompOrHookFn = (n) => Check.isFunction(n) && funcs.has(n);
4078
4219
  for (const { isWrite, node } of refAccesses) {
4079
- const obj = node.object;
4220
+ const obj = Extract.unwrap(node.object);
4080
4221
  if (obj.type !== AST_NODE_TYPES.Identifier) continue;
4081
4222
  switch (true) {
4082
4223
  case obj.name === "ref" || obj.name.endsWith("Ref"):
@@ -5874,9 +6015,10 @@ function isHookName(s) {
5874
6015
  * containing a hook name.
5875
6016
  */
5876
6017
  function isHook(node) {
5877
- if (node.type === "Identifier") return isHookName(node.name);
5878
- else if (node.type === "MemberExpression" && !node.computed && isHook(node.property)) {
5879
- const obj = node.object;
6018
+ const expr = Extract.unwrap(node);
6019
+ if (expr.type === "Identifier") return isHookName(expr.name);
6020
+ else if (expr.type === "MemberExpression" && !expr.computed && isHook(expr.property)) {
6021
+ const obj = expr.object;
5880
6022
  return obj.type === "Identifier" && /^[A-Z].*/.test(obj.name);
5881
6023
  } else return false;
5882
6024
  }
@@ -6210,7 +6352,8 @@ const rule = {
6210
6352
  analyzer.leaveNode(node);
6211
6353
  },
6212
6354
  CallExpression(node) {
6213
- if (isHook(node.callee)) {
6355
+ const callee = Extract.unwrap(node.callee);
6356
+ if (isHook(callee)) {
6214
6357
  const reactHooksMap = last(codePathReactHooksMapStack);
6215
6358
  const codePathSegment = last(codePathSegmentStack);
6216
6359
  let reactHooks = reactHooksMap.get(codePathSegment);
@@ -6218,7 +6361,7 @@ const rule = {
6218
6361
  reactHooks = [];
6219
6362
  reactHooksMap.set(codePathSegment, reactHooks);
6220
6363
  }
6221
- reactHooks.push(node.callee);
6364
+ reactHooks.push(callee);
6222
6365
  }
6223
6366
  const nodeWithoutNamespace = getNodeWithoutReactNamespace(node.callee);
6224
6367
  if ((isEffectIdentifier(nodeWithoutNamespace, additionalEffectHooks) || isUseEffectEventIdentifier(nodeWithoutNamespace)) && node.arguments.length > 0) lastEffect = node;
@@ -6740,10 +6883,13 @@ function create$3(context) {
6740
6883
  //#endregion
6741
6884
  //#region src/rules/unsupported-syntax/lib.ts
6742
6885
  function isEvalCall(node) {
6743
- return node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === "eval";
6886
+ const callee = Extract.unwrap(node.callee);
6887
+ return callee.type === AST_NODE_TYPES.Identifier && callee.name === "eval";
6744
6888
  }
6745
6889
  function isIifeCall(node) {
6746
- return node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node;
6890
+ let parent = node.parent;
6891
+ while (Check.isTypeExpression(parent) || parent.type === AST_NODE_TYPES.ChainExpression) parent = parent.parent;
6892
+ return parent.type === AST_NODE_TYPES.CallExpression && Extract.unwrap(parent.callee) === node;
6747
6893
  }
6748
6894
 
6749
6895
  //#endregion
@@ -6853,9 +6999,10 @@ function create$1(context) {
6853
6999
  }
6854
7000
  const [callbackArg] = node.arguments;
6855
7001
  if (callbackArg == null) return;
6856
- if (!Check.isFunction(callbackArg)) return;
6857
- if (callbackArg.type === AST_NODE_TYPES.ArrowFunctionExpression && callbackArg.body.type !== AST_NODE_TYPES.BlockStatement) return;
6858
- if (callbackArg.body.type !== AST_NODE_TYPES.BlockStatement) return;
7002
+ const callback = Extract.unwrap(callbackArg);
7003
+ if (!Check.isFunction(callback)) return;
7004
+ if (callback.type === AST_NODE_TYPES.ArrowFunctionExpression && callback.body.type !== AST_NODE_TYPES.BlockStatement) return;
7005
+ if (callback.body.type !== AST_NODE_TYPES.BlockStatement) return;
6859
7006
  const returnStatements = getNestedReturnStatements(callbackArg);
6860
7007
  if (returnStatements.length === 0) {
6861
7008
  context.report({
@@ -7041,7 +7188,9 @@ function create(context) {
7041
7188
  }
7042
7189
  }
7043
7190
  }
7044
- if (node.parent.type !== AST_NODE_TYPES.VariableDeclarator) {
7191
+ let parent = node.parent;
7192
+ while (Check.isTypeExpression(parent)) parent = parent.parent;
7193
+ if (parent.type !== AST_NODE_TYPES.VariableDeclarator) {
7045
7194
  if (!enforceAssignment) return;
7046
7195
  context.report({
7047
7196
  messageId: "invalidAssignment",
@@ -7100,6 +7249,7 @@ const plugin = {
7100
7249
  "component-hook-factories": component_hook_factories_default,
7101
7250
  "error-boundaries": error_boundaries_default,
7102
7251
  "exhaustive-deps": rule$1,
7252
+ globals: globals_default,
7103
7253
  immutability: immutability_default,
7104
7254
  "no-access-state-in-setstate": no_access_state_in_setstate_default,
7105
7255
  "no-array-index-key": no_array_index_key_default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "5.3.0-next.2",
3
+ "version": "5.3.1-next.0",
4
4
  "description": "A set of composable ESLint rules for libraries and frameworks that use React as a UI runtime.",
5
5
  "keywords": [
6
6
  "react",
@@ -46,12 +46,12 @@
46
46
  "string-ts": "^2.3.1",
47
47
  "ts-api-utils": "^2.5.0",
48
48
  "ts-pattern": "^5.9.0",
49
- "@eslint-react/ast": "5.3.0-next.2",
50
- "@eslint-react/jsx": "5.3.0-next.2",
51
- "@eslint-react/core": "5.3.0-next.2",
52
- "@eslint-react/shared": "5.3.0-next.2",
53
- "@eslint-react/eslint": "5.3.0-next.2",
54
- "@eslint-react/var": "5.3.0-next.2"
49
+ "@eslint-react/ast": "5.3.1-next.0",
50
+ "@eslint-react/eslint": "5.3.1-next.0",
51
+ "@eslint-react/core": "5.3.1-next.0",
52
+ "@eslint-react/jsx": "5.3.1-next.0",
53
+ "@eslint-react/shared": "5.3.1-next.0",
54
+ "@eslint-react/var": "5.3.1-next.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/react": "^19.2.14",