@exadev/eslint-config 2.3.0 → 2.5.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.3.0";
35
+ var version = "2.5.0";
35
36
  //#endregion
36
37
  //#region src/rules/barrel-helpers.ts
37
38
  const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
@@ -267,6 +268,109 @@ const barrelPolicy = {
267
268
  }
268
269
  };
269
270
  //#endregion
271
+ //#region src/rules/no-array-isarray-mutation.ts
272
+ const MUTATING_INSERT_METHODS$1 = /* @__PURE__ */ new Set([
273
+ "push",
274
+ "unshift",
275
+ "splice",
276
+ "fill",
277
+ "copyWithin"
278
+ ]);
279
+ const createRule$6 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
280
+ function isArrayIsArrayCall(node) {
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";
282
+ }
283
+ function definitelyExits$2(statement) {
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;
285
+ if (statement.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement) {
286
+ const last = statement.body.at(-1);
287
+ return last !== void 0 && definitelyExits$2(last);
288
+ }
289
+ return false;
290
+ }
291
+ const noArrayIsArrayMutation = createRule$6({
292
+ name: "no-array-isarray-mutation",
293
+ meta: {
294
+ type: "problem",
295
+ schema: [],
296
+ 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." },
297
+ 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." }
298
+ },
299
+ defaultOptions: [],
300
+ create(context) {
301
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
302
+ const checker = services.program.getTypeChecker();
303
+ function parameterHasReadonlyArrayConstituent(parameterNode) {
304
+ const tsNode = services.esTreeNodeToTSNodeMap.get(parameterNode);
305
+ const parameterType = checker.getTypeAtLocation(tsNode);
306
+ return (parameterType.isUnion() ? parameterType.types : [parameterType]).some((constituent) => checker.isArrayType(constituent) && constituent.getSymbol()?.name === "ReadonlyArray");
307
+ }
308
+ return { CallExpression(node) {
309
+ const { callee } = node;
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;
311
+ const variable = context.sourceCode.getScope(node).references.find((reference) => reference.identifier === callee.object)?.resolved;
312
+ if (!variable) return;
313
+ const parameterDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter);
314
+ if (!parameterDefinition) return;
315
+ const parameterNode = parameterDefinition.name;
316
+ if (parameterNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
317
+ if (!parameterHasReadonlyArrayConstituent(parameterNode)) return;
318
+ if (!isGuardedByArrayIsArray(node, variable, context)) return;
319
+ context.report({
320
+ node,
321
+ messageId: "unsound",
322
+ data: { method: callee.property.name }
323
+ });
324
+ } };
325
+ function resolvesToVariable(identifier, target, atNode, ruleContext) {
326
+ return ruleContext.sourceCode.getScope(atNode).references.find((reference) => reference.identifier === identifier)?.resolved === target;
327
+ }
328
+ function isNegatedArrayIsArrayCall(testNode, target, ruleContext) {
329
+ if (testNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.UnaryExpression || testNode.operator !== "!") return false;
330
+ return matchesArrayIsArrayOn(testNode.argument, target, ruleContext);
331
+ }
332
+ function matchesArrayIsArrayOn(testNode, target, ruleContext) {
333
+ if (!isArrayIsArrayCall(testNode)) return false;
334
+ const [argument] = testNode.arguments;
335
+ return argument?.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && resolvesToVariable(argument, target, testNode, ruleContext);
336
+ }
337
+ function isGuardedByArrayIsArray(startNode, parameterVariable, ruleContext) {
338
+ let current = startNode;
339
+ while (current.parent) {
340
+ const { parent } = current;
341
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement) {
342
+ if (parent.consequent === current && matchesArrayIsArrayOn(parent.test, parameterVariable, ruleContext)) return true;
343
+ if (parent.alternate === current && isNegatedArrayIsArrayCall(parent.test, parameterVariable, ruleContext)) return true;
344
+ }
345
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression && parent.operator === "&&" && parent.right === current && matchesArrayIsArrayOn(parent.left, parameterVariable, ruleContext)) return true;
346
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ConditionalExpression && parent.consequent === current && matchesArrayIsArrayOn(parent.test, parameterVariable, ruleContext)) return true;
347
+ current = parent;
348
+ }
349
+ return isGuardedByPrecedingEarlyReturn(startNode, parameterVariable, ruleContext);
350
+ }
351
+ function isGuardedByPrecedingEarlyReturn(startNode, parameterVariable, ruleContext) {
352
+ let current = startNode;
353
+ while (current.parent) {
354
+ const { parent } = current;
355
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement || parent.type === _typescript_eslint_utils.AST_NODE_TYPES.Program) {
356
+ const statements = parent.body;
357
+ let ownIndex = -1;
358
+ for (let i = 0; i < statements.length; i++) if (statements[i] === current) {
359
+ ownIndex = i;
360
+ break;
361
+ }
362
+ if (ownIndex > 0) for (let i = ownIndex - 1; i >= 0; i--) {
363
+ const sibling = statements[i];
364
+ if (sibling?.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedArrayIsArrayCall(sibling.test, parameterVariable, ruleContext) && definitelyExits$2(sibling.consequent)) return true;
365
+ }
366
+ }
367
+ current = parent;
368
+ }
369
+ return false;
370
+ }
371
+ }
372
+ });
373
+ //#endregion
270
374
  //#region src/rules/no-enum-number-widening.ts
271
375
  const noEnumNumberWidening = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`)({
272
376
  name: "no-enum-number-widening",
@@ -312,6 +416,67 @@ const noEnumNumberWidening = _typescript_eslint_utils.ESLintUtils.RuleCreator((n
312
416
  }
313
417
  });
314
418
  //#endregion
419
+ //#region src/rules/no-enum-reverse-lookup-widening.ts
420
+ const noEnumReverseLookupWidening = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`)({
421
+ name: "no-enum-reverse-lookup-widening",
422
+ meta: {
423
+ type: "problem",
424
+ hasSuggestions: true,
425
+ docs: { description: "Disallow indexing a numeric enum's reverse mapping with a bare (non-literal) number -- TypeScript types the result as plain 'string' for any number, including one outside the enum's actual member range, where it genuinely returns 'undefined' at runtime." },
426
+ schema: [],
427
+ messages: {
428
+ widening: "Indexing the numeric enum '{{ enumName }}' with a plain 'number' relies on its reverse mapping, which TypeScript types as 'string' for any number -- including one outside the enum's actual members, where this genuinely returns 'undefined' at runtime. Narrow the index to a known member first (a runtime membership check against the enum's own values), or accept that the result may be 'undefined' and handle it.",
429
+ suggestWidenAnnotation: "Widen this variable's annotation to 'string | undefined' so later uses of it as a bare 'string' surface as real compile errors you can resolve."
430
+ }
431
+ },
432
+ defaultOptions: [],
433
+ create(context) {
434
+ const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
435
+ const checker = services.program.getTypeChecker();
436
+ return { MemberExpression(node) {
437
+ if (!node.computed) return;
438
+ const objectTsNode = services.esTreeNodeToTSNodeMap.get(node.object);
439
+ if (!typescript.isExpression(objectTsNode)) return;
440
+ const objectType = checker.getTypeAtLocation(objectTsNode);
441
+ const objectSymbol = objectType.getSymbol();
442
+ if (!objectSymbol || !(objectSymbol.flags & typescript.SymbolFlags.Enum)) return;
443
+ if (!checker.getIndexInfoOfType(objectType, typescript.IndexKind.Number)) return;
444
+ const propertyTsNode = services.esTreeNodeToTSNodeMap.get(node.property);
445
+ if (!typescript.isExpression(propertyTsNode)) return;
446
+ const rawPropertyType = checker.getTypeAtLocation(propertyTsNode);
447
+ const propertyType = checker.getBaseConstraintOfType(rawPropertyType) ?? rawPropertyType;
448
+ if (propertyType.flags & typescript.TypeFlags.EnumLike) {
449
+ if (checker.isTypeAssignableTo(propertyType, checker.getDeclaredTypeOfSymbol(objectSymbol))) return;
450
+ } else {
451
+ if (propertyType.isLiteral()) return;
452
+ if (!(propertyType.flags & typescript.TypeFlags.NumberLike)) return;
453
+ }
454
+ const enumName = checker.typeToString(checker.getDeclaredTypeOfSymbol(objectSymbol));
455
+ const parent = node.parent;
456
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.VariableDeclarator && parent.init === node && parent.id.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && parent.id.typeAnnotation?.typeAnnotation.type === _typescript_eslint_utils.AST_NODE_TYPES.TSStringKeyword) {
457
+ const stringKeyword = parent.id.typeAnnotation.typeAnnotation;
458
+ context.report({
459
+ node,
460
+ messageId: "widening",
461
+ data: { enumName },
462
+ suggest: [{
463
+ messageId: "suggestWidenAnnotation",
464
+ fix(fixer) {
465
+ return fixer.replaceText(stringKeyword, "string | undefined");
466
+ }
467
+ }]
468
+ });
469
+ return;
470
+ }
471
+ context.report({
472
+ node,
473
+ messageId: "widening",
474
+ data: { enumName }
475
+ });
476
+ } };
477
+ }
478
+ });
479
+ //#endregion
315
480
  //#region src/rules/no-index-files.ts
316
481
  const noIndexFiles = {
317
482
  meta: {
@@ -330,6 +495,107 @@ const noIndexFiles = {
330
495
  }
331
496
  };
332
497
  //#endregion
498
+ //#region src/rules/no-map-instanceof-mutation.ts
499
+ const createRule$5 = _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$5({
517
+ name: "no-map-instanceof-mutation",
518
+ meta: {
519
+ type: "problem",
520
+ schema: [],
521
+ docs: { description: "Disallow mutating calls on a parameter whose real type includes a ReadonlyMap, narrowed via `instanceof Map`, which silently discards the declared readonly guarantee." },
522
+ messages: { unsound: "'{{ method }}' mutates a parameter 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 caller's genuinely read-only ReadonlyMap can be mutated here even though the parameter's real type includes ReadonlyMap. 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 parameterHasReadonlyMapConstituent(parameterNode) {
529
+ const tsNode = services.esTreeNodeToTSNodeMap.get(parameterNode);
530
+ const parameterType = checker.getTypeAtLocation(tsNode);
531
+ return (parameterType.isUnion() ? parameterType.types : [parameterType]).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 parameterDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter);
539
+ if (!parameterDefinition) return;
540
+ const parameterNode = parameterDefinition.name;
541
+ if (parameterNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
542
+ if (!parameterHasReadonlyMapConstituent(parameterNode)) 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
333
599
  //#region src/rules/no-mutable-union-array-param.ts
334
600
  const MUTATING_INSERT_METHODS = /* @__PURE__ */ new Set([
335
601
  "push",
@@ -338,7 +604,7 @@ const MUTATING_INSERT_METHODS = /* @__PURE__ */ new Set([
338
604
  "fill",
339
605
  "copyWithin"
340
606
  ]);
341
- const createRule$1 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
607
+ const createRule$4 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
342
608
  function isUnionArrayType(typeAnnotation) {
343
609
  if (typeAnnotation.type === _typescript_eslint_utils.AST_NODE_TYPES.TSArrayType && typeAnnotation.elementType.type === _typescript_eslint_utils.AST_NODE_TYPES.TSUnionType) return typeAnnotation.elementType;
344
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) {
@@ -346,7 +612,7 @@ function isUnionArrayType(typeAnnotation) {
346
612
  if (firstParam?.type === _typescript_eslint_utils.AST_NODE_TYPES.TSUnionType) return firstParam;
347
613
  }
348
614
  }
349
- const noMutableUnionArrayParam = createRule$1({
615
+ const noMutableUnionArrayParam = createRule$4({
350
616
  name: "no-mutable-union-array-param",
351
617
  meta: {
352
618
  type: "problem",
@@ -472,14 +738,14 @@ const noNonBarrelReexport = {
472
738
  };
473
739
  //#endregion
474
740
  //#region src/rules/no-object-assign.ts
475
- const createRule = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
741
+ const createRule$3 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
476
742
  function resolveFrom$1(scope, name) {
477
743
  for (let current = scope; current; current = current.upper) {
478
744
  const found = current.set.get(name);
479
745
  if (found) return found;
480
746
  }
481
747
  }
482
- const noObjectAssign = createRule({
748
+ const noObjectAssign = createRule$3({
483
749
  name: "no-object-assign",
484
750
  meta: {
485
751
  type: "problem",
@@ -554,6 +820,293 @@ function resolveFrom(scope, name) {
554
820
  if (found) return found;
555
821
  }
556
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$2 = _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$2({
897
+ name: "no-set-instanceof-mutation",
898
+ meta: {
899
+ type: "problem",
900
+ schema: [],
901
+ docs: { description: "Disallow mutating calls on a parameter 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 narrowed by instanceof Set -- instanceof Set's own narrowing widens straight to the mutable Set interface, so a caller's genuinely read-only set can be mutated here even though the parameter's real type includes a ReadonlySet. 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 parameterHasReadonlySetConstituent(parameterNode) {
909
+ const tsNode = services.esTreeNodeToTSNodeMap.get(parameterNode);
910
+ const parameterType = checker.getTypeAtLocation(tsNode);
911
+ return (parameterType.isUnion() ? parameterType.types : [parameterType]).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 parameterDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter);
919
+ if (!parameterDefinition) return;
920
+ const parameterNode = parameterDefinition.name;
921
+ if (parameterNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
922
+ if (!parameterHasReadonlySetConstituent(parameterNode)) 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$1 = _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$1({
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 = _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(param) {
1064
+ if (param.type === _typescript_eslint_utils.AST_NODE_TYPES.TSParameterProperty) return getAnnotatedParamNode(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 = [
1069
+ "ArrowFunctionExpression",
1070
+ "FunctionDeclaration",
1071
+ "FunctionExpression",
1072
+ "TSCallSignatureDeclaration",
1073
+ "TSConstructSignatureDeclaration",
1074
+ "TSDeclareFunction",
1075
+ "TSEmptyBodyFunctionExpression",
1076
+ "TSFunctionType",
1077
+ "TSMethodSignature"
1078
+ ].join(", ");
1079
+ const preferReadonlyArrayParam = createRule({
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(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](node) {
1106
+ for (const param of node.params) checkParam(param);
1107
+ } };
1108
+ }
1109
+ });
557
1110
  //#endregion
558
1111
  //#region src/plugin.ts
559
1112
  const plugin = {
@@ -565,83 +1118,20 @@ const plugin = {
565
1118
  rules: {
566
1119
  "barrel-direct-siblings-only": barrelDirectSiblingsOnly,
567
1120
  "barrel-policy": barrelPolicy,
1121
+ "no-array-isarray-mutation": noArrayIsArrayMutation,
568
1122
  "no-enum-number-widening": noEnumNumberWidening,
1123
+ "no-enum-reverse-lookup-widening": noEnumReverseLookupWidening,
569
1124
  "no-index-files": noIndexFiles,
1125
+ "no-map-instanceof-mutation": noMapInstanceofMutation,
570
1126
  "no-mutable-union-array-param": noMutableUnionArrayParam,
571
1127
  "no-non-barrel-index": noNonBarrelIndex,
572
1128
  "no-non-barrel-reexport": noNonBarrelReexport,
573
1129
  "no-object-assign": noObjectAssign,
574
- "no-pointless-reassignment": {
575
- meta: {
576
- type: "problem",
577
- fixable: "code",
578
- schema: [],
579
- messages: { pointlessReassignment: "Pointless reassignment: '{{ name }}' is just an alias for '{{ value }}'. Use the original directly." }
580
- },
581
- create(context) {
582
- return { VariableDeclarator(node) {
583
- if (node.id.type !== "Identifier" || node.init?.type !== "Identifier" || node.id.name.startsWith("_")) return;
584
- if (node.parent.type !== "VariableDeclaration" || node.parent.kind !== "const") return;
585
- const scope = context.sourceCode.getScope(node);
586
- const sourceVariable = scope.references.find((reference) => reference.identifier === node.init)?.resolved;
587
- if (!sourceVariable || sourceVariable.references.some((reference) => reference.isWrite() && !reference.init)) return;
588
- const aliasName = node.id.name;
589
- const originalName = node.init.name;
590
- const aliasIsAnnotated = hasTypeAnnotation(node.id);
591
- context.report({
592
- node,
593
- messageId: "pointlessReassignment",
594
- data: {
595
- name: aliasName,
596
- value: originalName
597
- },
598
- fix(fixer) {
599
- const variable = scope.set.get(aliasName);
600
- if (!variable) return null;
601
- if (aliasIsAnnotated) return null;
602
- if (variable.references.filter((reference) => reference.isWrite() && reference.identifier !== node.id).length > 0) return null;
603
- const readRefs = variable.references.filter((reference) => reference.isRead() && isIdentifierReference(reference));
604
- if (readRefs.some((reference) => {
605
- const afterToken = context.sourceCode.getTokenAfter(reference.identifier);
606
- if (afterToken?.value === ":") return false;
607
- if (afterToken?.value !== "}" && afterToken?.value !== ",") return false;
608
- let token = context.sourceCode.getTokenBefore(reference.identifier);
609
- while (token) {
610
- if (token.value === "{") return true;
611
- if (token.value === "[" || token.value === "(") return false;
612
- if (token.value === ":") return false;
613
- token = context.sourceCode.getTokenBefore(token);
614
- }
615
- return false;
616
- })) return null;
617
- if (readRefs.some((reference) => resolveFrom(reference.from, originalName) !== sourceVariable)) return null;
618
- const fixes = readRefs.map((reference) => fixer.replaceText(reference.identifier, originalName));
619
- const declaration = node.parent;
620
- if (declaration.type !== "VariableDeclaration" || declaration.declarations.length !== 1) return null;
621
- fixes.push(fixer.remove(declaration.parent.type === "ExportNamedDeclaration" ? declaration.parent : declaration));
622
- return fixes;
623
- }
624
- });
625
- } };
626
- }
627
- },
628
- "no-side-effects-in-index": {
629
- meta: {
630
- type: "problem",
631
- schema: [],
632
- 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 }}." }
633
- },
634
- create(context) {
635
- if (!isIndexFile(context.filename)) return {};
636
- return { Program(node) {
637
- for (const statement of node.body) if (!isPureReexport(statement)) context.report({
638
- node: statement,
639
- messageId: "notAPureReexport",
640
- data: { description: statement.type }
641
- });
642
- } };
643
- }
644
- }
1130
+ "no-pointless-reassignment": noPointlessReassignment,
1131
+ "no-set-instanceof-mutation": noSetInstanceofMutation,
1132
+ "no-side-effects-in-index": noSideEffectsInIndex,
1133
+ "prefer-numeric-sort-compare": preferNumericSortCompare,
1134
+ "prefer-readonly-array-param": preferReadonlyArrayParam
645
1135
  },
646
1136
  configs: {
647
1137
  get recommended() {
@@ -652,7 +1142,8 @@ const plugin = {
652
1142
  "exadev/barrel-policy": ["error", { mode: "banned" }],
653
1143
  "exadev/no-mutable-union-array-param": "error",
654
1144
  "exadev/no-object-assign": "error",
655
- "exadev/no-pointless-reassignment": "error"
1145
+ "exadev/no-pointless-reassignment": "error",
1146
+ "exadev/prefer-readonly-array-param": "error"
656
1147
  }
657
1148
  };
658
1149
  },
@@ -675,14 +1166,44 @@ const recommendedTypeChecked = [
675
1166
  linterOptions: { noInlineConfig: true },
676
1167
  rules: {
677
1168
  "exadev/barrel-policy": ["error", { mode: "banned" }],
1169
+ "exadev/no-array-isarray-mutation": "error",
678
1170
  "exadev/no-enum-number-widening": "error",
1171
+ "exadev/no-enum-reverse-lookup-widening": "error",
1172
+ "exadev/no-map-instanceof-mutation": "error",
679
1173
  "exadev/no-mutable-union-array-param": "error",
680
1174
  "exadev/no-object-assign": "error",
681
1175
  "exadev/no-pointless-reassignment": "error",
682
- "@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "never" }],
1176
+ "exadev/no-set-instanceof-mutation": "error",
1177
+ "exadev/prefer-numeric-sort-compare": "error",
1178
+ "exadev/prefer-readonly-array-param": "error",
683
1179
  "@typescript-eslint/ban-ts-comment": ["error", { "ts-expect-error": true }],
1180
+ "@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "never" }],
1181
+ "@typescript-eslint/consistent-type-exports": "error",
1182
+ "@typescript-eslint/consistent-type-imports": "error",
684
1183
  "@typescript-eslint/method-signature-style": ["error", "property"],
685
- "@typescript-eslint/no-non-null-assertion": "error"
1184
+ "@typescript-eslint/no-deprecated": "error",
1185
+ "@typescript-eslint/no-magic-numbers": ["error", {
1186
+ ignore: [
1187
+ -1,
1188
+ 0,
1189
+ 1,
1190
+ 2
1191
+ ],
1192
+ ignoreArrayIndexes: true,
1193
+ ignoreEnums: true,
1194
+ ignoreReadonlyClassProperties: true,
1195
+ ignoreDefaultValues: true
1196
+ }],
1197
+ "@typescript-eslint/no-misused-spread": "error",
1198
+ "@typescript-eslint/no-mixed-enums": "error",
1199
+ "@typescript-eslint/no-non-null-assertion": "error",
1200
+ "@typescript-eslint/no-unnecessary-condition": "error",
1201
+ "@typescript-eslint/prefer-readonly": "error",
1202
+ "@typescript-eslint/promise-function-async": "error",
1203
+ "@typescript-eslint/require-array-sort-compare": "error",
1204
+ "@typescript-eslint/strict-boolean-expressions": "error",
1205
+ "@typescript-eslint/switch-exhaustiveness-check": "error",
1206
+ "@typescript-eslint/use-unknown-in-catch-callback-variable": "error"
686
1207
  }
687
1208
  },
688
1209
  {