@blumintinc/eslint-plugin-blumint 1.20.12 → 1.20.13

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.12',
226
+ version: '1.20.13',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -237,6 +237,88 @@ function collectJsxElementNames(node) {
237
237
  visit(node);
238
238
  return names;
239
239
  }
240
+ /**
241
+ * Collect the capitalized binding names introduced by a destructuring pattern,
242
+ * following renames (`{ Slot: Renderer }`), defaults (`{ Slot = Fallback }`)
243
+ * and nesting (`{ slots: { Item } }`).
244
+ */
245
+ function collectPatternBindings(pattern, into) {
246
+ switch (pattern.type) {
247
+ case utils_1.AST_NODE_TYPES.Identifier:
248
+ if (/^[A-Z]/.test(pattern.name)) {
249
+ into.add(pattern.name);
250
+ }
251
+ break;
252
+ case utils_1.AST_NODE_TYPES.ObjectPattern:
253
+ for (const property of pattern.properties) {
254
+ if (property.type === utils_1.AST_NODE_TYPES.Property) {
255
+ collectPatternBindings(property.value, into);
256
+ }
257
+ // A `...rest` element rebinds the remaining props under a single
258
+ // lowercase-by-convention name; it introduces no component slot.
259
+ }
260
+ break;
261
+ case utils_1.AST_NODE_TYPES.AssignmentPattern:
262
+ collectPatternBindings(pattern.left, into);
263
+ break;
264
+ default:
265
+ break;
266
+ }
267
+ }
268
+ /**
269
+ * Names of JSX elements that resolve to one of the component's own props — a
270
+ * rendering strategy injected by the caller (`ViewComponent: ComponentType<T>`)
271
+ * rather than a fixed child.
272
+ *
273
+ * A props-parameter binding shadows every import, so such an element is not a
274
+ * dependency the parent can compose with: the concrete component is chosen per
275
+ * call site and the slot's accepted props are already constrained by the prop's
276
+ * own type annotation. Demanding `<Slot>Props` composition names a type that
277
+ * exists nowhere.
278
+ */
279
+ function collectPropSlotNames(funcNode) {
280
+ const slots = new Set();
281
+ const propsParam = funcNode.params[0];
282
+ if (!propsParam)
283
+ return slots;
284
+ if (propsParam.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
285
+ collectPatternBindings(propsParam, slots);
286
+ return slots;
287
+ }
288
+ // `(props: Props) => ...` — the slot may be destructured out of `props` in
289
+ // the body instead of in the signature.
290
+ if (propsParam.type !== utils_1.AST_NODE_TYPES.Identifier)
291
+ return slots;
292
+ const propsName = propsParam.name;
293
+ function visit(node) {
294
+ if (!node || typeof node !== 'object')
295
+ return;
296
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
297
+ node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
298
+ node.init?.type === utils_1.AST_NODE_TYPES.Identifier &&
299
+ node.init.name === propsName) {
300
+ collectPatternBindings(node.id, slots);
301
+ }
302
+ for (const key of Object.keys(node)) {
303
+ if (key === 'parent')
304
+ continue;
305
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
306
+ const child = node[key];
307
+ if (Array.isArray(child)) {
308
+ for (const item of child) {
309
+ if (item && typeof item === 'object' && 'type' in item) {
310
+ visit(item);
311
+ }
312
+ }
313
+ }
314
+ else if (child && typeof child === 'object' && 'type' in child) {
315
+ visit(child);
316
+ }
317
+ }
318
+ }
319
+ visit(funcNode.body);
320
+ return slots;
321
+ }
240
322
  /**
241
323
  * Find the Props type alias node that corresponds to a component by name.
242
324
  * Looks for `type <ComponentName>Props = ...` in the program body.
@@ -929,10 +1011,12 @@ exports.requirePropsComposition = (0, createRule_1.createRule)({
929
1011
  // Collect all JSX element names used in the component body
930
1012
  const body = funcNode.body ?? funcNode;
931
1013
  const allJsxNames = collectJsxElementNames(body);
1014
+ const propSlots = collectPropSlotNames(funcNode);
932
1015
  // Filter to non-excluded custom components
933
1016
  const depComponents = Array.from(allJsxNames).filter((name) => !excludeComponents.has(name) &&
934
1017
  !isDecorativeIcon(name) &&
935
1018
  name !== componentName &&
1019
+ !propSlots.has(name) &&
936
1020
  !isZeroPropComponent(prog, name));
937
1021
  if (depComponents.length < minDependencyCount) {
938
1022
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.12",
3
+ "version": "1.20.13",
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.20.13",
4
+ "date": "2026-07-29T06:26:25.973Z",
5
+ "rules": [
6
+ {
7
+ "name": "require-props-composition",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1374
11
+ ],
12
+ "summary": "exempt caller-injected component prop slots (closes #1374)"
13
+ }
14
+ ]
15
+ },
2
16
  {
3
17
  "version": "1.20.12",
4
18
  "date": "2026-07-28T22:32:01.811Z",