@blumintinc/eslint-plugin-blumint 1.17.2 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -176,6 +176,7 @@ full closed loop is documented in agora's `.claude/skills/eslint-autonomy/SKILL.
176
176
  | [no-firestore-object-arrays](docs/rules/no-firestore-object-arrays.md) | Disallow arrays of object types in Firestore models. Prefer Record maps keyed by id with an index field, or subcollections/arrays of IDs. | ✅ | | | | | |
177
177
  | [no-handler-suffix](docs/rules/no-handler-suffix.md) | Disallow the generic "handler" suffix in callback names so names explain the action they perform | ✅ | | | | | |
178
178
  | [no-hungarian](docs/rules/no-hungarian.md) | Disallow Hungarian notation in locally declared variables, types, and classes | ✅ | | | | | |
179
+ | [no-inline-component-prop](docs/rules/no-inline-component-prop.md) | Prevent inline function components defined in render from being passed to component-type props like CatalogWrapper to avoid remounts and UI flashes. | ✅ | | | | | |
179
180
  | [no-jsx-in-hooks](docs/rules/no-jsx-in-hooks.md) | Prevent hooks from returning JSX | ✅ | | | | | |
180
181
  | [no-jsx-whitespace-literal](docs/rules/no-jsx-whitespace-literal.md) | Disallow the use of {" "} elements in JSX code | ✅ | | | | | |
181
182
  | [no-margin-properties](docs/rules/no-margin-properties.md) | Prevent margin properties (margin, marginLeft, marginRight, marginTop, marginBottom, mx, my, etc.) in MUI styling because margins fight container-controlled spacing, double gutters, and misaligned breakpoints; keep spacing centralized with padding, gap, or spacing props instead. | ✅ | | | | | |
package/lib/index.js CHANGED
@@ -38,6 +38,7 @@ const no_hungarian_1 = require("./rules/no-hungarian");
38
38
  const no_misused_switch_case_1 = require("./rules/no-misused-switch-case");
39
39
  const no_unpinned_dependencies_1 = require("./rules/no-unpinned-dependencies");
40
40
  const no_unused_props_1 = require("./rules/no-unused-props");
41
+ const no_inline_component_prop_1 = require("./rules/no-inline-component-prop");
41
42
  const no_useless_fragment_1 = require("./rules/no-useless-fragment");
42
43
  const prefer_fragment_shorthand_1 = require("./rules/prefer-fragment-shorthand");
43
44
  const prefer_getter_over_parameterless_method_1 = require("./rules/prefer-getter-over-parameterless-method");
@@ -217,7 +218,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
217
218
  module.exports = {
218
219
  meta: {
219
220
  name: '@blumintinc/eslint-plugin-blumint',
220
- version: '1.17.2',
221
+ version: '1.18.0',
221
222
  },
222
223
  parseOptions: {
223
224
  ecmaVersion: 2020,
@@ -271,6 +272,7 @@ module.exports = {
271
272
  '@blumintinc/blumint/no-misused-switch-case': 'error',
272
273
  '@blumintinc/blumint/no-unpinned-dependencies': 'error',
273
274
  '@blumintinc/blumint/no-unused-props': 'error',
275
+ '@blumintinc/blumint/no-inline-component-prop': 'error',
274
276
  '@blumintinc/blumint/no-uuidv4-base62-as-key': 'error',
275
277
  '@blumintinc/blumint/no-useless-fragment': 'error',
276
278
  '@blumintinc/blumint/no-useless-usememo-primitives': 'error',
@@ -537,6 +539,7 @@ module.exports = {
537
539
  'no-misused-switch-case': no_misused_switch_case_1.noMisusedSwitchCase,
538
540
  'no-unpinned-dependencies': no_unpinned_dependencies_1.noUnpinnedDependencies,
539
541
  'no-unused-props': no_unused_props_1.noUnusedProps,
542
+ 'no-inline-component-prop': no_inline_component_prop_1.noInlineComponentProp,
540
543
  'no-useless-fragment': no_useless_fragment_1.noUselessFragment,
541
544
  'no-uuidv4-base62-as-key': no_uuidv4_base62_as_key_1.noUuidv4Base62AsKey,
542
545
  'enforce-dynamic-file-naming': enforce_dynamic_file_naming_1.default,
@@ -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.18.0",
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.18.0",
4
+ "date": "2026-07-02T16:19:34.385Z",
5
+ "rules": [
6
+ {
7
+ "name": "no-inline-component-prop",
8
+ "changeType": "feat",
9
+ "issues": [
10
+ 833
11
+ ],
12
+ "summary": "register the orphaned rule in the plugin (#1233)"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "version": "1.17.3",
18
+ "date": "2026-07-02T16:07:25.816Z",
19
+ "rules": [
20
+ {
21
+ "name": "no-unnecessary-verb-suffix",
22
+ "changeType": "fix",
23
+ "issues": [
24
+ 1256
25
+ ],
26
+ "summary": "make autofix reference-safe (closes #1256)"
27
+ }
28
+ ]
29
+ },
2
30
  {
3
31
  "version": "1.17.2",
4
32
  "date": "2026-07-02T12:29:19.590Z",