@blumintinc/eslint-plugin-blumint 1.20.2 → 1.20.4

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
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.2',
226
+ version: '1.20.4',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -166,6 +166,28 @@ function isExported(node) {
166
166
  }
167
167
  return false;
168
168
  }
169
+ /**
170
+ * Renames `identifier` to `newName` while leaving every other token inside the
171
+ * identifier's range intact.
172
+ *
173
+ * A TSESTree `Identifier` node's range spans the tokens that trail its name:
174
+ * a type annotation (`validateBy: Validator`), a definite-assignment assertion
175
+ * (`validateBy!: Validator`) and an optional marker (`cbBy?: Fn`). Replacing the
176
+ * whole node therefore deletes them, and dropping a contextual type turns
177
+ * inferred parameters into implicit `any` so the file no longer compiles — a
178
+ * silent corruption, since the rule reports nothing afterwards (#1351). The name
179
+ * is always the identifier's first token, so replacing that token's range alone
180
+ * renames the symbol and nothing else.
181
+ */
182
+ function renameIdentifier(fixer, sourceCode, identifier, newName) {
183
+ const nameToken = sourceCode.getFirstToken(identifier);
184
+ // The token store yields the name for every real identifier; the arithmetic
185
+ // end keeps the range narrowed even if a token is somehow unavailable.
186
+ const nameEnd = nameToken
187
+ ? nameToken.range[1]
188
+ : identifier.range[0] + identifier.name.length;
189
+ return fixer.replaceTextRange([identifier.range[0], nameEnd], newName);
190
+ }
169
191
  /**
170
192
  * Walks a scope chain upward from `scope` (inclusive) and reports whether
171
193
  * `targetName` is bound anywhere between `scope` and `stopScope` (inclusive).
@@ -561,16 +583,40 @@ exports.noUnnecessaryVerbSuffix = (0, createRule_1.createRule)({
561
583
  // Scope-tracked symbols (FunctionDeclaration, VariableDeclarator
562
584
  // arrows/functions, named FunctionExpression): rename the
563
585
  // declaration identifier and every in-file reference together so
564
- // no call site is left pointing at the old name.
586
+ // no call site is left pointing at the old name. Every rewrite
587
+ // goes through renameIdentifier, because an identifier's range
588
+ // can carry trailing tokens that must survive a rename (#1351).
589
+ const { sourceCode } = context;
565
590
  const fixes = [
566
- fixer.replaceText(declarationIdNode, suggestion),
591
+ renameIdentifier(fixer, sourceCode, declarationIdNode, suggestion),
567
592
  ];
568
593
  if (targetVariable) {
569
594
  for (const ref of targetVariable.references) {
570
595
  // Skip the declaration identifier itself — already handled.
571
596
  if (ref.identifier === declarationIdNode)
572
597
  continue;
573
- fixes.push(fixer.replaceText(ref.identifier, suggestion));
598
+ const refParent = ref.identifier.parent;
599
+ // An object-literal shorthand `{ fooBar }` desugars to
600
+ // `{ fooBar: fooBar }`: the one token is both the property
601
+ // key and its value. Renaming it would rename the KEY too,
602
+ // silently changing the object's shape so every
603
+ // `obj.fooBar` consumer reads undefined. Expand to
604
+ // `oldKey: newName` so only the value moves (#1352).
605
+ if (refParent?.type === utils_1.AST_NODE_TYPES.Property &&
606
+ refParent.shorthand &&
607
+ refParent.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
608
+ fixes.push(fixer.replaceText(ref.identifier, `${name}: ${suggestion}`));
609
+ continue;
610
+ }
611
+ // A re-export specifier `export { fooBar }` binds the public
612
+ // export name to this identifier, a cross-file contract a
613
+ // single-file fixer cannot rewrite. The declaration-level
614
+ // export guard misses this form because the declaration
615
+ // itself carries no `export` keyword (#1352).
616
+ if (refParent?.type === utils_1.AST_NODE_TYPES.ExportSpecifier) {
617
+ return null;
618
+ }
619
+ fixes.push(renameIdentifier(fixer, sourceCode, ref.identifier, suggestion));
574
620
  }
575
621
  }
576
622
  return fixes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.2",
3
+ "version": "1.20.4",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,32 @@
1
1
  [
2
+ {
3
+ "version": "1.20.4",
4
+ "date": "2026-07-25T03:08:45.043Z",
5
+ "rules": [
6
+ {
7
+ "name": "no-unnecessary-verb-suffix",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1352
11
+ ],
12
+ "summary": "stop the rename autofix from renaming shorthand keys and re-exported names (closes #1352)"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "version": "1.20.3",
18
+ "date": "2026-07-25T02:54:46.197Z",
19
+ "rules": [
20
+ {
21
+ "name": "no-unnecessary-verb-suffix",
22
+ "changeType": "fix",
23
+ "issues": [
24
+ 1351
25
+ ],
26
+ "summary": "preserve type annotations when the rename autofix renames an identifier (closes #1351)"
27
+ }
28
+ ]
29
+ },
2
30
  {
3
31
  "version": "1.20.2",
4
32
  "date": "2026-07-25T02:42:18.863Z",