@exadev/eslint-config 2.4.0 → 2.6.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.
package/dist/index.cjs CHANGED
@@ -30,8 +30,9 @@ let node_path = require("node:path");
30
30
  let _typescript_eslint_utils = require("@typescript-eslint/utils");
31
31
  let typescript = require("typescript");
32
32
  typescript = __toESM(typescript, 1);
33
+ let ts_api_utils = require("ts-api-utils");
33
34
  //#region package.json
34
- var version = "2.4.0";
35
+ var version = "2.6.0";
35
36
  //#endregion
36
37
  //#region src/rules/barrel-helpers.ts
37
38
  const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
@@ -275,45 +276,45 @@ const MUTATING_INSERT_METHODS$1 = /* @__PURE__ */ new Set([
275
276
  "fill",
276
277
  "copyWithin"
277
278
  ]);
278
- const createRule$2 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
279
+ const createRule$7 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
279
280
  function isArrayIsArrayCall(node) {
280
281
  return node.type === _typescript_eslint_utils.AST_NODE_TYPES.CallExpression && node.callee.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.object.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && node.callee.object.name === "Array" && node.callee.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && node.callee.property.name === "isArray";
281
282
  }
282
- function definitelyExits(statement) {
283
+ function definitelyExits$2(statement) {
283
284
  if (statement.type === _typescript_eslint_utils.AST_NODE_TYPES.ReturnStatement || statement.type === _typescript_eslint_utils.AST_NODE_TYPES.ThrowStatement || statement.type === _typescript_eslint_utils.AST_NODE_TYPES.ContinueStatement || statement.type === _typescript_eslint_utils.AST_NODE_TYPES.BreakStatement) return true;
284
285
  if (statement.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement) {
285
286
  const last = statement.body.at(-1);
286
- return last !== void 0 && definitelyExits(last);
287
+ return last !== void 0 && definitelyExits$2(last);
287
288
  }
288
289
  return false;
289
290
  }
290
- const noArrayIsArrayMutation = createRule$2({
291
+ const noArrayIsArrayMutation = createRule$7({
291
292
  name: "no-array-isarray-mutation",
292
293
  meta: {
293
294
  type: "problem",
294
295
  schema: [],
295
- docs: { description: "Disallow mutating-insertion calls on a parameter whose real type includes a readonly array, narrowed via Array.isArray, which silently discards the declared readonly guarantee." },
296
- messages: { unsound: "'{{ method }}' mutates a parameter narrowed by Array.isArray -- Array.isArray's own type declaration cannot preserve a readonly modifier through the guard, so a caller's genuinely readonly array can be mutated here even though the parameter's real type includes a readonly array. Copy the array before inserting (e.g. a spread into a new array), or narrow with a check that preserves readonly instead of Array.isArray." }
296
+ docs: { description: "Disallow mutating-insertion calls on a parameter or local variable whose real type includes a readonly array, narrowed via Array.isArray, which silently discards the declared readonly guarantee." },
297
+ messages: { unsound: "'{{ method }}' mutates a parameter or local variable narrowed by Array.isArray -- Array.isArray's own type declaration cannot preserve a readonly modifier through the guard, so a value whose real type includes a readonly array (a caller's array, for a parameter; the value's own declared type, for a local variable) can be mutated here despite that readonly guarantee. Copy the array before inserting (e.g. a spread into a new array), or narrow with a check that preserves readonly instead of Array.isArray." }
297
298
  },
298
299
  defaultOptions: [],
299
300
  create(context) {
300
301
  const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
301
302
  const checker = services.program.getTypeChecker();
302
- function parameterHasReadonlyArrayConstituent(parameterNode) {
303
- const tsNode = services.esTreeNodeToTSNodeMap.get(parameterNode);
304
- const parameterType = checker.getTypeAtLocation(tsNode);
305
- return (parameterType.isUnion() ? parameterType.types : [parameterType]).some((constituent) => checker.isArrayType(constituent) && constituent.getSymbol()?.name === "ReadonlyArray");
303
+ function declarationHasReadonlyArrayConstituent(declarationNode) {
304
+ const tsNode = services.esTreeNodeToTSNodeMap.get(declarationNode);
305
+ const declaredType = checker.getTypeAtLocation(tsNode);
306
+ return (declaredType.isUnion() ? declaredType.types : [declaredType]).some((constituent) => checker.isArrayType(constituent) && constituent.getSymbol()?.name === "ReadonlyArray");
306
307
  }
307
308
  return { CallExpression(node) {
308
309
  const { callee } = node;
309
310
  if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || callee.computed || callee.object.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !MUTATING_INSERT_METHODS$1.has(callee.property.name)) return;
310
311
  const variable = context.sourceCode.getScope(node).references.find((reference) => reference.identifier === callee.object)?.resolved;
311
312
  if (!variable) return;
312
- const parameterDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter);
313
- if (!parameterDefinition) return;
314
- const parameterNode = parameterDefinition.name;
315
- if (parameterNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
316
- if (!parameterHasReadonlyArrayConstituent(parameterNode)) return;
313
+ const declarationDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter || definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Variable);
314
+ if (!declarationDefinition) return;
315
+ const declarationNode = declarationDefinition.name;
316
+ if (declarationNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
317
+ if (!declarationHasReadonlyArrayConstituent(declarationNode)) return;
317
318
  if (!isGuardedByArrayIsArray(node, variable, context)) return;
318
319
  context.report({
319
320
  node,
@@ -360,7 +361,7 @@ const noArrayIsArrayMutation = createRule$2({
360
361
  }
361
362
  if (ownIndex > 0) for (let i = ownIndex - 1; i >= 0; i--) {
362
363
  const sibling = statements[i];
363
- if (sibling?.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedArrayIsArrayCall(sibling.test, parameterVariable, ruleContext) && definitelyExits(sibling.consequent)) return true;
364
+ if (sibling?.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedArrayIsArrayCall(sibling.test, parameterVariable, ruleContext) && definitelyExits$2(sibling.consequent)) return true;
364
365
  }
365
366
  }
366
367
  current = parent;
@@ -494,6 +495,107 @@ const noIndexFiles = {
494
495
  }
495
496
  };
496
497
  //#endregion
498
+ //#region src/rules/no-map-instanceof-mutation.ts
499
+ const createRule$6 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
500
+ const MUTATING_MAP_METHODS = /* @__PURE__ */ new Set([
501
+ "set",
502
+ "delete",
503
+ "clear"
504
+ ]);
505
+ function isInstanceofMapExpression(node) {
506
+ return node.type === _typescript_eslint_utils.AST_NODE_TYPES.BinaryExpression && node.operator === "instanceof" && node.right.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && node.right.name === "Map";
507
+ }
508
+ function definitelyExits$1(statement) {
509
+ if (statement.type === _typescript_eslint_utils.AST_NODE_TYPES.ReturnStatement || statement.type === _typescript_eslint_utils.AST_NODE_TYPES.ThrowStatement || statement.type === _typescript_eslint_utils.AST_NODE_TYPES.ContinueStatement || statement.type === _typescript_eslint_utils.AST_NODE_TYPES.BreakStatement) return true;
510
+ if (statement.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement) {
511
+ const last = statement.body.at(-1);
512
+ return last !== void 0 && definitelyExits$1(last);
513
+ }
514
+ return false;
515
+ }
516
+ const noMapInstanceofMutation = createRule$6({
517
+ name: "no-map-instanceof-mutation",
518
+ meta: {
519
+ type: "problem",
520
+ schema: [],
521
+ docs: { description: "Disallow mutating calls on a parameter or local variable whose real type includes a ReadonlyMap, narrowed via `instanceof Map`, which silently discards the declared readonly guarantee." },
522
+ messages: { unsound: "'{{ method }}' mutates a parameter or local variable narrowed by 'instanceof Map' -- Map is declared as extending ReadonlyMap, so 'instanceof Map' narrows straight past the readonly guarantee to the full mutable interface, and a value whose real type includes ReadonlyMap (a caller's map, for a parameter; the value's own declared type, for a local variable) can be mutated here despite that readonly guarantee. Copy the map before mutating (e.g. `new Map(input)`), or narrow with a check that preserves readonly instead of 'instanceof Map'." }
523
+ },
524
+ defaultOptions: [],
525
+ create(context) {
526
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
527
+ const checker = services.program.getTypeChecker();
528
+ function declarationHasReadonlyMapConstituent(declarationNode) {
529
+ const tsNode = services.esTreeNodeToTSNodeMap.get(declarationNode);
530
+ const declaredType = checker.getTypeAtLocation(tsNode);
531
+ return (declaredType.isUnion() ? declaredType.types : [declaredType]).some((constituent) => constituent.getSymbol()?.name === "ReadonlyMap");
532
+ }
533
+ return { CallExpression(node) {
534
+ const { callee } = node;
535
+ if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || callee.computed || callee.object.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !MUTATING_MAP_METHODS.has(callee.property.name)) return;
536
+ const variable = context.sourceCode.getScope(node).references.find((reference) => reference.identifier === callee.object)?.resolved;
537
+ if (!variable) return;
538
+ const declarationDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter || definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Variable);
539
+ if (!declarationDefinition) return;
540
+ const declarationNode = declarationDefinition.name;
541
+ if (declarationNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
542
+ if (!declarationHasReadonlyMapConstituent(declarationNode)) return;
543
+ if (!isGuardedByInstanceofMap(node, variable, context)) return;
544
+ context.report({
545
+ node,
546
+ messageId: "unsound",
547
+ data: { method: callee.property.name }
548
+ });
549
+ } };
550
+ function resolvesToVariable(identifier, target, atNode, ruleContext) {
551
+ return ruleContext.sourceCode.getScope(atNode).references.find((reference) => reference.identifier === identifier)?.resolved === target;
552
+ }
553
+ function isNegatedInstanceofMapExpression(testNode, target, ruleContext) {
554
+ if (testNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.UnaryExpression || testNode.operator !== "!") return false;
555
+ return matchesInstanceofMapOn(testNode.argument, target, ruleContext);
556
+ }
557
+ function matchesInstanceofMapOn(testNode, target, ruleContext) {
558
+ if (!isInstanceofMapExpression(testNode)) return false;
559
+ const { left } = testNode;
560
+ return left.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && resolvesToVariable(left, target, testNode, ruleContext);
561
+ }
562
+ function isGuardedByInstanceofMap(startNode, parameterVariable, ruleContext) {
563
+ let current = startNode;
564
+ while (current.parent) {
565
+ const { parent } = current;
566
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement) {
567
+ if (parent.consequent === current && matchesInstanceofMapOn(parent.test, parameterVariable, ruleContext)) return true;
568
+ if (parent.alternate === current && isNegatedInstanceofMapExpression(parent.test, parameterVariable, ruleContext)) return true;
569
+ }
570
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression && parent.operator === "&&" && parent.right === current && matchesInstanceofMapOn(parent.left, parameterVariable, ruleContext)) return true;
571
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ConditionalExpression && parent.consequent === current && matchesInstanceofMapOn(parent.test, parameterVariable, ruleContext)) return true;
572
+ current = parent;
573
+ }
574
+ return isGuardedByPrecedingEarlyReturn(startNode, parameterVariable, ruleContext);
575
+ }
576
+ function isGuardedByPrecedingEarlyReturn(startNode, parameterVariable, ruleContext) {
577
+ let current = startNode;
578
+ while (current.parent) {
579
+ const { parent } = current;
580
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement || parent.type === _typescript_eslint_utils.AST_NODE_TYPES.Program) {
581
+ const statements = parent.body;
582
+ let ownIndex = -1;
583
+ for (let i = 0; i < statements.length; i++) if (statements[i] === current) {
584
+ ownIndex = i;
585
+ break;
586
+ }
587
+ if (ownIndex > 0) for (let i = ownIndex - 1; i >= 0; i--) {
588
+ const sibling = statements[i];
589
+ if (sibling?.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedInstanceofMapExpression(sibling.test, parameterVariable, ruleContext) && definitelyExits$1(sibling.consequent)) return true;
590
+ }
591
+ }
592
+ current = parent;
593
+ }
594
+ return false;
595
+ }
596
+ }
597
+ });
598
+ //#endregion
497
599
  //#region src/rules/no-mutable-union-array-param.ts
498
600
  const MUTATING_INSERT_METHODS = /* @__PURE__ */ new Set([
499
601
  "push",
@@ -502,7 +604,7 @@ const MUTATING_INSERT_METHODS = /* @__PURE__ */ new Set([
502
604
  "fill",
503
605
  "copyWithin"
504
606
  ]);
505
- const createRule$1 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
607
+ const createRule$5 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
506
608
  function isUnionArrayType(typeAnnotation) {
507
609
  if (typeAnnotation.type === _typescript_eslint_utils.AST_NODE_TYPES.TSArrayType && typeAnnotation.elementType.type === _typescript_eslint_utils.AST_NODE_TYPES.TSUnionType) return typeAnnotation.elementType;
508
610
  if (typeAnnotation.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && typeAnnotation.typeName.name === "Array" && typeAnnotation.typeArguments?.params.length === 1) {
@@ -510,7 +612,7 @@ function isUnionArrayType(typeAnnotation) {
510
612
  if (firstParam?.type === _typescript_eslint_utils.AST_NODE_TYPES.TSUnionType) return firstParam;
511
613
  }
512
614
  }
513
- const noMutableUnionArrayParam = createRule$1({
615
+ const noMutableUnionArrayParam = createRule$5({
514
616
  name: "no-mutable-union-array-param",
515
617
  meta: {
516
618
  type: "problem",
@@ -636,14 +738,14 @@ const noNonBarrelReexport = {
636
738
  };
637
739
  //#endregion
638
740
  //#region src/rules/no-object-assign.ts
639
- const createRule = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
741
+ const createRule$4 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
640
742
  function resolveFrom$1(scope, name) {
641
743
  for (let current = scope; current; current = current.upper) {
642
744
  const found = current.set.get(name);
643
745
  if (found) return found;
644
746
  }
645
747
  }
646
- const noObjectAssign = createRule({
748
+ const noObjectAssign = createRule$4({
647
749
  name: "no-object-assign",
648
750
  meta: {
649
751
  type: "problem",
@@ -718,6 +820,378 @@ function resolveFrom(scope, name) {
718
820
  if (found) return found;
719
821
  }
720
822
  }
823
+ const noPointlessReassignment = {
824
+ meta: {
825
+ type: "problem",
826
+ fixable: "code",
827
+ schema: [],
828
+ messages: { pointlessReassignment: "Pointless reassignment: '{{ name }}' is just an alias for '{{ value }}'. Use the original directly." }
829
+ },
830
+ create(context) {
831
+ return { VariableDeclarator(node) {
832
+ if (node.id.type !== "Identifier" || node.init?.type !== "Identifier" || node.id.name.startsWith("_")) return;
833
+ if (node.parent.type !== "VariableDeclaration" || node.parent.kind !== "const") return;
834
+ const scope = context.sourceCode.getScope(node);
835
+ const sourceVariable = scope.references.find((reference) => reference.identifier === node.init)?.resolved;
836
+ if (!sourceVariable || sourceVariable.references.some((reference) => reference.isWrite() && reference.init !== true)) return;
837
+ const aliasName = node.id.name;
838
+ const originalName = node.init.name;
839
+ const aliasIsAnnotated = hasTypeAnnotation(node.id);
840
+ context.report({
841
+ node,
842
+ messageId: "pointlessReassignment",
843
+ data: {
844
+ name: aliasName,
845
+ value: originalName
846
+ },
847
+ fix(fixer) {
848
+ const variable = scope.set.get(aliasName);
849
+ if (!variable) return null;
850
+ if (aliasIsAnnotated) return null;
851
+ if (variable.references.filter((reference) => reference.isWrite() && reference.identifier !== node.id).length > 0) return null;
852
+ const readRefs = variable.references.filter((reference) => reference.isRead() && isIdentifierReference(reference));
853
+ if (readRefs.some((reference) => {
854
+ const afterToken = context.sourceCode.getTokenAfter(reference.identifier);
855
+ if (afterToken?.value === ":") return false;
856
+ if (afterToken?.value !== "}" && afterToken?.value !== ",") return false;
857
+ let token = context.sourceCode.getTokenBefore(reference.identifier);
858
+ while (token) {
859
+ if (token.value === "{") return true;
860
+ if (token.value === "[" || token.value === "(") return false;
861
+ if (token.value === ":") return false;
862
+ token = context.sourceCode.getTokenBefore(token);
863
+ }
864
+ return false;
865
+ })) return null;
866
+ if (readRefs.some((reference) => resolveFrom(reference.from, originalName) !== sourceVariable)) return null;
867
+ const fixes = readRefs.map((reference) => fixer.replaceText(reference.identifier, originalName));
868
+ const declaration = node.parent;
869
+ if (declaration.type !== "VariableDeclaration" || declaration.declarations.length !== 1) return null;
870
+ fixes.push(fixer.remove(declaration.parent.type === "ExportNamedDeclaration" ? declaration.parent : declaration));
871
+ return fixes;
872
+ }
873
+ });
874
+ } };
875
+ }
876
+ };
877
+ //#endregion
878
+ //#region src/rules/no-set-instanceof-mutation.ts
879
+ const MUTATING_SET_METHODS = /* @__PURE__ */ new Set([
880
+ "add",
881
+ "delete",
882
+ "clear"
883
+ ]);
884
+ const createRule$3 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
885
+ function isSetInstanceofExpression(node) {
886
+ return node.type === _typescript_eslint_utils.AST_NODE_TYPES.BinaryExpression && node.operator === "instanceof" && node.right.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && node.right.name === "Set";
887
+ }
888
+ function definitelyExits(statement) {
889
+ if (statement.type === _typescript_eslint_utils.AST_NODE_TYPES.ReturnStatement || statement.type === _typescript_eslint_utils.AST_NODE_TYPES.ThrowStatement || statement.type === _typescript_eslint_utils.AST_NODE_TYPES.ContinueStatement || statement.type === _typescript_eslint_utils.AST_NODE_TYPES.BreakStatement) return true;
890
+ if (statement.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement) {
891
+ const last = statement.body.at(-1);
892
+ return last !== void 0 && definitelyExits(last);
893
+ }
894
+ return false;
895
+ }
896
+ const noSetInstanceofMutation = createRule$3({
897
+ name: "no-set-instanceof-mutation",
898
+ meta: {
899
+ type: "problem",
900
+ schema: [],
901
+ docs: { description: "Disallow mutating calls on a parameter or local variable whose real type includes a ReadonlySet, narrowed via instanceof Set, which silently discards the declared read-only guarantee." },
902
+ messages: { unsound: "'{{ method }}' mutates a parameter or local variable narrowed by instanceof Set -- instanceof Set's own narrowing widens straight to the mutable Set interface, so a value whose real type includes a ReadonlySet (a caller's set, for a parameter; the value's own declared type, for a local variable) can be mutated here despite that readonly guarantee. Copy the set before mutating (e.g. new Set(input)), or narrow with a check that preserves read-only instead of instanceof Set." }
903
+ },
904
+ defaultOptions: [],
905
+ create(context) {
906
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
907
+ const checker = services.program.getTypeChecker();
908
+ function declarationHasReadonlySetConstituent(declarationNode) {
909
+ const tsNode = services.esTreeNodeToTSNodeMap.get(declarationNode);
910
+ const declaredType = checker.getTypeAtLocation(tsNode);
911
+ return (declaredType.isUnion() ? declaredType.types : [declaredType]).some((constituent) => constituent.getSymbol()?.name === "ReadonlySet");
912
+ }
913
+ return { CallExpression(node) {
914
+ const { callee } = node;
915
+ if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || callee.computed || callee.object.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !MUTATING_SET_METHODS.has(callee.property.name)) return;
916
+ const variable = context.sourceCode.getScope(node).references.find((reference) => reference.identifier === callee.object)?.resolved;
917
+ if (!variable) return;
918
+ const declarationDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter || definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Variable);
919
+ if (!declarationDefinition) return;
920
+ const declarationNode = declarationDefinition.name;
921
+ if (declarationNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
922
+ if (!declarationHasReadonlySetConstituent(declarationNode)) return;
923
+ if (!isGuardedBySetInstanceof(node, variable, context)) return;
924
+ context.report({
925
+ node,
926
+ messageId: "unsound",
927
+ data: { method: callee.property.name }
928
+ });
929
+ } };
930
+ function resolvesToVariable(identifier, target, atNode, ruleContext) {
931
+ return ruleContext.sourceCode.getScope(atNode).references.find((reference) => reference.identifier === identifier)?.resolved === target;
932
+ }
933
+ function isNegatedSetInstanceofExpression(testNode, target, ruleContext) {
934
+ if (testNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.UnaryExpression || testNode.operator !== "!") return false;
935
+ return matchesSetInstanceofOn(testNode.argument, target, ruleContext);
936
+ }
937
+ function matchesSetInstanceofOn(testNode, target, ruleContext) {
938
+ if (!isSetInstanceofExpression(testNode)) return false;
939
+ const { left } = testNode;
940
+ return left.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && resolvesToVariable(left, target, testNode, ruleContext);
941
+ }
942
+ function isGuardedBySetInstanceof(startNode, parameterVariable, ruleContext) {
943
+ let current = startNode;
944
+ while (current.parent) {
945
+ const { parent } = current;
946
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement) {
947
+ if (parent.consequent === current && matchesSetInstanceofOn(parent.test, parameterVariable, ruleContext)) return true;
948
+ if (parent.alternate === current && isNegatedSetInstanceofExpression(parent.test, parameterVariable, ruleContext)) return true;
949
+ }
950
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression && parent.operator === "&&" && parent.right === current && matchesSetInstanceofOn(parent.left, parameterVariable, ruleContext)) return true;
951
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ConditionalExpression && parent.consequent === current && matchesSetInstanceofOn(parent.test, parameterVariable, ruleContext)) return true;
952
+ current = parent;
953
+ }
954
+ return isGuardedByPrecedingEarlyReturn(startNode, parameterVariable, ruleContext);
955
+ }
956
+ function isGuardedByPrecedingEarlyReturn(startNode, parameterVariable, ruleContext) {
957
+ let current = startNode;
958
+ while (current.parent) {
959
+ const { parent } = current;
960
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement || parent.type === _typescript_eslint_utils.AST_NODE_TYPES.Program) {
961
+ const statements = parent.body;
962
+ let ownIndex = -1;
963
+ for (let i = 0; i < statements.length; i++) if (statements[i] === current) {
964
+ ownIndex = i;
965
+ break;
966
+ }
967
+ if (ownIndex > 0) for (let i = ownIndex - 1; i >= 0; i--) {
968
+ const sibling = statements[i];
969
+ if (sibling?.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedSetInstanceofExpression(sibling.test, parameterVariable, ruleContext) && definitelyExits(sibling.consequent)) return true;
970
+ }
971
+ }
972
+ current = parent;
973
+ }
974
+ return false;
975
+ }
976
+ }
977
+ });
978
+ //#endregion
979
+ //#region src/rules/no-side-effects-in-index.ts
980
+ const noSideEffectsInIndex = {
981
+ meta: {
982
+ type: "problem",
983
+ schema: [],
984
+ messages: { notAPureReexport: "A barrel (index) file may contain only re-export statements ('export * from ...' / 'export { x } from ...' / 'export type { x } from ...') -- nothing else, so it can never have a side effect at import time by construction. Found: {{ description }}." }
985
+ },
986
+ create(context) {
987
+ if (!isIndexFile(context.filename)) return {};
988
+ return { Program(node) {
989
+ for (const statement of node.body) if (!isPureReexport(statement)) context.report({
990
+ node: statement,
991
+ messageId: "notAPureReexport",
992
+ data: { description: statement.type }
993
+ });
994
+ } };
995
+ }
996
+ };
997
+ //#endregion
998
+ //#region src/rules/prefer-numeric-sort-compare.ts
999
+ const createRule$2 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
1000
+ const SORT_METHOD_NAMES = /* @__PURE__ */ new Set(["sort", "toSorted"]);
1001
+ function isDefinitelyNumberType(type) {
1002
+ if (type.isUnion()) return type.types.every((constituent) => isDefinitelyNumberType(constituent));
1003
+ return (type.flags & typescript.TypeFlags.NumberLike) !== 0;
1004
+ }
1005
+ const preferNumericSortCompare = createRule$2({
1006
+ name: "prefer-numeric-sort-compare",
1007
+ meta: {
1008
+ type: "suggestion",
1009
+ hasSuggestions: true,
1010
+ docs: { description: "Suggest an ascending numeric compare function for a bare '.sort()'/'.toSorted()' call on an array whose element type is definitively 'number' -- the default comparator sorts lexicographically, so a bare numeric sort is essentially always a bug." },
1011
+ schema: [],
1012
+ messages: {
1013
+ preferNumericCompare: "'.{{ method }}()' on a number array with no compare function sorts lexicographically (e.g. [1, 2, 10].sort() becomes [1, 10, 2]), not in ascending numeric order. Provide a compare function.",
1014
+ addAscendingCompare: "Add an ascending numeric compare function: '(a, b) => a - b'."
1015
+ }
1016
+ },
1017
+ defaultOptions: [],
1018
+ create(context) {
1019
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
1020
+ const checker = services.program.getTypeChecker();
1021
+ return { CallExpression(node) {
1022
+ if (node.arguments.length > 0) return;
1023
+ const { callee } = node;
1024
+ if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || callee.computed) return;
1025
+ if (callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !SORT_METHOD_NAMES.has(callee.property.name)) return;
1026
+ const receiverTsNode = services.esTreeNodeToTSNodeMap.get(callee.object);
1027
+ if (!typescript.isExpression(receiverTsNode)) return;
1028
+ const receiverType = checker.getTypeAtLocation(receiverTsNode);
1029
+ if (!checker.isArrayType(receiverType)) return;
1030
+ if (!(0, ts_api_utils.isTypeReference)(receiverType)) return;
1031
+ const [elementType] = checker.getTypeArguments(receiverType);
1032
+ if (!elementType || !isDefinitelyNumberType(elementType)) return;
1033
+ context.report({
1034
+ node,
1035
+ messageId: "preferNumericCompare",
1036
+ data: { method: callee.property.name },
1037
+ suggest: [{
1038
+ messageId: "addAscendingCompare",
1039
+ fix(fixer) {
1040
+ const closingParen = context.sourceCode.getLastToken(node);
1041
+ if (!closingParen) return null;
1042
+ return fixer.insertTextBefore(closingParen, "(a, b) => a - b");
1043
+ }
1044
+ }]
1045
+ });
1046
+ } };
1047
+ }
1048
+ });
1049
+ //#endregion
1050
+ //#region src/rules/prefer-readonly-array-param.ts
1051
+ const createRule$1 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
1052
+ function getFixableArrayOrTupleType(typeNode) {
1053
+ if (typeNode.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTypeOperator && typeNode.operator === "readonly") return void 0;
1054
+ if (typeNode.type === _typescript_eslint_utils.AST_NODE_TYPES.TSArrayType) return typeNode;
1055
+ if (typeNode.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTupleType) return typeNode;
1056
+ if (typeNode.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTypeReference && typeNode.typeName.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && typeNode.typeName.name === "Array") return typeNode;
1057
+ }
1058
+ function getFixableTypesForAnnotation(typeNode) {
1059
+ if (typeNode.type === _typescript_eslint_utils.AST_NODE_TYPES.TSUnionType) return typeNode.types.flatMap(getFixableTypesForAnnotation);
1060
+ const fixable = getFixableArrayOrTupleType(typeNode);
1061
+ return fixable ? [fixable] : [];
1062
+ }
1063
+ function getAnnotatedParamNode$1(param) {
1064
+ if (param.type === _typescript_eslint_utils.AST_NODE_TYPES.TSParameterProperty) return getAnnotatedParamNode$1(param.parameter);
1065
+ if (param.type === _typescript_eslint_utils.AST_NODE_TYPES.AssignmentPattern) return param.left.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier ? param.left : void 0;
1066
+ if (param.type === _typescript_eslint_utils.AST_NODE_TYPES.RestElement || param.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return param;
1067
+ }
1068
+ const FUNCTION_LIKE_SELECTOR$1 = [
1069
+ "ArrowFunctionExpression",
1070
+ "FunctionDeclaration",
1071
+ "FunctionExpression",
1072
+ "TSCallSignatureDeclaration",
1073
+ "TSConstructSignatureDeclaration",
1074
+ "TSDeclareFunction",
1075
+ "TSEmptyBodyFunctionExpression",
1076
+ "TSFunctionType",
1077
+ "TSMethodSignature"
1078
+ ].join(", ");
1079
+ const preferReadonlyArrayParam = createRule$1({
1080
+ name: "prefer-readonly-array-param",
1081
+ meta: {
1082
+ type: "problem",
1083
+ fixable: "code",
1084
+ docs: { description: "Require array and tuple parameters to be typed readonly, regardless of whether the function body mutates them -- a narrower, safely-autofixable sibling of @typescript-eslint/prefer-readonly-parameter-types scoped to array/tuple shapes only." },
1085
+ schema: [],
1086
+ messages: { preferReadonly: "Array and tuple parameters should be typed readonly ({{ suggestion }}) so a caller can pass a readonly or shared array with confidence, and so any mutation inside the function becomes a deliberate, visible compile error instead of a silent side effect on the caller's data." }
1087
+ },
1088
+ defaultOptions: [],
1089
+ create(context) {
1090
+ function checkParam(param) {
1091
+ const annotatedNode = getAnnotatedParamNode$1(param);
1092
+ if (!annotatedNode?.typeAnnotation) return;
1093
+ const fixableTypes = getFixableTypesForAnnotation(annotatedNode.typeAnnotation.typeAnnotation);
1094
+ if (fixableTypes.length === 0) return;
1095
+ const suggestion = fixableTypes.map((fixableType) => fixableType.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTypeReference ? "ReadonlyArray<T>" : `readonly ${fixableType.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTupleType ? "[T, U]" : "T[]"}`).join(" / ");
1096
+ context.report({
1097
+ node: param,
1098
+ messageId: "preferReadonly",
1099
+ data: { suggestion },
1100
+ fix(fixer) {
1101
+ return fixableTypes.map((fixableType) => fixableType.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTypeReference ? fixer.replaceText(fixableType.typeName, "ReadonlyArray") : fixer.insertTextBefore(fixableType, "readonly "));
1102
+ }
1103
+ });
1104
+ }
1105
+ return { [FUNCTION_LIKE_SELECTOR$1](node) {
1106
+ for (const param of node.params) checkParam(param);
1107
+ } };
1108
+ }
1109
+ });
1110
+ //#endregion
1111
+ //#region src/rules/prefer-readonly-object-param.ts
1112
+ const createRule = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
1113
+ function getAnnotatedParamNode(param) {
1114
+ if (param.type === _typescript_eslint_utils.AST_NODE_TYPES.TSParameterProperty) return getAnnotatedParamNode(param.parameter);
1115
+ if (param.type === _typescript_eslint_utils.AST_NODE_TYPES.AssignmentPattern) return param.left.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier ? param.left : void 0;
1116
+ if (param.type === _typescript_eslint_utils.AST_NODE_TYPES.RestElement || param.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return param;
1117
+ }
1118
+ const FUNCTION_LIKE_SELECTOR = [
1119
+ "ArrowFunctionExpression",
1120
+ "FunctionDeclaration",
1121
+ "FunctionExpression",
1122
+ "TSCallSignatureDeclaration",
1123
+ "TSConstructSignatureDeclaration",
1124
+ "TSDeclareFunction",
1125
+ "TSEmptyBodyFunctionExpression",
1126
+ "TSFunctionType",
1127
+ "TSMethodSignature"
1128
+ ].join(", ");
1129
+ function getCandidateTypeNode(typeNode) {
1130
+ if (typeNode.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTypeLiteral) return typeNode;
1131
+ if (typeNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.TSTypeReference) return void 0;
1132
+ if (typeNode.typeName.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return void 0;
1133
+ if (typeNode.typeName.name === "Readonly") return void 0;
1134
+ return typeNode;
1135
+ }
1136
+ const PRIMITIVE_LIKE_FLAGS = typescript.TypeFlags.StringLike | typescript.TypeFlags.NumberLike | typescript.TypeFlags.BooleanLike | typescript.TypeFlags.BigIntLike | typescript.TypeFlags.ESSymbolLike | typescript.TypeFlags.Null | typescript.TypeFlags.Undefined;
1137
+ function isFlatPropertyType(checker, type) {
1138
+ if (type.isUnion()) return type.types.every((constituent) => isFlatPropertyType(checker, constituent));
1139
+ if ((type.flags & PRIMITIVE_LIKE_FLAGS) !== 0) return true;
1140
+ return checker.getSignaturesOfType(type, typescript.SignatureKind.Call).length > 0 && checker.getPropertiesOfType(type).length === 0 && checker.getIndexInfosOfType(type).length === 0;
1141
+ }
1142
+ function isFlatObjectType(checker, type, location) {
1143
+ if (type.flags & (typescript.TypeFlags.Union | typescript.TypeFlags.Intersection | typescript.TypeFlags.TypeParameter)) return false;
1144
+ if (checker.isArrayType(type) || checker.isTupleType(type)) return false;
1145
+ if (checker.getSignaturesOfType(type, typescript.SignatureKind.Call).length > 0) return false;
1146
+ if (checker.getSignaturesOfType(type, typescript.SignatureKind.Construct).length > 0) return false;
1147
+ if ((type.getSymbol()?.flags ?? 0) & typescript.SymbolFlags.Class) return false;
1148
+ const symbolName = type.getSymbol()?.name;
1149
+ if (symbolName === "Map" || symbolName === "ReadonlyMap" || symbolName === "Set" || symbolName === "ReadonlySet") return false;
1150
+ for (const property of checker.getPropertiesOfType(type)) if (!isFlatPropertyType(checker, checker.getTypeOfSymbolAtLocation(property, location))) return false;
1151
+ for (const indexInfo of checker.getIndexInfosOfType(type)) if (!isFlatPropertyType(checker, indexInfo.type)) return false;
1152
+ return true;
1153
+ }
1154
+ function isAlreadyFullyReadonly(checker, type) {
1155
+ const properties = checker.getPropertiesOfType(type);
1156
+ const indexInfos = checker.getIndexInfosOfType(type);
1157
+ if (properties.length === 0 && indexInfos.length === 0) return false;
1158
+ return properties.every((property) => (0, ts_api_utils.isPropertyReadonlyInType)(type, property.getEscapedName(), checker)) && indexInfos.every((indexInfo) => indexInfo.isReadonly);
1159
+ }
1160
+ const preferReadonlyObjectParam = createRule({
1161
+ name: "prefer-readonly-object-param",
1162
+ meta: {
1163
+ type: "problem",
1164
+ fixable: "code",
1165
+ docs: { description: "Require a 'flat' object parameter (every property is a primitive or a callback, so a shallow wrapper is provably sufficient) to be typed 'Readonly<T>' -- a narrower, safely-autofixable sibling of @typescript-eslint/prefer-readonly-parameter-types and of this package's own prefer-readonly-array-param, scoped to the object shapes where a shallow fix is genuinely complete." },
1166
+ schema: [],
1167
+ messages: { preferReadonlyObject: "This object parameter is 'flat' -- every property (and index-signature value, if any) is a primitive or a callback, so there is no nested mutable state a shallow wrapper could miss. Wrap it in 'Readonly<...>' so a caller can pass a readonly or shared object with confidence, and so any attempt to mutate it inside the function becomes a deliberate, visible compile error instead of a silent side effect on the caller's data." }
1168
+ },
1169
+ defaultOptions: [],
1170
+ create(context) {
1171
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
1172
+ const checker = services.program.getTypeChecker();
1173
+ function checkParam(param) {
1174
+ const annotatedNode = getAnnotatedParamNode(param);
1175
+ if (!annotatedNode?.typeAnnotation) return;
1176
+ const candidateTypeNode = getCandidateTypeNode(annotatedNode.typeAnnotation.typeAnnotation);
1177
+ if (!candidateTypeNode) return;
1178
+ const tsNode = services.esTreeNodeToTSNodeMap.get(annotatedNode);
1179
+ const type = checker.getTypeAtLocation(tsNode);
1180
+ if (!isFlatObjectType(checker, type, tsNode)) return;
1181
+ if (isAlreadyFullyReadonly(checker, type)) return;
1182
+ context.report({
1183
+ node: param,
1184
+ messageId: "preferReadonlyObject",
1185
+ fix(fixer) {
1186
+ return [fixer.insertTextBefore(candidateTypeNode, "Readonly<"), fixer.insertTextAfter(candidateTypeNode, ">")];
1187
+ }
1188
+ });
1189
+ }
1190
+ return { [FUNCTION_LIKE_SELECTOR](node) {
1191
+ for (const param of node.params) checkParam(param);
1192
+ } };
1193
+ }
1194
+ });
721
1195
  //#endregion
722
1196
  //#region src/plugin.ts
723
1197
  const plugin = {
@@ -733,81 +1207,17 @@ const plugin = {
733
1207
  "no-enum-number-widening": noEnumNumberWidening,
734
1208
  "no-enum-reverse-lookup-widening": noEnumReverseLookupWidening,
735
1209
  "no-index-files": noIndexFiles,
1210
+ "no-map-instanceof-mutation": noMapInstanceofMutation,
736
1211
  "no-mutable-union-array-param": noMutableUnionArrayParam,
737
1212
  "no-non-barrel-index": noNonBarrelIndex,
738
1213
  "no-non-barrel-reexport": noNonBarrelReexport,
739
1214
  "no-object-assign": noObjectAssign,
740
- "no-pointless-reassignment": {
741
- meta: {
742
- type: "problem",
743
- fixable: "code",
744
- schema: [],
745
- messages: { pointlessReassignment: "Pointless reassignment: '{{ name }}' is just an alias for '{{ value }}'. Use the original directly." }
746
- },
747
- create(context) {
748
- return { VariableDeclarator(node) {
749
- if (node.id.type !== "Identifier" || node.init?.type !== "Identifier" || node.id.name.startsWith("_")) return;
750
- if (node.parent.type !== "VariableDeclaration" || node.parent.kind !== "const") return;
751
- const scope = context.sourceCode.getScope(node);
752
- const sourceVariable = scope.references.find((reference) => reference.identifier === node.init)?.resolved;
753
- if (!sourceVariable || sourceVariable.references.some((reference) => reference.isWrite() && reference.init !== true)) return;
754
- const aliasName = node.id.name;
755
- const originalName = node.init.name;
756
- const aliasIsAnnotated = hasTypeAnnotation(node.id);
757
- context.report({
758
- node,
759
- messageId: "pointlessReassignment",
760
- data: {
761
- name: aliasName,
762
- value: originalName
763
- },
764
- fix(fixer) {
765
- const variable = scope.set.get(aliasName);
766
- if (!variable) return null;
767
- if (aliasIsAnnotated) return null;
768
- if (variable.references.filter((reference) => reference.isWrite() && reference.identifier !== node.id).length > 0) return null;
769
- const readRefs = variable.references.filter((reference) => reference.isRead() && isIdentifierReference(reference));
770
- if (readRefs.some((reference) => {
771
- const afterToken = context.sourceCode.getTokenAfter(reference.identifier);
772
- if (afterToken?.value === ":") return false;
773
- if (afterToken?.value !== "}" && afterToken?.value !== ",") return false;
774
- let token = context.sourceCode.getTokenBefore(reference.identifier);
775
- while (token) {
776
- if (token.value === "{") return true;
777
- if (token.value === "[" || token.value === "(") return false;
778
- if (token.value === ":") return false;
779
- token = context.sourceCode.getTokenBefore(token);
780
- }
781
- return false;
782
- })) return null;
783
- if (readRefs.some((reference) => resolveFrom(reference.from, originalName) !== sourceVariable)) return null;
784
- const fixes = readRefs.map((reference) => fixer.replaceText(reference.identifier, originalName));
785
- const declaration = node.parent;
786
- if (declaration.type !== "VariableDeclaration" || declaration.declarations.length !== 1) return null;
787
- fixes.push(fixer.remove(declaration.parent.type === "ExportNamedDeclaration" ? declaration.parent : declaration));
788
- return fixes;
789
- }
790
- });
791
- } };
792
- }
793
- },
794
- "no-side-effects-in-index": {
795
- meta: {
796
- type: "problem",
797
- schema: [],
798
- messages: { notAPureReexport: "A barrel (index) file may contain only re-export statements ('export * from ...' / 'export { x } from ...' / 'export type { x } from ...') -- nothing else, so it can never have a side effect at import time by construction. Found: {{ description }}." }
799
- },
800
- create(context) {
801
- if (!isIndexFile(context.filename)) return {};
802
- return { Program(node) {
803
- for (const statement of node.body) if (!isPureReexport(statement)) context.report({
804
- node: statement,
805
- messageId: "notAPureReexport",
806
- data: { description: statement.type }
807
- });
808
- } };
809
- }
810
- }
1215
+ "no-pointless-reassignment": noPointlessReassignment,
1216
+ "no-set-instanceof-mutation": noSetInstanceofMutation,
1217
+ "no-side-effects-in-index": noSideEffectsInIndex,
1218
+ "prefer-numeric-sort-compare": preferNumericSortCompare,
1219
+ "prefer-readonly-array-param": preferReadonlyArrayParam,
1220
+ "prefer-readonly-object-param": preferReadonlyObjectParam
811
1221
  },
812
1222
  configs: {
813
1223
  get recommended() {
@@ -818,7 +1228,8 @@ const plugin = {
818
1228
  "exadev/barrel-policy": ["error", { mode: "banned" }],
819
1229
  "exadev/no-mutable-union-array-param": "error",
820
1230
  "exadev/no-object-assign": "error",
821
- "exadev/no-pointless-reassignment": "error"
1231
+ "exadev/no-pointless-reassignment": "error",
1232
+ "exadev/prefer-readonly-array-param": "error"
822
1233
  }
823
1234
  };
824
1235
  },
@@ -844,11 +1255,18 @@ const recommendedTypeChecked = [
844
1255
  "exadev/no-array-isarray-mutation": "error",
845
1256
  "exadev/no-enum-number-widening": "error",
846
1257
  "exadev/no-enum-reverse-lookup-widening": "error",
1258
+ "exadev/no-map-instanceof-mutation": "error",
847
1259
  "exadev/no-mutable-union-array-param": "error",
848
1260
  "exadev/no-object-assign": "error",
849
1261
  "exadev/no-pointless-reassignment": "error",
1262
+ "exadev/no-set-instanceof-mutation": "error",
1263
+ "exadev/prefer-numeric-sort-compare": "error",
1264
+ "exadev/prefer-readonly-array-param": "error",
1265
+ "exadev/prefer-readonly-object-param": "error",
850
1266
  "@typescript-eslint/ban-ts-comment": ["error", { "ts-expect-error": true }],
851
1267
  "@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "never" }],
1268
+ "@typescript-eslint/consistent-type-exports": "error",
1269
+ "@typescript-eslint/consistent-type-imports": "error",
852
1270
  "@typescript-eslint/method-signature-style": ["error", "property"],
853
1271
  "@typescript-eslint/no-deprecated": "error",
854
1272
  "@typescript-eslint/no-magic-numbers": ["error", {
@@ -868,6 +1286,7 @@ const recommendedTypeChecked = [
868
1286
  "@typescript-eslint/no-non-null-assertion": "error",
869
1287
  "@typescript-eslint/no-unnecessary-condition": "error",
870
1288
  "@typescript-eslint/prefer-readonly": "error",
1289
+ "@typescript-eslint/promise-function-async": "error",
871
1290
  "@typescript-eslint/require-array-sort-compare": "error",
872
1291
  "@typescript-eslint/strict-boolean-expressions": "error",
873
1292
  "@typescript-eslint/switch-exhaustiveness-check": "error",