@blumintinc/eslint-plugin-blumint 1.17.2 → 1.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -217,7 +217,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
217
217
  module.exports = {
218
218
  meta: {
219
219
  name: '@blumintinc/eslint-plugin-blumint',
220
- version: '1.17.2',
220
+ version: '1.17.3',
221
221
  },
222
222
  parseOptions: {
223
223
  ecmaVersion: 2020,
@@ -131,6 +131,41 @@ function isPhrasalVerbEnding(name, suffix) {
131
131
  const lastWord = beforeSuffix.match(/[A-Z]?[a-z]+$/)?.[0] ?? '';
132
132
  return phrasalVerbStem(lastWord) !== null;
133
133
  }
134
+ /**
135
+ * Returns true when `node` (or its relevant ancestor for the declaration kind)
136
+ * is directly wrapped in an export declaration. An exported symbol must not be
137
+ * auto-fixed because the fixer can only rename within the current file, leaving
138
+ * cross-file import references broken.
139
+ *
140
+ * Call this with:
141
+ * - the FunctionDeclaration node for `function foo() {}`
142
+ * - the ArrowFunctionExpression/FunctionExpression node for `const foo = () => {}`
143
+ * (its parent chain: arrow → VariableDeclarator → VariableDeclaration → export?)
144
+ */
145
+ function isExported(node) {
146
+ const parent = node.parent;
147
+ if (!parent)
148
+ return false;
149
+ // FunctionDeclaration directly inside `export function foo() {}`
150
+ if (parent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
151
+ parent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
152
+ return true;
153
+ }
154
+ // Arrow/FunctionExpression assigned to a VariableDeclarator:
155
+ // VariableDeclarator → VariableDeclaration → ExportNamedDeclaration
156
+ if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
157
+ const varDecl = parent.parent; // VariableDeclaration
158
+ if (!varDecl)
159
+ return false;
160
+ const varDeclParent = varDecl.parent; // possible ExportNamedDeclaration
161
+ if (varDeclParent &&
162
+ (varDeclParent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
163
+ varDeclParent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration)) {
164
+ return true;
165
+ }
166
+ }
167
+ return false;
168
+ }
134
169
  exports.noUnnecessaryVerbSuffix = (0, createRule_1.createRule)({
135
170
  name: 'no-unnecessary-verb-suffix',
136
171
  meta: {
@@ -147,7 +182,27 @@ exports.noUnnecessaryVerbSuffix = (0, createRule_1.createRule)({
147
182
  },
148
183
  defaultOptions: [],
149
184
  create(context) {
150
- function checkFunctionName(node, name) {
185
+ function checkFunctionName(node, name,
186
+ /**
187
+ * The AST node that holds the identifier being renamed.
188
+ * For FunctionDeclaration this is `node.id`; for VariableDeclarator
189
+ * assigned arrows it is `declarator.id`; for Property/MethodDefinition
190
+ * it is the key node. Passing it explicitly avoids re-deriving it inside
191
+ * the fixer and allows the reference-rename loop to skip it cleanly.
192
+ */
193
+ declarationIdNode,
194
+ /**
195
+ * The AST node whose declared variables the scope manager tracks.
196
+ * For FunctionDeclaration this is `node` itself; for a VariableDeclarator
197
+ * arrow it is the VariableDeclarator; for methods/properties it is null
198
+ * because member references are not in scope (resolved via `this.x`).
199
+ */
200
+ scopeNode,
201
+ /**
202
+ * Whether the symbol is exported. When true, the fix is suppressed so we
203
+ * don't produce broken cross-file renames.
204
+ */
205
+ exported) {
151
206
  if (!name)
152
207
  return;
153
208
  for (const suffix of COMMON_PREPOSITION_SUFFIXES) {
@@ -179,25 +234,50 @@ exports.noUnnecessaryVerbSuffix = (0, createRule_1.createRule)({
179
234
  suggestion,
180
235
  },
181
236
  fix(fixer) {
182
- if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
183
- return fixer.replaceText(node.id, suggestion);
237
+ // An autofix here is only reference-safe when the fixer can
238
+ // rename EVERY use of the symbol, not just its declaration —
239
+ // otherwise call sites are orphaned and produce a ReferenceError
240
+ // (#1256). Two cases make that impossible, so the fix is
241
+ // suppressed (report only, no `--fix` change):
242
+ //
243
+ // 1. Exported symbols — a single-file fixer cannot reach
244
+ // cross-file import references.
245
+ // 2. Member-accessed symbols (class methods, object-literal
246
+ // properties, interface method signatures) — their call
247
+ // sites are member expressions (`this.x()`, `obj.x()`) that
248
+ // the scope manager does not track as variable references,
249
+ // so they cannot be found and renamed syntactically.
250
+ // `scopeNode` is null for exactly these declarations.
251
+ if (exported || !scopeNode || !declarationIdNode) {
252
+ return null;
184
253
  }
185
- else if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
186
- node.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
187
- const parent = node.parent;
188
- if (parent &&
189
- (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
190
- parent.type === utils_1.AST_NODE_TYPES.Property ||
191
- parent.type === utils_1.AST_NODE_TYPES.MethodDefinition)) {
192
- if ('key' in parent) {
193
- return fixer.replaceText(parent.key, suggestion);
194
- }
195
- else if ('id' in parent && parent.id) {
196
- return fixer.replaceText(parent.id, suggestion);
197
- }
254
+ // Scope-tracked symbols (FunctionDeclaration, VariableDeclarator
255
+ // arrows/functions, named FunctionExpression): rename the
256
+ // declaration identifier and every in-file reference together so
257
+ // no call site is left pointing at the old name.
258
+ const fixes = [
259
+ fixer.replaceText(declarationIdNode, suggestion),
260
+ ];
261
+ // Note: context.getDeclaredVariables is the API available in the
262
+ // pinned @typescript-eslint version (the SourceCode-based
263
+ // replacement is not yet in these type definitions).
264
+ const declaredVars = context.getDeclaredVariables(scopeNode);
265
+ // getDeclaredVariables returns ALL variables the node declares
266
+ // (e.g. for a FunctionDeclaration it includes the function name
267
+ // variable AND its parameter variables). Filter to the one whose
268
+ // name matches the symbol being renamed so we only follow
269
+ // references to the name, not parameters.
270
+ for (const variable of declaredVars) {
271
+ if (variable.name !== name)
272
+ continue;
273
+ for (const ref of variable.references) {
274
+ // Skip the declaration identifier itself — already handled.
275
+ if (ref.identifier === declarationIdNode)
276
+ continue;
277
+ fixes.push(fixer.replaceText(ref.identifier, suggestion));
198
278
  }
199
279
  }
200
- return null;
280
+ return fixes;
201
281
  },
202
282
  });
203
283
  }
@@ -207,38 +287,49 @@ exports.noUnnecessaryVerbSuffix = (0, createRule_1.createRule)({
207
287
  return {
208
288
  FunctionDeclaration(node) {
209
289
  if (node.id) {
210
- checkFunctionName(node, node.id.name);
290
+ checkFunctionName(node, node.id.name, node.id, node, isExported(node));
211
291
  }
212
292
  },
213
293
  VariableDeclarator(node) {
214
294
  if (node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
215
295
  (node.init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
216
296
  node.init?.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
217
- checkFunctionName(node.init, node.id.name);
297
+ checkFunctionName(node.init, node.id.name, node.id, node, isExported(node.init));
218
298
  }
219
299
  },
220
300
  MethodDefinition(node) {
221
301
  if (node.key.type === utils_1.AST_NODE_TYPES.Identifier) {
222
- checkFunctionName(node.value, node.key.name);
302
+ // Class methods are called via member expressions (`this.method()`,
303
+ // `instance.method()`) that the scope manager does not track as
304
+ // references. A syntactic single-file fixer therefore cannot find and
305
+ // rename those call sites, so renaming the method would orphan them
306
+ // (#1256). Pass null for both the rename target and scopeNode so the
307
+ // violation is still reported but no unsafe fix is offered.
308
+ checkFunctionName(node.value, node.key.name, null, null, false);
223
309
  }
224
310
  },
225
311
  TSMethodSignature(node) {
226
- // Handle interface method signatures
312
+ // Interface method signatures have their implementations and call sites
313
+ // elsewhere (member accesses on implementers), unreachable from this
314
+ // declaration. Report only — never offer a rename fix.
227
315
  if (node.key.type === utils_1.AST_NODE_TYPES.Identifier) {
228
- checkFunctionName(node, node.key.name);
316
+ checkFunctionName(node, node.key.name, null, null, false);
229
317
  }
230
318
  },
231
319
  Property(node) {
232
320
  if (node.key.type === utils_1.AST_NODE_TYPES.Identifier &&
233
321
  (node.value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
234
322
  node.value.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
235
- checkFunctionName(node.value, node.key.name);
323
+ // Object-literal method properties are accessed via member expressions
324
+ // (`obj.method()`) the scope manager does not track. As with class
325
+ // methods, the fix is suppressed to avoid orphaning call sites.
326
+ checkFunctionName(node.value, node.key.name, null, null, false);
236
327
  }
237
328
  },
238
329
  FunctionExpression(node) {
239
330
  // Handle named function expressions
240
331
  if (node.id) {
241
- checkFunctionName(node, node.id.name);
332
+ checkFunctionName(node, node.id.name, node.id, node, isExported(node));
242
333
  }
243
334
  },
244
335
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.17.2",
3
+ "version": "1.17.3",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "version": "1.17.3",
4
+ "date": "2026-07-02T16:07:25.816Z",
5
+ "rules": [
6
+ {
7
+ "name": "no-unnecessary-verb-suffix",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1256
11
+ ],
12
+ "summary": "make autofix reference-safe (closes #1256)"
13
+ }
14
+ ]
15
+ },
2
16
  {
3
17
  "version": "1.17.2",
4
18
  "date": "2026-07-02T12:29:19.590Z",