exadev-eslint-config 2.17.0 → 2.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.cjs +196 -213
  2. package/dist/index.js +196 -213
  3. package/package.json +8 -2
package/dist/index.cjs CHANGED
@@ -263,9 +263,6 @@ function tryRequire(specifier, requireFn = nodeRequire) {
263
263
  function isRecord$1(value) {
264
264
  return typeof value === "object" && value !== null;
265
265
  }
266
- function isFlatConfig$1(value) {
267
- return isRecord$1(value);
268
- }
269
266
  function normalizeLegacyParserOptions(record) {
270
267
  if (!("parserOptions" in record)) return record;
271
268
  const { parserOptions, languageOptions, ...rest } = record;
@@ -285,8 +282,7 @@ function readFlatConfig(module, path) {
285
282
  current = current[key];
286
283
  }
287
284
  if (!isRecord$1(current)) return void 0;
288
- const normalized = normalizeLegacyParserOptions(current);
289
- return isFlatConfig$1(normalized) ? normalized : void 0;
285
+ return normalizeLegacyParserOptions(current);
290
286
  }
291
287
  //#endregion
292
288
  //#region src/nextjs.ts
@@ -299,7 +295,7 @@ function buildNextjsConfig(options = {}) {
299
295
  }
300
296
  //#endregion
301
297
  //#region package.json
302
- var version = "2.17.0";
298
+ var version = "2.17.2";
303
299
  //#endregion
304
300
  //#region src/react.ts
305
301
  const JSX_FILE_PATTERNS = ["**/*.jsx", "**/*.tsx"];
@@ -341,12 +337,15 @@ function buildReactConfig(options = {}) {
341
337
  //#region src/rules/barrel-helpers.ts
342
338
  const INDEX_BASENAME$1 = /^index\.[cm]?[tj]sx?$/;
343
339
  function basenameOf(filename) {
344
- const slash = filename.lastIndexOf("/");
345
- return slash === -1 ? filename : filename.slice(slash + 1);
340
+ return filename.slice(filename.lastIndexOf("/") + 1);
346
341
  }
347
342
  function isIndexFile(filename) {
348
343
  return INDEX_BASENAME$1.test(basenameOf(filename));
349
344
  }
345
+ function moduleSpecifierValue(literal) {
346
+ if (typeof literal.value !== "string") throw new Error(`Unreachable: a module specifier's own grammar only ever produces a string literal, got ${typeof literal.value} instead.`);
347
+ return literal.value;
348
+ }
350
349
  function isMainBarrel(filename) {
351
350
  return filename.endsWith("/src/index.ts");
352
351
  }
@@ -381,6 +380,9 @@ function isInsideAmbientModuleDeclaration(node) {
381
380
  }
382
381
  return false;
383
382
  }
383
+ function hasSource(node) {
384
+ return node.source !== null && node.source !== void 0;
385
+ }
384
386
  function createSplitReexportDetector() {
385
387
  const importsByName = /* @__PURE__ */ new Map();
386
388
  const bareExportSpecifiers = [];
@@ -418,14 +420,15 @@ function createSplitReexportDetector() {
418
420
  });
419
421
  }
420
422
  for (const declarationNode of defaultExportDeclarations) {
421
- const name = declarationNode.declaration.type === "Identifier" ? declarationNode.declaration.name : void 0;
422
- if (name === void 0) continue;
423
- const trackedImport = importsByName.get(name);
423
+ if (declarationNode.declaration.type !== "Identifier") continue;
424
+ const identifierNode = declarationNode.declaration;
425
+ const trackedImport = importsByName.get(identifierNode.name);
424
426
  if (trackedImport === void 0) continue;
425
427
  out.push({
426
428
  kind: "default",
427
429
  declaration: declarationNode,
428
- name,
430
+ identifierNode,
431
+ name: identifierNode.name,
429
432
  trackedImport
430
433
  });
431
434
  }
@@ -439,15 +442,14 @@ const barrelDirectSiblingsOnly = {
439
442
  meta: {
440
443
  type: "problem",
441
444
  schema: [],
442
- messages: { notADirectSibling: "A barrel may re-export only from a direct sibling file or folder ('./module' or './module.ts') -- found '{{ source }}'. Move the source closer, or import it directly at the call site rather than re-exporting it through this barrel." }
445
+ messages: { notADirectSibling: "A barrel may re-export only from a direct sibling file or folder ('./module' or './module.ts') found '{{ source }}'. Move the source closer, or import it directly at the call site rather than re-exporting it through this barrel." }
443
446
  },
444
447
  create(context) {
445
448
  if (!isIndexFile(context.filename)) return {};
446
449
  return {
447
450
  ExportNamedDeclaration(node) {
448
451
  if (node.source === null || node.source === void 0) return;
449
- const source = node.source.value;
450
- if (typeof source !== "string") return;
452
+ const source = moduleSpecifierValue(node.source);
451
453
  if (!isDirectSibling(source)) context.report({
452
454
  node,
453
455
  messageId: "notADirectSibling",
@@ -455,8 +457,7 @@ const barrelDirectSiblingsOnly = {
455
457
  });
456
458
  },
457
459
  ExportAllDeclaration(node) {
458
- const source = node.source.value;
459
- if (typeof source !== "string") return;
460
+ const source = moduleSpecifierValue(node.source);
460
461
  if (!isDirectSibling(source)) context.report({
461
462
  node,
462
463
  messageId: "notADirectSibling",
@@ -469,7 +470,7 @@ const barrelDirectSiblingsOnly = {
469
470
  //#endregion
470
471
  //#region src/rules/barrel-policy.ts
471
472
  function readMode(options) {
472
- if (options === void 0 || typeof options !== "object" || options === null || !("mode" in options) || !isBarrelMode(options.mode)) throw new Error("exadev/barrel-policy requires options: { mode: 'banned' | 'single' | 'siblings' }.");
473
+ if (typeof options !== "object" || options === null || !("mode" in options) || !isBarrelMode(options.mode)) throw new Error("exadev/barrel-policy requires options: { mode: 'banned' | 'single' | 'siblings' }.");
473
474
  return options.mode;
474
475
  }
475
476
  const barrelPolicy = {
@@ -489,20 +490,17 @@ const barrelPolicy = {
489
490
  additionalProperties: false
490
491
  }],
491
492
  messages: {
492
- indexFileBanned: "Index (barrel) files are banned in this project -- import directly from the module that owns the export instead. Rename this file to something descriptive.",
493
- nonMainIndexFile: "Only src/index.ts may be a barrel in this project -- this index file is not it. Move its contents into the module that owns them or give the file a descriptive name.",
494
- sideEffectInBarrel: "A barrel 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 }}.",
495
- reexportOutsideBarrel: "Re-exports belong only in a barrel (index) file -- import this value directly in the file that uses it instead of re-exporting it through this one.",
496
- notADirectSibling: "A barrel may re-export only from a direct sibling file or folder ('./module' or './module.ts') -- found '{{ source }}'. Move the source closer, or import it directly at the call site rather than re-exporting it through this barrel."
493
+ indexFileBanned: "Index (barrel) files are banned in this project import directly from the module that owns the export instead. Rename this file to something descriptive.",
494
+ nonMainIndexFile: "Only src/index.ts may be a barrel in this project this index file is not it. Move its contents into the module that owns them or give the file a descriptive name.",
495
+ sideEffectInBarrel: "A barrel 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 }}.",
496
+ reexportOutsideBarrel: "Re-exports belong only in a barrel (index) file import this value directly in the file that uses it instead of re-exporting it through this one.",
497
+ notADirectSibling: "A barrel may re-export only from a direct sibling file or folder ('./module' or './module.ts') found '{{ source }}'. Move the source closer, or import it directly at the call site rather than re-exporting it through this barrel."
497
498
  }
498
499
  },
499
500
  create(context) {
500
501
  const mode = readMode(context.options[0]);
501
502
  const filename = context.filename;
502
503
  const detector = createSplitReexportDetector();
503
- function hasSource(node) {
504
- return node.source !== null && node.source !== void 0;
505
- }
506
504
  return {
507
505
  Program(node) {
508
506
  if (mode === "banned") {
@@ -543,12 +541,12 @@ const barrelPolicy = {
543
541
  ExportNamedDeclaration(node) {
544
542
  detector.visitExportNamed(node);
545
543
  if (hasSource(node) && !isInsideAmbientModuleDeclaration(node)) {
546
- const source = node.source === null || node.source === void 0 ? void 0 : node.source.value;
544
+ const source = moduleSpecifierValue(node.source);
547
545
  if (!isPermittedBarrel(filename, mode)) context.report({
548
546
  node,
549
547
  messageId: "reexportOutsideBarrel"
550
548
  });
551
- else if (mode === "siblings" && typeof source === "string" && !isDirectSibling(source)) context.report({
549
+ else if (mode === "siblings" && !isDirectSibling(source)) context.report({
552
550
  node,
553
551
  messageId: "notADirectSibling",
554
552
  data: { source }
@@ -557,12 +555,12 @@ const barrelPolicy = {
557
555
  },
558
556
  ExportAllDeclaration(node) {
559
557
  if (isInsideAmbientModuleDeclaration(node)) return;
560
- const source = node.source.value;
558
+ const source = moduleSpecifierValue(node.source);
561
559
  if (!isPermittedBarrel(filename, mode)) context.report({
562
560
  node,
563
561
  messageId: "reexportOutsideBarrel"
564
562
  });
565
- else if (mode === "siblings" && typeof source === "string" && !isDirectSibling(source)) context.report({
563
+ else if (mode === "siblings" && !isDirectSibling(source)) context.report({
566
564
  node,
567
565
  messageId: "notADirectSibling",
568
566
  data: { source }
@@ -574,8 +572,8 @@ const barrelPolicy = {
574
572
  "Program:exit"() {
575
573
  for (const violation of detector.violations()) if (isPermittedBarrel(filename, mode)) {
576
574
  if (mode === "siblings") {
577
- const importSource = violation.trackedImport.declaration.source.value;
578
- if (typeof importSource === "string" && !isDirectSibling(importSource)) context.report({
575
+ const importSource = moduleSpecifierValue(violation.trackedImport.declaration.source);
576
+ if (!isDirectSibling(importSource)) context.report({
579
577
  node: violation.kind === "named" ? violation.specifier : violation.declaration,
580
578
  messageId: "notADirectSibling",
581
579
  data: { source: importSource }
@@ -590,6 +588,12 @@ const barrelPolicy = {
590
588
  }
591
589
  };
592
590
  //#endregion
591
+ //#region src/rules/scope-guards.ts
592
+ function asIdentifierName(name) {
593
+ if (name.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) throw new Error(`Unreachable: expected a Parameter/Variable Definition's own name to be an Identifier, got ${name.type} instead.`);
594
+ return name;
595
+ }
596
+ //#endregion
593
597
  //#region src/rules/no-array-isarray-mutation.ts
594
598
  const MUTATING_INSERT_METHODS$1 = /* @__PURE__ */ new Set([
595
599
  "push",
@@ -616,7 +620,7 @@ const noArrayIsArrayMutation = createRule$7({
616
620
  type: "problem",
617
621
  schema: [],
618
622
  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." },
619
- 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." }
623
+ 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." }
620
624
  },
621
625
  defaultOptions: [],
622
626
  create(context) {
@@ -629,14 +633,12 @@ const noArrayIsArrayMutation = createRule$7({
629
633
  }
630
634
  return { CallExpression(node) {
631
635
  const { callee } = node;
632
- 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;
636
+ if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !MUTATING_INSERT_METHODS$1.has(callee.property.name)) return;
633
637
  const variable = context.sourceCode.getScope(node).references.find((reference) => reference.identifier === callee.object)?.resolved;
634
638
  if (!variable) return;
635
639
  const declarationDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter || definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Variable);
636
640
  if (!declarationDefinition) return;
637
- const declarationNode = declarationDefinition.name;
638
- if (declarationNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
639
- if (!declarationHasReadonlyArrayConstituent(declarationNode)) return;
641
+ if (!declarationHasReadonlyArrayConstituent(asIdentifierName(declarationDefinition.name))) return;
640
642
  if (!isGuardedByArrayIsArray(node, variable, context)) return;
641
643
  context.report({
642
644
  node,
@@ -664,7 +666,7 @@ const noArrayIsArrayMutation = createRule$7({
664
666
  if (parent.consequent === current && matchesArrayIsArrayOn(parent.test, parameterVariable, ruleContext)) return true;
665
667
  if (parent.alternate === current && isNegatedArrayIsArrayCall(parent.test, parameterVariable, ruleContext)) return true;
666
668
  }
667
- if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression && parent.operator === "&&" && parent.right === current && matchesArrayIsArrayOn(parent.left, parameterVariable, ruleContext)) return true;
669
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression && parent.operator === "&&" && matchesArrayIsArrayOn(parent.left, parameterVariable, ruleContext)) return true;
668
670
  if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ConditionalExpression && parent.consequent === current && matchesArrayIsArrayOn(parent.test, parameterVariable, ruleContext)) return true;
669
671
  current = parent;
670
672
  }
@@ -676,15 +678,7 @@ const noArrayIsArrayMutation = createRule$7({
676
678
  const { parent } = current;
677
679
  if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement || parent.type === _typescript_eslint_utils.AST_NODE_TYPES.Program) {
678
680
  const statements = parent.body;
679
- let ownIndex = -1;
680
- for (let i = 0; i < statements.length; i++) if (statements[i] === current) {
681
- ownIndex = i;
682
- break;
683
- }
684
- if (ownIndex > 0) for (let i = ownIndex - 1; i >= 0; i--) {
685
- const sibling = statements[i];
686
- if (sibling?.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedArrayIsArrayCall(sibling.test, parameterVariable, ruleContext) && definitelyExits$2(sibling.consequent)) return true;
687
- }
681
+ if (statements.slice(0, statements.indexOf(current)).some((sibling) => sibling.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedArrayIsArrayCall(sibling.test, parameterVariable, ruleContext) && definitelyExits$2(sibling.consequent))) return true;
688
682
  }
689
683
  current = parent;
690
684
  }
@@ -763,22 +757,36 @@ const noControlFlow = {
763
757
  }
764
758
  };
765
759
  //#endregion
760
+ //#region src/rules/ts-node-guards.ts
761
+ function asExpression(tsNode) {
762
+ if (!typescript.isExpression(tsNode)) throw new Error(`Unreachable: expected a ts.Expression, got ts.SyntaxKind.${typescript.SyntaxKind[tsNode.kind]} instead.`);
763
+ return tsNode;
764
+ }
765
+ function asTypeReference(type) {
766
+ if (!(0, ts_api_utils.isTypeReference)(type)) throw new Error("Unreachable: expected an array/tuple type to be backed by a ts.TypeReference.");
767
+ return type;
768
+ }
769
+ function lastTokenOrThrow(sourceCode, node) {
770
+ const token = sourceCode.getLastToken(node);
771
+ if (token === null) throw new Error("Unreachable: getLastToken returned null for a node expected to always have at least one token.");
772
+ return token;
773
+ }
774
+ //#endregion
766
775
  //#region src/rules/no-enum-number-widening.ts
767
776
  const noEnumNumberWidening = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`)({
768
777
  name: "no-enum-number-widening",
769
778
  meta: {
770
779
  type: "problem",
771
- docs: { description: "Disallow assigning a bare (non-literal) number where a numeric enum type is expected -- TypeScript accepts any number for a numeric enum slot, not just its own members, once the value is not a literal the compiler can range-check." },
780
+ docs: { description: "Disallow assigning a bare (non-literal) number where a numeric enum type is expected TypeScript accepts any number for a numeric enum slot, not just its own members, once the value is not a literal the compiler can range-check." },
772
781
  schema: [],
773
- messages: { widening: "A plain 'number' value is being used where the numeric enum '{{ enumName }}' is expected. TypeScript does not verify the value is actually one of the enum's members here -- narrow it to a known member first (e.g. a lookup/guard against the enum's own values), or accept a plain 'number' parameter instead of widening it implicitly." }
782
+ messages: { widening: "A plain 'number' value is being used where the numeric enum '{{ enumName }}' is expected. TypeScript does not verify the value is actually one of the enum's members here narrow it to a known member first (e.g. a lookup/guard against the enum's own values), or accept a plain 'number' parameter instead of widening it implicitly." },
783
+ defaultOptions: []
774
784
  },
775
- defaultOptions: [],
776
785
  create(context) {
777
786
  const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
778
787
  const checker = services.program.getTypeChecker();
779
788
  function checkNode(expression) {
780
- const tsNode = services.esTreeNodeToTSNodeMap.get(expression);
781
- if (!typescript.isExpression(tsNode)) return;
789
+ const tsNode = asExpression(services.esTreeNodeToTSNodeMap.get(expression));
782
790
  const contextualType = checker.getContextualType(tsNode);
783
791
  if (!contextualType || !(contextualType.flags & typescript.TypeFlags.EnumLike)) return;
784
792
  const actualType = checker.getTypeAtLocation(tsNode);
@@ -814,27 +822,24 @@ const noEnumReverseLookupWidening = _typescript_eslint_utils.ESLintUtils.RuleCre
814
822
  meta: {
815
823
  type: "problem",
816
824
  hasSuggestions: true,
817
- 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." },
825
+ 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." },
818
826
  schema: [],
819
827
  messages: {
820
- 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.",
828
+ 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.",
821
829
  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."
822
- }
830
+ },
831
+ defaultOptions: []
823
832
  },
824
- defaultOptions: [],
825
833
  create(context) {
826
834
  const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
827
835
  const checker = services.program.getTypeChecker();
828
836
  return { MemberExpression(node) {
829
- if (!node.computed) return;
830
- const objectTsNode = services.esTreeNodeToTSNodeMap.get(node.object);
831
- if (!typescript.isExpression(objectTsNode)) return;
837
+ const objectTsNode = asExpression(services.esTreeNodeToTSNodeMap.get(node.object));
832
838
  const objectType = checker.getTypeAtLocation(objectTsNode);
833
839
  const objectSymbol = objectType.getSymbol();
834
840
  if (!objectSymbol || !(objectSymbol.flags & typescript.SymbolFlags.Enum)) return;
835
841
  if (!checker.getIndexInfoOfType(objectType, typescript.IndexKind.Number)) return;
836
- const propertyTsNode = services.esTreeNodeToTSNodeMap.get(node.property);
837
- if (!typescript.isExpression(propertyTsNode)) return;
842
+ const propertyTsNode = asExpression(services.esTreeNodeToTSNodeMap.get(node.property));
838
843
  const rawPropertyType = checker.getTypeAtLocation(propertyTsNode);
839
844
  const propertyType = checker.getBaseConstraintOfType(rawPropertyType) ?? rawPropertyType;
840
845
  if (propertyType.flags & typescript.TypeFlags.EnumLike) {
@@ -845,7 +850,7 @@ const noEnumReverseLookupWidening = _typescript_eslint_utils.ESLintUtils.RuleCre
845
850
  }
846
851
  const enumName = checker.typeToString(checker.getDeclaredTypeOfSymbol(objectSymbol));
847
852
  const parent = node.parent;
848
- 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) {
853
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.VariableDeclarator && parent.id.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && parent.id.typeAnnotation?.typeAnnotation.type === _typescript_eslint_utils.AST_NODE_TYPES.TSStringKeyword) {
849
854
  const stringKeyword = parent.id.typeAnnotation.typeAnnotation;
850
855
  context.report({
851
856
  node,
@@ -874,7 +879,7 @@ const noIndexFiles = {
874
879
  meta: {
875
880
  type: "problem",
876
881
  schema: [],
877
- messages: { indexFileBanned: "Index (barrel) files are banned -- import directly from the module that owns the export instead. Rename this file to something descriptive." }
882
+ messages: { indexFileBanned: "Index (barrel) files are banned import directly from the module that owns the export instead. Rename this file to something descriptive." }
878
883
  },
879
884
  create(context) {
880
885
  if (!isIndexFile(context.filename)) return {};
@@ -911,7 +916,7 @@ const noMapInstanceofMutation = createRule$6({
911
916
  type: "problem",
912
917
  schema: [],
913
918
  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." },
914
- 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'." }
919
+ 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'." }
915
920
  },
916
921
  defaultOptions: [],
917
922
  create(context) {
@@ -924,14 +929,12 @@ const noMapInstanceofMutation = createRule$6({
924
929
  }
925
930
  return { CallExpression(node) {
926
931
  const { callee } = node;
927
- 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;
932
+ if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !MUTATING_MAP_METHODS.has(callee.property.name)) return;
928
933
  const variable = context.sourceCode.getScope(node).references.find((reference) => reference.identifier === callee.object)?.resolved;
929
934
  if (!variable) return;
930
935
  const declarationDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter || definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Variable);
931
936
  if (!declarationDefinition) return;
932
- const declarationNode = declarationDefinition.name;
933
- if (declarationNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
934
- if (!declarationHasReadonlyMapConstituent(declarationNode)) return;
937
+ if (!declarationHasReadonlyMapConstituent(asIdentifierName(declarationDefinition.name))) return;
935
938
  if (!isGuardedByInstanceofMap(node, variable, context)) return;
936
939
  context.report({
937
940
  node,
@@ -959,7 +962,7 @@ const noMapInstanceofMutation = createRule$6({
959
962
  if (parent.consequent === current && matchesInstanceofMapOn(parent.test, parameterVariable, ruleContext)) return true;
960
963
  if (parent.alternate === current && isNegatedInstanceofMapExpression(parent.test, parameterVariable, ruleContext)) return true;
961
964
  }
962
- if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression && parent.operator === "&&" && parent.right === current && matchesInstanceofMapOn(parent.left, parameterVariable, ruleContext)) return true;
965
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression && parent.operator === "&&" && matchesInstanceofMapOn(parent.left, parameterVariable, ruleContext)) return true;
963
966
  if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ConditionalExpression && parent.consequent === current && matchesInstanceofMapOn(parent.test, parameterVariable, ruleContext)) return true;
964
967
  current = parent;
965
968
  }
@@ -971,15 +974,7 @@ const noMapInstanceofMutation = createRule$6({
971
974
  const { parent } = current;
972
975
  if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement || parent.type === _typescript_eslint_utils.AST_NODE_TYPES.Program) {
973
976
  const statements = parent.body;
974
- let ownIndex = -1;
975
- for (let i = 0; i < statements.length; i++) if (statements[i] === current) {
976
- ownIndex = i;
977
- break;
978
- }
979
- if (ownIndex > 0) for (let i = ownIndex - 1; i >= 0; i--) {
980
- const sibling = statements[i];
981
- if (sibling?.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedInstanceofMapExpression(sibling.test, parameterVariable, ruleContext) && definitelyExits$1(sibling.consequent)) return true;
982
- }
977
+ if (statements.slice(0, statements.indexOf(current)).some((sibling) => sibling.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedInstanceofMapExpression(sibling.test, parameterVariable, ruleContext) && definitelyExits$1(sibling.consequent))) return true;
983
978
  }
984
979
  current = parent;
985
980
  }
@@ -997,6 +992,11 @@ const MUTATING_INSERT_METHODS = /* @__PURE__ */ new Set([
997
992
  "copyWithin"
998
993
  ]);
999
994
  const createRule$5 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
995
+ function buildReadonlyArrayFix(annotated, fixer) {
996
+ if (annotated.type === _typescript_eslint_utils.AST_NODE_TYPES.TSArrayType) return fixer.insertTextBefore(annotated, "readonly ");
997
+ if (annotated.type !== _typescript_eslint_utils.AST_NODE_TYPES.TSTypeReference) throw new Error(`Unreachable: expected a TSArrayType or TSTypeReference, got ${annotated.type} instead.`);
998
+ return fixer.replaceText(annotated.typeName, "ReadonlyArray");
999
+ }
1000
1000
  function isUnionArrayType(typeAnnotation) {
1001
1001
  if (typeAnnotation.type === _typescript_eslint_utils.AST_NODE_TYPES.TSArrayType && typeAnnotation.elementType.type === _typescript_eslint_utils.AST_NODE_TYPES.TSUnionType) return typeAnnotation.elementType;
1002
1002
  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) {
@@ -1011,30 +1011,24 @@ const noMutableUnionArrayParam = createRule$5({
1011
1011
  fixable: "code",
1012
1012
  docs: { description: "Disallow mutating-insertion calls on a union-element array parameter, which lets a caller pass a narrower array whose declared element type the call can silently violate." },
1013
1013
  schema: [],
1014
- messages: { unsound: "'{{ method }}' inserts into a parameter typed as an array of a union -- a caller may have passed a narrower array (e.g. number[] where (string | number)[] is declared), and TypeScript's covariant array typing does not catch the resulting mismatch. Mark the parameter readonly to turn this into a real compile error, or narrow the parameter type." }
1014
+ messages: { unsound: "'{{ method }}' inserts into a parameter typed as an array of a union a caller may have passed a narrower array (e.g. number[] where (string | number)[] is declared), and TypeScript's covariant array typing does not catch the resulting mismatch. Mark the parameter readonly to turn this into a real compile error, or narrow the parameter type." },
1015
+ defaultOptions: []
1015
1016
  },
1016
- defaultOptions: [],
1017
1017
  create(context) {
1018
1018
  return { CallExpression(node) {
1019
1019
  const { callee } = node;
1020
- 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.has(callee.property.name)) return;
1020
+ if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !MUTATING_INSERT_METHODS.has(callee.property.name)) return;
1021
1021
  const parameterDefinition = (context.sourceCode.getScope(node).references.find((reference) => reference.identifier === callee.object)?.resolved)?.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter);
1022
1022
  if (!parameterDefinition) return;
1023
- const parameterNode = parameterDefinition.name;
1024
- if (parameterNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !parameterNode.typeAnnotation) return;
1025
- if (!isUnionArrayType(parameterNode.typeAnnotation.typeAnnotation)) return;
1023
+ const parameterNode = asIdentifierName(parameterDefinition.name);
1024
+ if (!parameterNode.typeAnnotation) return;
1025
+ const annotated = parameterNode.typeAnnotation.typeAnnotation;
1026
+ if (!isUnionArrayType(annotated)) return;
1026
1027
  context.report({
1027
1028
  node,
1028
1029
  messageId: "unsound",
1029
1030
  data: { method: callee.property.name },
1030
- fix(fixer) {
1031
- const typeAnnotation = parameterNode.typeAnnotation;
1032
- if (!typeAnnotation) return null;
1033
- const annotated = typeAnnotation.typeAnnotation;
1034
- if (annotated.type === _typescript_eslint_utils.AST_NODE_TYPES.TSArrayType) return fixer.insertTextBefore(annotated, "readonly ");
1035
- if (annotated.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTypeReference && annotated.typeName.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return fixer.replaceText(annotated.typeName, "ReadonlyArray");
1036
- return null;
1037
- }
1031
+ fix: (fixer) => buildReadonlyArrayFix(annotated, fixer)
1038
1032
  });
1039
1033
  } };
1040
1034
  }
@@ -1050,8 +1044,7 @@ const noNonBarrelIndex = {
1050
1044
  },
1051
1045
  create(context) {
1052
1046
  const filename = context.filename;
1053
- const slash = filename.lastIndexOf("/");
1054
- const basename = slash === -1 ? filename : filename.slice(slash + 1);
1047
+ const basename = filename.slice(filename.lastIndexOf("/") + 1);
1055
1048
  if (!INDEX_BASENAME.test(basename)) return {};
1056
1049
  if (filename.endsWith("/src/index.ts")) return {};
1057
1050
  return { Program(node) {
@@ -1086,8 +1079,8 @@ const noNonBarrelReexport = {
1086
1079
  fixable: "code",
1087
1080
  schema: [],
1088
1081
  messages: {
1089
- splitStatementReexport: "'{{ name }}' is imported here and handed straight back out via a bare export -- the identical re-export 'export { {{ name }} } from ...' would be, just split across two statements. Re-exports belong only in the public barrel.",
1090
- splitStatementDefaultReexport: "'{{ name }}' is imported here and handed straight back out via `export default` -- the identical re-export 'export { {{ name }} as default } from ...' would be, just split across two statements. Re-exports belong only in the public barrel."
1082
+ splitStatementReexport: "'{{ name }}' is imported here and handed straight back out via a bare export the identical re-export 'export { {{ name }} } from ...' would be, just split across two statements. Re-exports belong only in the public barrel.",
1083
+ splitStatementDefaultReexport: "'{{ name }}' is imported here and handed straight back out via `export default` the identical re-export 'export { {{ name }} as default } from ...' would be, just split across two statements. Re-exports belong only in the public barrel."
1091
1084
  }
1092
1085
  },
1093
1086
  create(context) {
@@ -1118,14 +1111,14 @@ const noNonBarrelReexport = {
1118
1111
  }
1119
1112
  });
1120
1113
  } else {
1121
- const { declaration, name, trackedImport } = violation;
1114
+ const { declaration, identifierNode, name, trackedImport } = violation;
1122
1115
  context.report({
1123
1116
  node: declaration,
1124
1117
  messageId: "splitStatementDefaultReexport",
1125
1118
  data: { name },
1126
1119
  fix(fixer) {
1127
1120
  const fixes = [fixer.remove(declaration)];
1128
- if (declaration.declaration.type === "Identifier" && importIsOnlyUsedByThisExport(sourceCode, trackedImport, declaration.declaration)) fixes.push(removeListMember(fixer, sourceCode, trackedImport.declaration, trackedImport.declaration.specifiers, trackedImport.specifier));
1121
+ if (importIsOnlyUsedByThisExport(sourceCode, trackedImport, identifierNode)) fixes.push(removeListMember(fixer, sourceCode, trackedImport.declaration, trackedImport.declaration.specifiers, trackedImport.specifier));
1129
1122
  return fixes;
1130
1123
  }
1131
1124
  });
@@ -1149,14 +1142,14 @@ const noObjectAssign = createRule$4({
1149
1142
  type: "problem",
1150
1143
  fixable: "code",
1151
1144
  hasSuggestions: true,
1152
- docs: { description: "Disallow Object.assign, whose own type declarations do not check a source object's properties against the target's declared types -- object spread does." },
1145
+ docs: { description: "Disallow Object.assign, whose own type declarations do not check a source object's properties against the target's declared types object spread does." },
1153
1146
  schema: [],
1154
1147
  messages: {
1155
1148
  unsound: "Object.assign does not verify that a source object's properties are assignable to the target's declared types, so a type mismatch here passes silently where a direct property assignment would be rejected. Use object spread ({ ...target, ...source }) to build a correctly type-checked replacement instead.",
1156
- suggestSpreadReassign: "Replace with object spread and reassignment (changes the object reference -- anything else already holding this object will not see the update)."
1157
- }
1149
+ suggestSpreadReassign: "Replace with object spread and reassignment (changes the object reference anything else already holding this object will not see the update)."
1150
+ },
1151
+ defaultOptions: []
1158
1152
  },
1159
- defaultOptions: [],
1160
1153
  create(context) {
1161
1154
  return { CallExpression(node) {
1162
1155
  const { callee } = node;
@@ -1164,7 +1157,7 @@ const noObjectAssign = createRule$4({
1164
1157
  const [target, ...sources] = node.arguments;
1165
1158
  const sourcesAreSpreadable = sources.every((argument) => argument.type !== _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement);
1166
1159
  if (target?.type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression && sourcesAreSpreadable) {
1167
- const isBareStatement = node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ExpressionStatement && node.parent.expression === node;
1160
+ const isBareStatement = node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ExpressionStatement;
1168
1161
  context.report({
1169
1162
  node,
1170
1163
  messageId: "unsound",
@@ -1177,7 +1170,7 @@ const noObjectAssign = createRule$4({
1177
1170
  });
1178
1171
  return;
1179
1172
  }
1180
- if (target?.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && sourcesAreSpreadable && node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ExpressionStatement && node.parent.expression === node) {
1173
+ if (target?.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && sourcesAreSpreadable && node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ExpressionStatement) {
1181
1174
  const targetName = target.name;
1182
1175
  const variable = resolveFrom$1(context.sourceCode.getScope(node), targetName);
1183
1176
  const isConst = (variable?.defs.find((candidate) => candidate.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Variable))?.parent.kind === "const";
@@ -1210,7 +1203,16 @@ function isIdentifierReference(reference) {
1210
1203
  return reference.identifier.type === "Identifier";
1211
1204
  }
1212
1205
  function hasTypeAnnotation(id) {
1213
- return "typeAnnotation" in id && id.typeAnnotation !== void 0 && id.typeAnnotation !== null;
1206
+ return "typeAnnotation" in id && id.typeAnnotation !== void 0;
1207
+ }
1208
+ function isConstDeclarator(declarator) {
1209
+ if (declarator.parent.type !== "VariableDeclaration") throw new Error(`Unreachable: expected a VariableDeclarator's own parent to be a VariableDeclaration, got "${declarator.parent.type}" instead.`);
1210
+ return declarator.parent.kind === "const";
1211
+ }
1212
+ function variableInScope(scope, name) {
1213
+ const variable = scope.set.get(name);
1214
+ if (variable === void 0) throw new Error(`Unreachable: expected scope.set to hold a variable named "${name}".`);
1215
+ return variable;
1214
1216
  }
1215
1217
  function resolveFrom(scope, name) {
1216
1218
  for (let current = scope; current; current = current.upper) {
@@ -1218,6 +1220,18 @@ function resolveFrom(scope, name) {
1218
1220
  if (found) return found;
1219
1221
  }
1220
1222
  }
1223
+ function hasEnclosingShorthandBoundary(precedingTokens) {
1224
+ for (const token of [...precedingTokens].reverse()) {
1225
+ if (token.value === "{" || token.value === ",") return true;
1226
+ if (token.value === "[" || token.value === "(" || token.value === ":") return false;
1227
+ }
1228
+ return false;
1229
+ }
1230
+ function isShorthandPropertyRead(sourceCode, identifier) {
1231
+ const afterToken = sourceCode.getTokenAfter(identifier);
1232
+ if (afterToken?.value !== "}" && afterToken?.value !== ",") return false;
1233
+ return hasEnclosingShorthandBoundary(sourceCode.getTokensBefore(identifier));
1234
+ }
1221
1235
  const noPointlessReassignment = {
1222
1236
  meta: {
1223
1237
  type: "problem",
@@ -1228,7 +1242,7 @@ const noPointlessReassignment = {
1228
1242
  create(context) {
1229
1243
  return { VariableDeclarator(node) {
1230
1244
  if (node.id.type !== "Identifier" || node.init?.type !== "Identifier" || node.id.name.startsWith("_")) return;
1231
- if (node.parent.type !== "VariableDeclaration" || node.parent.kind !== "const") return;
1245
+ if (!isConstDeclarator(node)) return;
1232
1246
  const scope = context.sourceCode.getScope(node);
1233
1247
  const sourceVariable = scope.references.find((reference) => reference.identifier === node.init)?.resolved;
1234
1248
  if (!sourceVariable || sourceVariable.references.some((reference) => reference.isWrite() && reference.init !== true)) return;
@@ -1243,24 +1257,13 @@ const noPointlessReassignment = {
1243
1257
  value: originalName
1244
1258
  },
1245
1259
  fix(fixer) {
1246
- const variable = scope.set.get(aliasName);
1247
- if (!variable) return null;
1260
+ const variable = variableInScope(scope, aliasName);
1248
1261
  if (aliasIsAnnotated) return null;
1249
1262
  if (variable.references.filter((reference) => reference.isWrite() && reference.identifier !== node.id).length > 0) return null;
1250
- const readRefs = variable.references.filter((reference) => reference.isRead() && isIdentifierReference(reference));
1251
- if (readRefs.some((reference) => {
1252
- const afterToken = context.sourceCode.getTokenAfter(reference.identifier);
1253
- if (afterToken?.value === ":") return false;
1254
- if (afterToken?.value !== "}" && afterToken?.value !== ",") return false;
1255
- let token = context.sourceCode.getTokenBefore(reference.identifier);
1256
- while (token) {
1257
- if (token.value === "{") return true;
1258
- if (token.value === "[" || token.value === "(") return false;
1259
- if (token.value === ":") return false;
1260
- token = context.sourceCode.getTokenBefore(token);
1261
- }
1262
- return false;
1263
- })) return null;
1263
+ const readReferences = variable.references.filter((reference) => reference.isRead());
1264
+ const readRefs = readReferences.filter(isIdentifierReference);
1265
+ if (readRefs.length !== readReferences.length) return null;
1266
+ if (readRefs.some((reference) => isShorthandPropertyRead(context.sourceCode, reference.identifier))) return null;
1264
1267
  if (readRefs.some((reference) => resolveFrom(reference.from, originalName) !== sourceVariable)) return null;
1265
1268
  const fixes = readRefs.map((reference) => fixer.replaceText(reference.identifier, originalName));
1266
1269
  const declaration = node.parent;
@@ -1297,7 +1300,7 @@ const noSetInstanceofMutation = createRule$3({
1297
1300
  type: "problem",
1298
1301
  schema: [],
1299
1302
  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." },
1300
- 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." }
1303
+ 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." }
1301
1304
  },
1302
1305
  defaultOptions: [],
1303
1306
  create(context) {
@@ -1310,14 +1313,12 @@ const noSetInstanceofMutation = createRule$3({
1310
1313
  }
1311
1314
  return { CallExpression(node) {
1312
1315
  const { callee } = node;
1313
- 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;
1316
+ if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !MUTATING_SET_METHODS.has(callee.property.name)) return;
1314
1317
  const variable = context.sourceCode.getScope(node).references.find((reference) => reference.identifier === callee.object)?.resolved;
1315
1318
  if (!variable) return;
1316
1319
  const declarationDefinition = variable.defs.find((definition) => definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Parameter || definition.type === _typescript_eslint_utils.TSESLint.Scope.DefinitionType.Variable);
1317
1320
  if (!declarationDefinition) return;
1318
- const declarationNode = declarationDefinition.name;
1319
- if (declarationNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
1320
- if (!declarationHasReadonlySetConstituent(declarationNode)) return;
1321
+ if (!declarationHasReadonlySetConstituent(asIdentifierName(declarationDefinition.name))) return;
1321
1322
  if (!isGuardedBySetInstanceof(node, variable, context)) return;
1322
1323
  context.report({
1323
1324
  node,
@@ -1345,7 +1346,7 @@ const noSetInstanceofMutation = createRule$3({
1345
1346
  if (parent.consequent === current && matchesSetInstanceofOn(parent.test, parameterVariable, ruleContext)) return true;
1346
1347
  if (parent.alternate === current && isNegatedSetInstanceofExpression(parent.test, parameterVariable, ruleContext)) return true;
1347
1348
  }
1348
- if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression && parent.operator === "&&" && parent.right === current && matchesSetInstanceofOn(parent.left, parameterVariable, ruleContext)) return true;
1349
+ if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.LogicalExpression && parent.operator === "&&" && matchesSetInstanceofOn(parent.left, parameterVariable, ruleContext)) return true;
1349
1350
  if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ConditionalExpression && parent.consequent === current && matchesSetInstanceofOn(parent.test, parameterVariable, ruleContext)) return true;
1350
1351
  current = parent;
1351
1352
  }
@@ -1357,15 +1358,7 @@ const noSetInstanceofMutation = createRule$3({
1357
1358
  const { parent } = current;
1358
1359
  if (parent.type === _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement || parent.type === _typescript_eslint_utils.AST_NODE_TYPES.Program) {
1359
1360
  const statements = parent.body;
1360
- let ownIndex = -1;
1361
- for (let i = 0; i < statements.length; i++) if (statements[i] === current) {
1362
- ownIndex = i;
1363
- break;
1364
- }
1365
- if (ownIndex > 0) for (let i = ownIndex - 1; i >= 0; i--) {
1366
- const sibling = statements[i];
1367
- if (sibling?.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedSetInstanceofExpression(sibling.test, parameterVariable, ruleContext) && definitelyExits(sibling.consequent)) return true;
1368
- }
1361
+ if (statements.slice(0, statements.indexOf(current)).some((sibling) => sibling.type === _typescript_eslint_utils.AST_NODE_TYPES.IfStatement && !sibling.alternate && isNegatedSetInstanceofExpression(sibling.test, parameterVariable, ruleContext) && definitelyExits(sibling.consequent))) return true;
1369
1362
  }
1370
1363
  current = parent;
1371
1364
  }
@@ -1379,7 +1372,7 @@ const noSideEffectsInIndex = {
1379
1372
  meta: {
1380
1373
  type: "problem",
1381
1374
  schema: [],
1382
- 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 }}." }
1375
+ 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 }}." }
1383
1376
  },
1384
1377
  create(context) {
1385
1378
  if (!isIndexFile(context.filename)) return {};
@@ -1410,6 +1403,12 @@ const DEFAULT_SORT_AZ = [
1410
1403
  "resolutions",
1411
1404
  "scripts"
1412
1405
  ];
1406
+ /** Reads `array[index]`, throwing loudly instead of silently returning `undefined` when it is genuinely out of bounds. Every call site in this file passes an index that is provably in range by construction (a loop bound, a length check, or a mathematical invariant established immediately above the call) — this exists to satisfy `noUncheckedIndexedAccess` with a real runtime guarantee instead of a silent `as` cast or a `?? fallback` that could quietly paper over a genuine bug, and none of the arrays this file indexes (keys, nodes, ranges, characters) ever legitimately store `undefined` as an element. Exported so each call site's own "this can never actually throw" claim is checked directly against a deliberately out-of-bounds input, rather than trusted on the strength of a comment alone. */
1407
+ function at(array, index) {
1408
+ const value = array[index];
1409
+ if (value === void 0) throw new Error(`Unreachable: index ${String(index)} is out of bounds for an array of length ${String(array.length)}.`);
1410
+ return value;
1411
+ }
1413
1412
  function categorize(char) {
1414
1413
  if (char >= "0" && char <= "9") return 1;
1415
1414
  if (/[a-z]/iu.test(char)) return 2;
@@ -1419,14 +1418,15 @@ function categorize(char) {
1419
1418
  function compareSyncpackKey(a, b) {
1420
1419
  const length = Math.min(a.length, b.length);
1421
1420
  for (let index = 0; index < length; index += 1) {
1422
- const charA = a[index] ?? "";
1423
- const charB = b[index] ?? "";
1421
+ const charA = a.charAt(index);
1422
+ const charB = b.charAt(index);
1424
1423
  const categoryA = categorize(charA);
1425
1424
  const categoryB = categorize(charB);
1426
1425
  if (categoryA !== categoryB) return categoryA - categoryB;
1427
1426
  const foldedA = categoryA === 2 ? charA.toLowerCase() : charA;
1428
1427
  const foldedB = categoryB === 2 ? charB.toLowerCase() : charB;
1429
- if (foldedA !== foldedB) return foldedA < foldedB ? -1 : 1;
1428
+ if (foldedA < foldedB) return -1;
1429
+ if (foldedA > foldedB) return 1;
1430
1430
  }
1431
1431
  return a.length - b.length;
1432
1432
  }
@@ -1446,16 +1446,16 @@ function isValidSortAzOrder(prev, curr) {
1446
1446
  return compareSyncpackKey(prev, curr) <= 0;
1447
1447
  }
1448
1448
  /**
1449
- * The target permutation for a whole container's own keys, given a comparator over adjacent pairs (either `isValidTopLevelOrder`-shaped or `isValidSortAzOrder`-shaped). Returns, for each output position, the ORIGINAL index of the key that belongs there -- e.g. `[2, 0, 1]` means the key currently at index 2 comes first. A stable sort (ties keep their original relative order), matching real syncpack output for keys that compare equal (see `compareSyncpackKey`'s own case-insensitive-letter test case).
1449
+ * The target permutation for a whole container's own keys, given a comparator over adjacent pairs (either `isValidTopLevelOrder`-shaped or `isValidSortAzOrder`-shaped). Returns, for each output position, the ORIGINAL index of the key that belongs there e.g. `[2, 0, 1]` means the key currently at index 2 comes first. A stable sort (ties keep their original relative order), matching real syncpack output for keys that compare equal (see `compareSyncpackKey`'s own case-insensitive-letter test case).
1450
1450
  *
1451
- * Reordering by computing this full permutation once, rather than reporting and fixing one out-of-order adjacent pair at a time (the technique `@eslint/json`'s own `sort-keys` rule uses), is deliberate: an adjacent-swap fixer only makes one pass of bubble-sort-style progress per lint pass, and a realistically scrambled `package.json` (many top-level keys all needing to move several positions, several nested objects also needing sorting) was confirmed directly to need up to 11 fix passes to fully converge -- one more than ESLint's own `Linter#verifyAndFix` will ever run (`MAX_AUTOFIX_PASSES` is 10), meaning a single real `eslint --fix` invocation could leave such a file only partially reordered. Computing the whole target order up front and replacing every affected key's position in one combined fix converges in a single pass regardless of how scrambled the input is.
1451
+ * Reordering by computing this full permutation once, rather than reporting and fixing one out-of-order adjacent pair at a time (the technique `@eslint/json`'s own `sort-keys` rule uses), is deliberate: an adjacent-swap fixer only makes one pass of bubble-sort-style progress per lint pass, and a realistically scrambled `package.json` (many top-level keys all needing to move several positions, several nested objects also needing sorting) was confirmed directly to need up to 11 fix passes to fully converge one more than ESLint's own `Linter#verifyAndFix` will ever run (`MAX_AUTOFIX_PASSES` is 10), meaning a single real `eslint --fix` invocation could leave such a file only partially reordered. Computing the whole target order up front and replacing every affected key's position in one combined fix converges in a single pass regardless of how scrambled the input is.
1452
1452
  */
1453
1453
  function computeOrderPermutation(keys, isValidOrder) {
1454
1454
  return keys.map((_, index) => index).map((index) => ({
1455
- key: keys[index] ?? "",
1455
+ key: at(keys, index),
1456
1456
  index
1457
1457
  })).sort((a, b) => {
1458
- if (isValidOrder(a.key, b.key) && isValidOrder(b.key, a.key)) return a.index - b.index;
1458
+ if (isValidOrder(a.key, b.key) && isValidOrder(b.key, a.key)) return 0;
1459
1459
  return isValidOrder(a.key, b.key) ? -1 : 1;
1460
1460
  }).map((entry) => entry.index);
1461
1461
  }
@@ -1465,17 +1465,27 @@ function isIdentityPermutation(permutation) {
1465
1465
  function isStringNode(node) {
1466
1466
  return node.type === "String";
1467
1467
  }
1468
+ function isStringNamed(name) {
1469
+ return name.type === "String";
1470
+ }
1468
1471
  function getMemberKeyName(member) {
1469
- return member.name.type === "String" ? member.name.value : void 0;
1472
+ const { name } = member;
1473
+ if (!isStringNamed(name)) throw new Error(`Unreachable: package-json-key-order only supports json/json and json/jsonc, where an unquoted (Identifier) member name is a parse error — got a "${name.type}" name instead.`);
1474
+ return name.value;
1475
+ }
1476
+ function rangeOf(node) {
1477
+ if (node.range === void 0) throw new Error("Unreachable: every node reaching this function was produced by a JSON language that always requests range tracking.");
1478
+ return node.range;
1470
1479
  }
1471
- function hasRange(node) {
1472
- return node.range !== void 0;
1480
+ function objectParentOrThrow(parent) {
1481
+ if (parent === void 0) throw new Error("Unreachable: the Object visitor is never invoked for the traversal root, which is always the Document node.");
1482
+ return parent;
1473
1483
  }
1474
1484
  const commentTypes = /* @__PURE__ */ new Set(["LineComment", "BlockComment"]);
1475
1485
  /**
1476
- * Enforces the same `package.json` key order `syncpack format` would produce: `sortFirst` fields pinned to the top in that exact order, then every other top-level key alphabetically; and, inside each `sortAz`-listed field's own object or array value, its members/elements sorted the same way (no pinning there -- `sortFirst` only ever applies at the top level).
1486
+ * Enforces the same `package.json` key order `syncpack format` would produce: `sortFirst` fields pinned to the top in that exact order, then every other top-level key alphabetically; and, inside each `sortAz`-listed field's own object or array value, its members/elements sorted the same way (no pinning there `sortFirst` only ever applies at the top level).
1477
1487
  *
1478
- * This exists so a project using `@exadev/eslint-config` without syncpack still gets real `package.json` canonicalization, and so a project using both never sees them fight: `eslint --fix` and `syncpack format` converge on the identical output, confirmed directly against real `syncpack@15` output rather than assumed from its docs (see `compareSyncpackKey`'s own comment). Deliberately narrower than `@eslint/json`'s own `sort-keys` rule, which only supports a single alphabetical order for every key with no field-specific pinning -- not a fit for syncpack's own two-tier scheme.
1488
+ * This exists so a project using `@exadev/eslint-config` without syncpack still gets real `package.json` canonicalization, and so a project using both never sees them fight: `eslint --fix` and `syncpack format` converge on the identical output, confirmed directly against real `syncpack@15` output rather than assumed from its docs (see `compareSyncpackKey`'s own comment). Deliberately narrower than `@eslint/json`'s own `sort-keys` rule, which only supports a single alphabetical order for every key with no field-specific pinning not a fit for syncpack's own two-tier scheme.
1479
1489
  *
1480
1490
  * Not part of `plugin.configs.recommended`: opt-in, the same tri-state shape as `react`/`nextjs` (see `docs.md`'s own usage example), since a project without syncpack needs to choose this deliberately, and a project with syncpack needs to confirm the two tools agree before turning it on.
1481
1491
  */
@@ -1513,65 +1523,42 @@ const packageJsonKeyOrder = {
1513
1523
  const { sourceCode } = context;
1514
1524
  function hasAdjacentComment(node) {
1515
1525
  const before = sourceCode.getTokenBefore(node, { includeComments: true });
1516
- let after = sourceCode.getTokenAfter(node, { includeComments: true });
1517
- if (after?.type === "Comma") after = sourceCode.getTokenAfter(after, { includeComments: true });
1526
+ const after = sourceCode.getTokenAfter(node, { includeComments: true });
1518
1527
  return before !== null && commentTypes.has(before.type) || after !== null && commentTypes.has(after.type);
1519
1528
  }
1520
1529
  function buildReorderedText(nodes, permutation) {
1521
1530
  const separators = nodes.slice(0, -1).map((node, index) => {
1522
- const next = nodes[index + 1];
1523
- return hasRange(node) && next !== void 0 && hasRange(next) ? sourceCode.text.slice(node.range[1], next.range[0]) : "";
1531
+ const next = at(nodes, index + 1);
1532
+ return sourceCode.text.slice(rangeOf(node)[1], rangeOf(next)[0]);
1524
1533
  });
1525
- return permutation.reduce((result, originalIndex, position) => {
1526
- const text = sourceCode.getText(nodes[originalIndex]);
1527
- return position === 0 ? text : `${result}${separators[position - 1] ?? ""}${text}`;
1528
- }, "");
1534
+ return permutation.map((originalIndex) => sourceCode.getText(at(nodes, originalIndex))).reduce((result, text, position) => `${result}${at(separators, position - 1)}${text}`);
1529
1535
  }
1530
1536
  function getNodeRanges(nodes) {
1531
- const ranges = [];
1532
- for (const node of nodes) {
1533
- if (!hasRange(node)) return void 0;
1534
- ranges.push(node.range);
1535
- }
1536
- return ranges;
1537
+ return nodes.map((node) => rangeOf(node));
1537
1538
  }
1538
1539
  function reportWholeContainerReorder(nodes, keys, isValidOrder, reportLoc) {
1539
1540
  const permutation = computeOrderPermutation(keys, isValidOrder);
1540
1541
  if (isIdentityPermutation(permutation)) return;
1541
- const firstNode = nodes[0];
1542
- const lastNode = nodes[nodes.length - 1];
1543
- if (firstNode === void 0 || lastNode === void 0) return;
1544
- let violationIndex = 1;
1545
- for (let index = 1; index < keys.length; index += 1) if (!isValidOrder(keys[index - 1] ?? "", keys[index] ?? "")) {
1546
- violationIndex = index;
1547
- break;
1548
- }
1542
+ const violationIndex = keys.findIndex((curr, index) => index > 0 && !isValidOrder(at(keys, index - 1), curr));
1549
1543
  context.report({
1550
- loc: reportLoc(nodes[violationIndex] ?? firstNode),
1544
+ loc: reportLoc(at(nodes, violationIndex)),
1551
1545
  messageId: "outOfOrder",
1552
1546
  data: {
1553
- curr: keys[violationIndex] ?? "",
1554
- prev: keys[violationIndex - 1] ?? ""
1547
+ curr: at(keys, violationIndex),
1548
+ prev: at(keys, violationIndex - 1)
1555
1549
  },
1556
1550
  fix(fixer) {
1551
+ if (nodes.some((node) => hasAdjacentComment(node))) return null;
1557
1552
  const ranges = getNodeRanges(nodes);
1558
- if (ranges === void 0 || nodes.some((node) => hasAdjacentComment(node))) return null;
1559
- const firstRange = ranges[0];
1560
- const lastRange = ranges[ranges.length - 1];
1561
- if (firstRange === void 0 || lastRange === void 0) return null;
1553
+ const firstRange = at(ranges, 0);
1554
+ const lastRange = at(ranges, ranges.length - 1);
1562
1555
  const rewritten = buildReorderedText(nodes, permutation);
1563
1556
  return fixer.replaceTextRange([firstRange[0], lastRange[1]], rewritten);
1564
1557
  }
1565
1558
  });
1566
1559
  }
1567
1560
  function checkMembers(members, isValidOrder) {
1568
- const keys = [];
1569
- for (const member of members) {
1570
- const key = getMemberKeyName(member);
1571
- if (key === void 0) return;
1572
- keys.push(key);
1573
- }
1574
- reportWholeContainerReorder(members, keys, isValidOrder, (member) => member.name.loc.start);
1561
+ reportWholeContainerReorder(members, members.map((member) => getMemberKeyName(member)), isValidOrder, (member) => member.name.loc.start);
1575
1562
  }
1576
1563
  function checkElements(elements) {
1577
1564
  const stringValues = [];
@@ -1582,21 +1569,19 @@ const packageJsonKeyOrder = {
1582
1569
  reportWholeContainerReorder(stringValues, stringValues.map((value) => value.value), isValidSortAzOrder, (value) => value.loc.start);
1583
1570
  }
1584
1571
  return {
1585
- Object(node, parent) {
1586
- if (parent === void 0) return;
1572
+ Object(node, rawParent) {
1573
+ const parent = objectParentOrThrow(rawParent);
1587
1574
  if (parent.type === "Document") {
1588
1575
  checkMembers(node.members, (prev, curr) => isValidTopLevelOrder(prev, curr, sortFirst));
1589
1576
  return;
1590
1577
  }
1591
1578
  if (parent.type === "Member") {
1592
- const key = getMemberKeyName(parent);
1593
- if (key !== void 0 && sortAz.has(key)) checkMembers(node.members, isValidSortAzOrder);
1579
+ if (sortAz.has(getMemberKeyName(parent))) checkMembers(node.members, isValidSortAzOrder);
1594
1580
  }
1595
1581
  },
1596
1582
  Array(node, parent) {
1597
1583
  if (parent?.type !== "Member") return;
1598
- const key = getMemberKeyName(parent);
1599
- if (key !== void 0 && sortAz.has(key)) checkElements(node.elements);
1584
+ if (sortAz.has(getMemberKeyName(parent))) checkElements(node.elements);
1600
1585
  }
1601
1586
  };
1602
1587
  }
@@ -1614,14 +1599,14 @@ const preferNumericSortCompare = createRule$2({
1614
1599
  meta: {
1615
1600
  type: "suggestion",
1616
1601
  hasSuggestions: true,
1617
- 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." },
1602
+ 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." },
1618
1603
  schema: [],
1619
1604
  messages: {
1620
1605
  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.",
1621
1606
  addAscendingCompare: "Add an ascending numeric compare function: '(a, b) => a - b'."
1622
- }
1607
+ },
1608
+ defaultOptions: []
1623
1609
  },
1624
- defaultOptions: [],
1625
1610
  create(context) {
1626
1611
  const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
1627
1612
  const checker = services.program.getTypeChecker();
@@ -1630,12 +1615,10 @@ const preferNumericSortCompare = createRule$2({
1630
1615
  const { callee } = node;
1631
1616
  if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || callee.computed) return;
1632
1617
  if (callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !SORT_METHOD_NAMES.has(callee.property.name)) return;
1633
- const receiverTsNode = services.esTreeNodeToTSNodeMap.get(callee.object);
1634
- if (!typescript.isExpression(receiverTsNode)) return;
1618
+ const receiverTsNode = asExpression(services.esTreeNodeToTSNodeMap.get(callee.object));
1635
1619
  const receiverType = checker.getTypeAtLocation(receiverTsNode);
1636
1620
  if (!checker.isArrayType(receiverType)) return;
1637
- if (!(0, ts_api_utils.isTypeReference)(receiverType)) return;
1638
- const [elementType] = checker.getTypeArguments(receiverType);
1621
+ const [elementType] = checker.getTypeArguments(asTypeReference(receiverType));
1639
1622
  if (!elementType || !isDefinitelyNumberType(elementType)) return;
1640
1623
  context.report({
1641
1624
  node,
@@ -1644,8 +1627,7 @@ const preferNumericSortCompare = createRule$2({
1644
1627
  suggest: [{
1645
1628
  messageId: "addAscendingCompare",
1646
1629
  fix(fixer) {
1647
- const closingParen = context.sourceCode.getLastToken(node);
1648
- if (!closingParen) return null;
1630
+ const closingParen = lastTokenOrThrow(context.sourceCode, node);
1649
1631
  return fixer.insertTextBefore(closingParen, "(a, b) => a - b");
1650
1632
  }
1651
1633
  }]
@@ -1657,7 +1639,6 @@ const preferNumericSortCompare = createRule$2({
1657
1639
  //#region src/rules/prefer-readonly-array-param.ts
1658
1640
  const createRule$1 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/ExaDev/eslint-config/blob/main/src/rules/${name}.ts`);
1659
1641
  function getFixableArrayOrTupleType(typeNode) {
1660
- if (typeNode.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTypeOperator && typeNode.operator === "readonly") return void 0;
1661
1642
  if (typeNode.type === _typescript_eslint_utils.AST_NODE_TYPES.TSArrayType) return typeNode;
1662
1643
  if (typeNode.type === _typescript_eslint_utils.AST_NODE_TYPES.TSTupleType) return typeNode;
1663
1644
  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;
@@ -1688,11 +1669,11 @@ const preferReadonlyArrayParam = createRule$1({
1688
1669
  meta: {
1689
1670
  type: "problem",
1690
1671
  fixable: "code",
1691
- 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." },
1672
+ 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." },
1692
1673
  schema: [],
1693
- 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." }
1674
+ 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." },
1675
+ defaultOptions: []
1694
1676
  },
1695
- defaultOptions: [],
1696
1677
  create(context) {
1697
1678
  function checkParam(param) {
1698
1679
  const annotatedNode = getAnnotatedParamNode$1(param);
@@ -1746,15 +1727,18 @@ function isFlatPropertyType(checker, type) {
1746
1727
  if ((type.flags & PRIMITIVE_LIKE_FLAGS) !== 0) return true;
1747
1728
  return checker.getSignaturesOfType(type, typescript.SignatureKind.Call).length > 0 && checker.getPropertiesOfType(type).length === 0 && checker.getIndexInfosOfType(type).length === 0;
1748
1729
  }
1730
+ function hasSymbolFlag(type, flag) {
1731
+ return ((type.getSymbol()?.flags ?? 0) & flag) !== 0;
1732
+ }
1749
1733
  function isFlatObjectType(checker, type, location) {
1750
1734
  if ((type.flags & typescript.TypeFlags.Object) === 0) return false;
1751
- if (type.flags & (typescript.TypeFlags.Union | typescript.TypeFlags.Intersection | typescript.TypeFlags.TypeParameter)) return false;
1752
- if (checker.isArrayType(type) || checker.isTupleType(type)) return false;
1753
1735
  if (checker.getSignaturesOfType(type, typescript.SignatureKind.Call).length > 0) return false;
1754
1736
  if (checker.getSignaturesOfType(type, typescript.SignatureKind.Construct).length > 0) return false;
1755
- if ((type.getSymbol()?.flags ?? 0) & typescript.SymbolFlags.Class) return false;
1737
+ if (hasSymbolFlag(type, typescript.SymbolFlags.Class)) return false;
1756
1738
  const symbolName = type.getSymbol()?.name;
1739
+ if (symbolName === "Date") return false;
1757
1740
  if (symbolName === "Map" || symbolName === "ReadonlyMap" || symbolName === "Set" || symbolName === "ReadonlySet") return false;
1741
+ if (symbolName === "WeakMap" || symbolName === "WeakSet") return false;
1758
1742
  for (const property of checker.getPropertiesOfType(type)) if (!isFlatPropertyType(checker, checker.getTypeOfSymbolAtLocation(property, location))) return false;
1759
1743
  for (const indexInfo of checker.getIndexInfosOfType(type)) if (!isFlatPropertyType(checker, indexInfo.type)) return false;
1760
1744
  return true;
@@ -1770,11 +1754,11 @@ const preferReadonlyObjectParam = createRule({
1770
1754
  meta: {
1771
1755
  type: "problem",
1772
1756
  fixable: "code",
1773
- 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." },
1757
+ 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." },
1774
1758
  schema: [],
1775
- 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." }
1759
+ 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." },
1760
+ defaultOptions: []
1776
1761
  },
1777
- defaultOptions: [],
1778
1762
  create(context) {
1779
1763
  const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
1780
1764
  const checker = services.program.getTypeChecker();
@@ -1896,14 +1880,13 @@ function resolveJsonPlugin(value) {
1896
1880
  if (isRecord(value) && isJsonLanguagePlugin(value["default"])) return value["default"];
1897
1881
  return isJsonLanguagePlugin(value) ? value : void 0;
1898
1882
  }
1899
- const JSON_PLUGIN_INSTALL_COMMAND = "pnpm add -D @eslint/json";
1900
1883
  function buildPackageJsonKeyOrderConfig(options = {}) {
1901
1884
  const cwd = options.cwd ?? process.cwd();
1902
1885
  if (options.enabled === false) return [];
1903
1886
  if (options.enabled === void 0 && hasSyncpackConfig(cwd)) return [];
1904
1887
  const jsonPlugin = resolveJsonPlugin(tryRequire("@eslint/json", options.requireFn));
1905
1888
  if (jsonPlugin === void 0) {
1906
- if (options.enabled === true) throw new Error(`@exadev/eslint-config: package.json key ordering was explicitly requested but '@eslint/json' could not be resolved. Install it with: ${JSON_PLUGIN_INSTALL_COMMAND}`);
1889
+ if (options.enabled === true) throw new Error(`@exadev/eslint-config: package.json key ordering was explicitly requested but '@eslint/json' could not be resolved. Install it with: pnpm add -D @eslint/json`);
1907
1890
  return [];
1908
1891
  }
1909
1892
  const ruleOptions = {