eslint-rule-forge 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
1
  import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
2
  import * as ts from 'typescript';
3
- import { hasForeignBinding } from '../bindings.mjs';
4
- import { bindValueImports } from '../imports.mjs';
3
+ import { bindValueImportsAt } from '../imports.mjs';
5
4
  import { getArgumentText } from '../parentheses.mjs';
6
5
  import { isIdentifierNamed, skipTypeWrappers, typeHoldsAtEveryLayer, } from '../type-wrappers.mjs';
7
6
 
@@ -68,7 +67,7 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
68
67
  schema: [],
69
68
  messages: {
70
69
  ...messages,
71
- replaceWithSafeParse: `Replace with \`Result.unwrapOkOr(${namespace}.${method}({{argName}}), Number.NaN)\`.`,
70
+ replaceWithSafeParse: `Replace with \`{{resultName}}.unwrapOkOr({{namespaceName}}.${method}({{argName}}), Number.NaN)\`.`,
72
71
  },
73
72
  defaultOptions: [],
74
73
  },
@@ -99,20 +98,27 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
99
98
  mut_reports.push({ node, argExpression: firstArg });
100
99
  },
101
100
  'Program:exit': () => {
102
- const binding = bindValueImports(sourceCode, moduleName, requiredImports);
103
- const localNameOf = (name) => binding?.localNames[requiredImports.indexOf(name)];
104
- const namespaceName = localNameOf(namespace);
105
- const resultName = localNameOf('Result');
101
+ if (mut_reports.length === 0) {
102
+ return;
103
+ }
104
+ const bindingAt = bindValueImportsAt(sourceCode, moduleName, requiredImports);
106
105
 
107
106
 
108
107
 
109
108
 
110
109
 
111
110
  for (const { node, argExpression } of mut_reports) {
111
+ const binding = bindingAt(node);
112
+ const localNameOf = (name) => binding?.localNames[requiredImports.indexOf(name)];
113
+ const namespaceName = localNameOf(namespace);
114
+ const resultName = localNameOf('Result');
112
115
  const argText = getArgumentText(sourceCode, argExpression);
113
116
  const data = {
114
117
  original: sourceCode.getText(node),
115
118
  argName: argText,
119
+
120
+ namespaceName: namespaceName ?? namespace,
121
+ resultName: resultName ?? 'Result',
116
122
  };
117
123
  context.report({
118
124
  node,
@@ -120,8 +126,7 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
120
126
  data,
121
127
  suggest: binding === undefined ||
122
128
  namespaceName === undefined ||
123
- resultName === undefined ||
124
- [namespaceName, resultName].some((name) => hasForeignBinding(sourceCode, node, name, moduleName))
129
+ resultName === undefined
125
130
  ? []
126
131
  : [
127
132
  {
@@ -140,7 +145,11 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
140
145
  },
141
146
  };
142
147
  };
143
- const messageText = ({ moduleName, namespace, method, }) => `Replace \`{{original}}\` with \`Result.unwrapOkOr(${namespace}.${method}({{argName}}), Number.NaN)\` from ${moduleName}. It parses some strings differently from the call it replaces, so check the input before applying it.`;
148
+
149
+
150
+
151
+
152
+ const messageText = ({ moduleName, method }) => `Replace \`{{original}}\` with \`{{resultName}}.unwrapOkOr({{namespaceName}}.${method}({{argName}}), Number.NaN)\` from ${moduleName}. It parses some strings differently from the call it replaces, so check the input before applying it.`;
144
153
 
145
154
  const isGlobalCall = (callee, name) => callee.type === AST_NODE_TYPES.Identifier && callee.name === name;
146
155
 
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-safe-parse.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-parse.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-safe-parse.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-parse.mts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-rule-forge",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "Helpers for writing typescript-eslint rules that see through type wrappers and parentheses, and resolve what a file imports.",
6
6
  "keywords": [
@@ -41,14 +41,14 @@
41
41
  "@typescript-eslint/rule-tester": "8.72.0",
42
42
  "dedent": "1.7.2",
43
43
  "eslint": "10.13.0",
44
- "eslint-config-typed": "7.4.0",
45
- "eslint-plugin-ts-fortress": "1.3.13",
46
- "eslint-plugin-ts-type-forge": "0.8.18",
44
+ "eslint-config-typed": "7.5.0",
45
+ "eslint-plugin-ts-fortress": "1.3.14",
46
+ "eslint-plugin-ts-type-forge": "0.8.19",
47
47
  "prettier": "3.9.10",
48
48
  "prettier-plugin-organize-imports": "4.3.0",
49
49
  "prettier-plugin-packagejson": "3.0.2",
50
50
  "ts-data-forge": "14.11.0",
51
- "ts-repo-utils": "10.7.2"
51
+ "ts-repo-utils": "10.7.3"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "eslint": ">=9.0.0",
package/src/imports.mts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
6
  import type { DeepReadonly } from 'ts-type-forge';
7
+ import { hasForeignBinding } from './bindings.mjs';
7
8
  import { isTypeWrapper, skipTypeWrappers } from './type-wrappers.mjs';
8
9
 
9
10
  /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
@@ -224,47 +225,60 @@ export const bindValueImports = (
224
225
  sourceCode: TSESLint.SourceCode,
225
226
  moduleName: string,
226
227
  exportNames: readonly string[],
227
- ): ValueImportBinding | undefined => {
228
- const program = sourceCode.ast;
229
-
230
- const allImports = program.body.filter(
231
- (node): node is TSESTree.ImportDeclaration =>
232
- node.type === AST_NODE_TYPES.ImportDeclaration,
233
- );
234
-
235
- const declarations = getImportDeclarations(program, moduleName);
236
-
237
- const named = declarations.flatMap((declaration) =>
238
- declaration.specifiers.flatMap((specifier) =>
239
- specifier.type === AST_NODE_TYPES.ImportSpecifier
240
- ? [{ declaration, specifier }]
241
- : [],
242
- ),
243
- );
228
+ ): ValueImportBinding | undefined =>
229
+ bindNames(sourceCode, moduleName, exportNames, undefined);
244
230
 
245
- const bindings = exportNames.map((exportName) =>
246
- bindingOf(sourceCode, allImports, named, exportName),
247
- );
248
-
249
- if (bindings.includes(undefined)) {
250
- return undefined;
251
- }
252
-
253
- const defined = bindings.filter((b) => b !== undefined);
254
-
255
- const toAdd = defined.flatMap((b) => (b.isAdded ? [b.localName] : []));
256
-
257
- const hosts = hostsFor(declarations);
231
+ /**
232
+ * {@link bindValueImports} for the code at a node, which writes an export
233
+ * through a namespace import of the module (`ns.f` after
234
+ * `import * as ns from '<moduleName>'`) rather than importing it by name beside
235
+ * the namespace. Gives, for a node, what a fix there writes, or `undefined`
236
+ * when no fix can: an import binds a name to something else, or something other
237
+ * than an import from the module takes a name the fix writes where the node is
238
+ * ({@link hasForeignBinding}).
239
+ *
240
+ * - An export imported as a value is written by its local name, as
241
+ * {@link bindValueImports} writes it.
242
+ * - Any other, one imported only by `import { type x }` included, goes through
243
+ * the first namespace import that is not `import type * as`, with no edit.
244
+ * - Where a local binding shadows the namespace's name, every export is bound
245
+ * as {@link bindValueImports} binds it instead.
246
+ *
247
+ * Each choice is worked out once, however many nodes ask.
248
+ */
249
+ export const bindValueImportsAt = (
250
+ sourceCode: TSESLint.SourceCode,
251
+ moduleName: string,
252
+ exportNames: readonly string[],
253
+ ): ((node: TSESTree.Node) => ValueImportBinding | undefined) => {
254
+ const namespaceName = getImportDeclarations(sourceCode.ast, moduleName)
255
+ .filter((declaration) => !isTypeOnlyDeclaration(declaration))
256
+ .flatMap((declaration) => declaration.specifiers)
257
+ .find(
258
+ (specifier) => specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,
259
+ )?.local.name;
258
260
 
259
- return {
260
- localNames: defined.map((b) => b.localName),
261
- edits: (fixer) => [
262
- ...defined.flatMap((b) => b.edits(fixer)),
263
- ...(toAdd.length === 0
264
- ? []
265
- : addToFirstHost(fixer, sourceCode, moduleName, hosts, toAdd)),
266
- ],
267
- };
261
+ const viaNamespace =
262
+ namespaceName === undefined
263
+ ? undefined
264
+ : bindNames(sourceCode, moduleName, exportNames, namespaceName);
265
+
266
+ const byName = bindNames(sourceCode, moduleName, exportNames, undefined);
267
+
268
+ const holdsAt = (
269
+ binding: BoundNames | undefined,
270
+ node: TSESTree.Node,
271
+ ): binding is BoundNames =>
272
+ binding?.boundNames.every(
273
+ (name) => !hasForeignBinding(sourceCode, node, name, moduleName),
274
+ ) ?? false;
275
+
276
+ return (node) =>
277
+ holdsAt(viaNamespace, node)
278
+ ? viaNamespace
279
+ : holdsAt(byName, node)
280
+ ? byName
281
+ : undefined;
268
282
  };
269
283
 
270
284
  /**
@@ -400,20 +414,6 @@ const addToFirstHost = (
400
414
  : buildNamedImportFix(fixer, sourceCode.ast, moduleName, specifiers);
401
415
  };
402
416
 
403
- /** What {@link bindValueImports} gives. */
404
- export type ValueImportBinding = Readonly<{
405
- /** The names to write, in the order of the export names asked for. */
406
- localNames: readonly string[];
407
-
408
- /** The edits that bring every one of them into scope. */
409
- edits: (fixer: TSESLint.RuleFixer) => readonly TSESLint.RuleFix[];
410
- }>;
411
-
412
- type NamedSpecifier = Readonly<{
413
- declaration: TSESTree.ImportDeclaration;
414
- specifier: TSESTree.ImportSpecifier;
415
- }>;
416
-
417
417
  /** `import type …`. */
418
418
  const isTypeOnlyDeclaration = (
419
419
  declaration: TSESTree.ImportDeclaration,
@@ -460,9 +460,11 @@ const bindingOf = (
460
460
  allImports: readonly TSESTree.ImportDeclaration[],
461
461
  named: readonly NamedSpecifier[],
462
462
  exportName: string,
463
+ namespaceName: string | undefined,
463
464
  ):
464
465
  | Readonly<{
465
466
  localName: string;
467
+ boundName: string;
466
468
  isAdded: boolean;
467
469
  edits: (fixer: TSESLint.RuleFixer) => readonly TSESLint.RuleFix[];
468
470
  }>
@@ -476,6 +478,16 @@ const bindingOf = (
476
478
  if (value !== undefined) {
477
479
  return {
478
480
  localName: value.specifier.local.name,
481
+ boundName: value.specifier.local.name,
482
+ isAdded: false,
483
+ edits: () => [],
484
+ };
485
+ }
486
+
487
+ if (namespaceName !== undefined) {
488
+ return {
489
+ localName: `${namespaceName}.${exportName}`,
490
+ boundName: namespaceName,
479
491
  isAdded: false,
480
492
  edits: () => [],
481
493
  };
@@ -497,6 +509,7 @@ const bindingOf = (
497
509
  ? undefined
498
510
  : {
499
511
  localName: inlineType.local.name,
512
+ boundName: inlineType.local.name,
500
513
  isAdded: false,
501
514
  edits: (fixer) => [
502
515
  fixer.removeRange([typeKeyword.range[0], next.range[0]]),
@@ -512,5 +525,89 @@ const bindingOf = (
512
525
 
513
526
  return isTaken
514
527
  ? undefined
515
- : { localName: exportName, isAdded: true, edits: () => [] };
528
+ : {
529
+ localName: exportName,
530
+ boundName: exportName,
531
+ isAdded: true,
532
+ edits: () => [],
533
+ };
534
+ };
535
+
536
+ /**
537
+ * {@link bindValueImports}, writing an export no value import binds through the
538
+ * namespace import `namespaceName` when there is one. `boundNames` are the
539
+ * names the written text starts with, which something at the node must not
540
+ * shadow.
541
+ */
542
+ const bindNames = (
543
+ sourceCode: TSESLint.SourceCode,
544
+ moduleName: string,
545
+ exportNames: readonly string[],
546
+ namespaceName: string | undefined,
547
+ ): BoundNames | undefined => {
548
+ const program = sourceCode.ast;
549
+
550
+ const allImports = program.body.filter(
551
+ (node): node is TSESTree.ImportDeclaration =>
552
+ node.type === AST_NODE_TYPES.ImportDeclaration,
553
+ );
554
+
555
+ const declarations = getImportDeclarations(program, moduleName);
556
+
557
+ const named = declarations.flatMap((declaration) =>
558
+ declaration.specifiers.flatMap((specifier) =>
559
+ specifier.type === AST_NODE_TYPES.ImportSpecifier
560
+ ? [{ declaration, specifier }]
561
+ : [],
562
+ ),
563
+ );
564
+
565
+ const bindings = exportNames.map((exportName) =>
566
+ bindingOf(sourceCode, allImports, named, exportName, namespaceName),
567
+ );
568
+
569
+ if (bindings.includes(undefined)) {
570
+ return undefined;
571
+ }
572
+
573
+ const defined = bindings.filter((b) => b !== undefined);
574
+
575
+ const toAdd = defined.flatMap((b) => (b.isAdded ? [b.localName] : []));
576
+
577
+ const hosts = hostsFor(declarations);
578
+
579
+ return {
580
+ localNames: defined.map((b) => b.localName),
581
+ boundNames: defined.map((b) => b.boundName),
582
+ edits: (fixer) => [
583
+ ...defined.flatMap((b) => b.edits(fixer)),
584
+ ...(toAdd.length === 0
585
+ ? []
586
+ : addToFirstHost(fixer, sourceCode, moduleName, hosts, toAdd)),
587
+ ],
588
+ };
516
589
  };
590
+
591
+ /** What {@link bindValueImports} and {@link bindValueImportsAt} give. */
592
+ export type ValueImportBinding = Readonly<{
593
+ /**
594
+ * What to write for each export, in the order of the export names asked for:
595
+ * a local name, or `ns.x` through a namespace import.
596
+ */
597
+ localNames: readonly string[];
598
+
599
+ /** The edits that bring every one of them into scope. */
600
+ edits: (fixer: TSESLint.RuleFixer) => readonly TSESLint.RuleFix[];
601
+ }>;
602
+
603
+ /** What {@link bindNames} gives. */
604
+ type BoundNames = ValueImportBinding &
605
+ Readonly<{
606
+ /** The name each of `localNames` starts with. */
607
+ boundNames: readonly string[];
608
+ }>;
609
+
610
+ type NamedSpecifier = Readonly<{
611
+ declaration: TSESTree.ImportDeclaration;
612
+ specifier: TSESTree.ImportSpecifier;
613
+ }>;
@@ -3,8 +3,7 @@ import {
3
3
  type TSESLint,
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
- import { hasForeignBinding } from '../bindings.mjs';
7
- import { bindValueImports } from '../imports.mjs';
6
+ import { bindValueImportsAt } from '../imports.mjs';
8
7
  import { getRangeWithParens } from '../parentheses.mjs';
9
8
  import { isIdentifierNamed, skipTypeWrappers } from '../type-wrappers.mjs';
10
9
 
@@ -22,19 +21,18 @@ export const createPreferArrayIsArray = ({
22
21
 
23
22
  /** The exported namespace whose `isArray` replaces `Array.isArray`. */
24
23
  namespace: string;
25
- }>): TSESLint.RuleModule<'useIsArray', readonly []> => {
26
- const replacement = `${namespace}.isArray` as const;
27
-
28
- const text =
29
- `Replace \`Array.isArray\` with \`${replacement}\` from ${moduleName}.` as const;
30
-
31
- return {
24
+ }>): TSESLint.RuleModule<'useIsArray', readonly []> =>
25
+ ({
32
26
  meta: {
33
27
  type: 'suggestion',
34
- docs: { description: text },
28
+ docs: {
29
+ description: `Replace \`Array.isArray\` with \`${namespace}.isArray\` from ${moduleName}.`,
30
+ },
35
31
  fixable: 'code',
36
32
  schema: [],
37
- messages: { useIsArray: text },
33
+ messages: {
34
+ useIsArray: `Replace \`Array.isArray\` with \`{{replacement}}\` from ${moduleName}.`,
35
+ },
38
36
  defaultOptions: [],
39
37
  },
40
38
 
@@ -52,20 +50,30 @@ export const createPreferArrayIsArray = ({
52
50
  mut_nodesToFix.push(node);
53
51
  },
54
52
  'Program:exit': () => {
55
- const binding = bindValueImports(sourceCode, moduleName, [namespace]);
53
+ if (mut_nodesToFix.length === 0) {
54
+ return;
55
+ }
56
56
 
57
- const localName = binding?.localNames[0];
57
+ const bindingAt = bindValueImportsAt(sourceCode, moduleName, [
58
+ namespace,
59
+ ]);
58
60
 
59
61
  // Every fix brings the namespace into scope itself, so that one
60
62
  // applied alone (an editor's quick fix) is complete.
61
63
  for (const node of mut_nodesToFix) {
64
+ const binding = bindingAt(node);
65
+
66
+ const localName = binding?.localNames[0];
67
+
62
68
  context.report({
63
69
  node,
64
70
  messageId: 'useIsArray',
71
+ data: {
72
+ // The name the fix writes; with no fix, the export's own.
73
+ replacement: `${localName ?? namespace}.isArray`,
74
+ },
65
75
  fix:
66
- binding === undefined ||
67
- localName === undefined ||
68
- hasForeignBinding(sourceCode, node, localName, moduleName)
76
+ binding === undefined || localName === undefined
69
77
  ? null
70
78
  : (fixer) => [
71
79
  ...binding.edits(fixer),
@@ -82,8 +90,7 @@ export const createPreferArrayIsArray = ({
82
90
  },
83
91
  };
84
92
  },
85
- } as const;
86
- };
93
+ }) as const;
87
94
 
88
95
  /**
89
96
  * Whether `node` calls `Array.isArray`, the callee and `Array` both read
@@ -3,8 +3,7 @@ import {
3
3
  type TSESLint,
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
- import { hasForeignBinding } from '../bindings.mjs';
7
- import { bindValueImports } from '../imports.mjs';
6
+ import { bindValueImportsAt } from '../imports.mjs';
8
7
  import { getRangeWithParens, parenthesesDepth } from '../parentheses.mjs';
9
8
  import { skipTypeWrappers } from '../type-wrappers.mjs';
10
9
 
@@ -28,7 +27,7 @@ export const createPreferIsNonNullObject = ({
28
27
  fixable: 'code',
29
28
  schema: [],
30
29
  messages: {
31
- useIsNonNullObject: `Replace the object/null check with \`isNonNullObject()\` from ${moduleName}.`,
30
+ useIsNonNullObject: `Replace the object/null check with \`{{isNonNullObject}}()\` from ${moduleName}.`,
32
31
  },
33
32
  defaultOptions: [],
34
33
  },
@@ -54,15 +53,21 @@ export const createPreferIsNonNullObject = ({
54
53
  );
55
54
  },
56
55
  'Program:exit': () => {
57
- const binding = bindValueImports(sourceCode, moduleName, [
56
+ if (mut_pairsToFix.length === 0) {
57
+ return;
58
+ }
59
+
60
+ const bindingAt = bindValueImportsAt(sourceCode, moduleName, [
58
61
  'isNonNullObject',
59
62
  ]);
60
63
 
61
- const localName = binding?.localNames[0];
62
-
63
64
  // Every fix brings `isNonNullObject` into scope itself, so that one
64
65
  // applied alone (an editor's quick fix) is complete.
65
66
  for (const { range, identifierName, chain } of mut_pairsToFix) {
67
+ const binding = bindingAt(chain);
68
+
69
+ const localName = binding?.localNames[0];
70
+
66
71
  const loc = {
67
72
  start: sourceCode.getLocFromIndex(range[0]),
68
73
  end: sourceCode.getLocFromIndex(range[1]),
@@ -71,10 +76,12 @@ export const createPreferIsNonNullObject = ({
71
76
  context.report({
72
77
  loc,
73
78
  messageId: 'useIsNonNullObject',
79
+ data: {
80
+ // The name the fix writes; with no fix, the export's own.
81
+ isNonNullObject: localName ?? 'isNonNullObject',
82
+ },
74
83
  fix:
75
- binding === undefined ||
76
- localName === undefined ||
77
- hasForeignBinding(sourceCode, chain, localName, moduleName)
84
+ binding === undefined || localName === undefined
78
85
  ? null
79
86
  : (fixer) => [
80
87
  ...binding.edits(fixer),
@@ -4,8 +4,7 @@ import {
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
6
  import * as ts from 'typescript';
7
- import { hasForeignBinding } from '../bindings.mjs';
8
- import { bindValueImports } from '../imports.mjs';
7
+ import { bindValueImportsAt } from '../imports.mjs';
9
8
  import { getArgumentText } from '../parentheses.mjs';
10
9
  import {
11
10
  isIdentifierNamed,
@@ -33,8 +32,8 @@ export const createPreferIsRecordAndHasKey = ({
33
32
  fixable: 'code',
34
33
  schema: [],
35
34
  messages: {
36
- useIsRecordAndHasKey: `Replace \`{{original}}\` with \`isRecord({{objName}}) && hasKey({{objName}}, {{keyName}})\` from ${moduleName}.`,
37
- useHasKey: `Replace \`{{original}}\` with \`hasKey({{objName}}, {{keyName}})\` from ${moduleName}.`,
35
+ useIsRecordAndHasKey: `Replace \`{{original}}\` with \`{{isRecord}}({{objName}}) && {{hasKey}}({{objName}}, {{keyName}})\` from ${moduleName}.`,
36
+ useHasKey: `Replace \`{{original}}\` with \`{{hasKey}}({{objName}}, {{keyName}})\` from ${moduleName}.`,
38
37
  },
39
38
  defaultOptions: [],
40
39
  },
@@ -152,14 +151,18 @@ export const createPreferIsRecordAndHasKey = ({
152
151
  },
153
152
 
154
153
  'Program:exit': () => {
154
+ if (mut_nodesToFix.length === 0) {
155
+ return;
156
+ }
157
+
155
158
  // Bound once for each shape of rewrite: a file may import `hasKey`
156
159
  // but not `isRecord`, which only the rewrites that need it add.
157
- const withIsRecord = bindValueImports(sourceCode, moduleName, [
160
+ const withIsRecordAt = bindValueImportsAt(sourceCode, moduleName, [
158
161
  'isRecord',
159
162
  'hasKey',
160
163
  ]);
161
164
 
162
- const hasKeyOnly = bindValueImports(sourceCode, moduleName, [
165
+ const hasKeyOnlyAt = bindValueImportsAt(sourceCode, moduleName, [
163
166
  'hasKey',
164
167
  ]);
165
168
 
@@ -168,7 +171,9 @@ export const createPreferIsRecordAndHasKey = ({
168
171
  // true, so it is left out of the replacement entirely.
169
172
  const needsIsRecord = !isKnownRecord(objExpression);
170
173
 
171
- const binding = needsIsRecord ? withIsRecord : hasKeyOnly;
174
+ const binding = (needsIsRecord ? withIsRecordAt : hasKeyOnlyAt)(
175
+ node,
176
+ );
172
177
 
173
178
  const hasKeyName = binding?.localNames.at(-1);
174
179
 
@@ -185,11 +190,7 @@ export const createPreferIsRecordAndHasKey = ({
185
190
  hasKeyName !== undefined &&
186
191
  (!needsIsRecord || isRecordName !== undefined) &&
187
192
  // `isRecord(obj) && hasKey(obj, key)` evaluates `obj` twice.
188
- (!needsIsRecord || isSideEffectFree(objExpression)) &&
189
- binding.localNames.every(
190
- (name) =>
191
- !hasForeignBinding(sourceCode, node, name, moduleName),
192
- );
193
+ (!needsIsRecord || isSideEffectFree(objExpression));
193
194
 
194
195
  context.report({
195
196
  node,
@@ -198,6 +199,10 @@ export const createPreferIsRecordAndHasKey = ({
198
199
  original: sourceCode.getText(node),
199
200
  objName: objText,
200
201
  keyName: keyText,
202
+ // The names a fix here writes; where none can bind them, the
203
+ // exports' own.
204
+ isRecord: isRecordName ?? 'isRecord',
205
+ hasKey: hasKeyName ?? 'hasKey',
201
206
  },
202
207
  fix: fixable
203
208
  ? (fixer) => [