@blumintinc/eslint-plugin-blumint 1.20.199 → 1.20.200

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.199',
226
+ version: '1.20.200',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -204,7 +204,7 @@ function typeDeclarationNamed(statement, name) {
204
204
  * dropped the nested-`any` check rather than reporting anything.
205
205
  */
206
206
  function declarationOfType(from, name) {
207
- return (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => {
207
+ return (0, lexicalScope_1.resolveNameInEnclosingScopes)(from, name, 'type', (statements) => {
208
208
  for (const statement of statements) {
209
209
  const declaration = typeDeclarationNamed(statement, name);
210
210
  if (declaration) {
@@ -408,7 +408,10 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
408
408
  * the shadow.
409
409
  */
410
410
  function resolveClassBody(name, reference) {
411
- return ((0, lexicalScope_1.resolveInEnclosingScopes)(reference, (statements, container) => containerClasses(container, statements).get(name)) ?? null);
411
+ return ((0, lexicalScope_1.resolveNameInEnclosingScopes)(reference, name,
412
+ // `extends X` names X as a value, so a value binder — the parameter
413
+ // of a mixin factory — is what shadows a same-named outer class.
414
+ 'value', (statements, container) => containerClasses(container, statements).get(name)) ?? null);
412
415
  }
413
416
  function enclosingClassBody(node) {
414
417
  let current = node.parent;
@@ -40,7 +40,7 @@ function typeAliasNamed(statement, name) {
40
40
  * hoisting of type declarations.
41
41
  */
42
42
  function resolveTypeAlias(from, name) {
43
- return (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => {
43
+ return (0, lexicalScope_1.resolveNameInEnclosingScopes)(from, name, 'type', (statements) => {
44
44
  for (const statement of statements) {
45
45
  const alias = typeAliasNamed(statement, name);
46
46
  if (alias) {
@@ -493,7 +493,7 @@ function createReturnCycleResolver(visitorKeys) {
493
493
  bindings.set(container, computed);
494
494
  return computed;
495
495
  };
496
- const resolveFunctionInScope = (from, name) => (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements, container) => {
496
+ const resolveFunctionInScope = (from, name) => (0, lexicalScope_1.resolveNameInEnclosingScopes)(from, name, 'value', (statements, container) => {
497
497
  const scope = bindingsOf(container, statements);
498
498
  if (!scope.has(name))
499
499
  return undefined;
@@ -366,7 +366,7 @@ exports.noFirestoreObjectArrays = (0, createRule_1.createRule)({
366
366
  * declaration written below its own reference still resolves, matching
367
367
  * TypeScript's hoisting of type declarations.
368
368
  */
369
- const resolveTypeName = (from, name) => (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => typeDeclarationIn(statements, name), () => {
369
+ const resolveTypeName = (from, name) => (0, lexicalScope_1.resolveNameInEnclosingScopes)(from, name, 'type', (statements) => typeDeclarationIn(statements, name), () => {
370
370
  if (interfaceNames.has(name))
371
371
  return { kind: 'interface' };
372
372
  if (enumNames.has(name))
@@ -376,7 +376,7 @@ exports.noFirestoreObjectArrays = (0, createRule_1.createRule)({
376
376
  ? { kind: 'alias', typeAnnotation: aliased }
377
377
  : undefined;
378
378
  });
379
- const resolveConstArray = (from, name) => (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => constArrayIn(statements, name), () => constArrayNameToLiteral.get(name));
379
+ const resolveConstArray = (from, name) => (0, lexicalScope_1.resolveNameInEnclosingScopes)(from, name, 'value', (statements) => constArrayIn(statements, name), () => constArrayNameToLiteral.get(name));
380
380
  // Keyed by resolved declaration rather than by name: one name can denote
381
381
  // different declarations in different scopes, so a name-keyed memo would
382
382
  // answer for whichever scope asked first.
@@ -272,7 +272,7 @@ function isMapInstance(node) {
272
272
  * searched instead, and the first match wins, which is what shadowing requires.
273
273
  */
274
274
  function findVariableDeclaration(node, varName) {
275
- return (0, lexicalScope_1.resolveInEnclosingScopes)(node, (statements) => {
275
+ return (0, lexicalScope_1.resolveNameInEnclosingScopes)(node, varName, 'value', (statements) => {
276
276
  for (const statement of statements) {
277
277
  const declaration = (0, lexicalScope_1.declarationOf)(statement);
278
278
  if (declaration.type !== utils_1.AST_NODE_TYPES.VariableDeclaration)
@@ -266,7 +266,7 @@ function typeDeclarationIn(statements, name) {
266
266
  * find what the walk would have.
267
267
  */
268
268
  function findLocalTypeDeclaration(program, from, name) {
269
- return ((0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => typeDeclarationIn(statements, name) ?? undefined, () => typeDeclarationIn(program.body, name) ?? undefined) ?? null);
269
+ return ((0, lexicalScope_1.resolveNameInEnclosingScopes)(from, name, 'type', (statements) => typeDeclarationIn(statements, name) ?? undefined, () => typeDeclarationIn(program.body, name) ?? undefined) ?? null);
270
270
  }
271
271
  /**
272
272
  * Finds a type alias or interface a module exports by writing `export` in front
@@ -154,36 +154,57 @@ function constArrayMembersNamed(statement, name) {
154
154
  * type aliases hoist: a component declared above its own props alias must still
155
155
  * resolve it.
156
156
  */
157
- function aliasResolverFrom(lists, startIndex = 0) {
157
+ function aliasResolverFrom(frames, startIndex = 0, barrierFrom = 0) {
158
+ /**
159
+ * Walks the frames outward, stopping at the first binder that takes the name.
160
+ *
161
+ * A frame's `barriers` gate entry into it from the frame inside it, so a
162
+ * resumed resolver skips the barriers of the frame it resumes at: the alias
163
+ * body it is reading already sits there and never crosses them.
164
+ */
165
+ const search = (name, namespace, found) => {
166
+ for (let index = startIndex; index < frames.length; index += 1) {
167
+ const frame = frames[index];
168
+ if (index >= barrierFrom &&
169
+ frame.barriers.some((barrier) => (0, lexicalScope_1.bindsNameOutsideStatements)(barrier, name, namespace))) {
170
+ return undefined;
171
+ }
172
+ const match = found(frame, index);
173
+ if (match !== undefined) {
174
+ return match;
175
+ }
176
+ }
177
+ return undefined;
178
+ };
158
179
  return {
159
180
  typeAlias(name) {
160
- for (let index = startIndex; index < lists.length; index += 1) {
161
- for (const statement of lists[index]) {
181
+ return search(name, 'type', (frame, index) => {
182
+ for (const statement of frame.statements) {
162
183
  const declaration = aliasDeclarationNamed(statement, name);
163
184
  if (declaration) {
164
185
  return {
165
186
  typeNode: declaration.typeAnnotation,
166
- resolve: aliasResolverFrom(lists, index),
187
+ resolve: aliasResolverFrom(frames, index, index + 1),
167
188
  };
168
189
  }
169
190
  }
170
- }
171
- return undefined;
191
+ return undefined;
192
+ });
172
193
  },
173
194
  constArrayKeys(name) {
174
- for (let index = startIndex; index < lists.length; index += 1) {
175
- for (const statement of lists[index]) {
195
+ return search(name, 'value', (frame) => {
196
+ for (const statement of frame.statements) {
176
197
  const members = constArrayMembersNamed(statement, name);
177
198
  if (members)
178
199
  return members;
179
200
  }
180
- }
181
- return undefined;
201
+ return undefined;
202
+ });
182
203
  },
183
204
  };
184
205
  }
185
206
  function aliasResolverAt(from) {
186
- return aliasResolverFrom((0, lexicalScope_1.enclosingStatementLists)(from));
207
+ return aliasResolverFrom((0, lexicalScope_1.enclosingScopeFrames)(from));
187
208
  }
188
209
  /**
189
210
  * The property names a `Pick<T, K>` keep-list names, or `null` when the list is
@@ -303,7 +303,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
303
303
  * resolved fine (#1781).
304
304
  */
305
305
  function resolveTypeDeclaration(from, name) {
306
- return (0, lexicalScope_1.resolveInEnclosingScopes)(from, (statements) => {
306
+ return (0, lexicalScope_1.resolveNameInEnclosingScopes)(from, name, 'type', (statements) => {
307
307
  for (const statement of statements) {
308
308
  const found = typeDeclarationNamed(statement, name);
309
309
  if (found) {
@@ -1842,7 +1842,7 @@ exports.requirePropsComposition = (0, createRule_1.createRule)({
1842
1842
  * would instead hand one scope's verdict to another.
1843
1843
  */
1844
1844
  function findPropsTypeAliasByName(scope, typeName) {
1845
- return ((0, lexicalScope_1.resolveInEnclosingScopes)(scope, (statements) => {
1845
+ return ((0, lexicalScope_1.resolveNameInEnclosingScopes)(scope, typeName, 'type', (statements) => {
1846
1846
  for (const stmt of statements) {
1847
1847
  const declaration = (0, lexicalScope_1.declarationOf)(stmt);
1848
1848
  if (declaration.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration &&
@@ -82,3 +82,64 @@ export declare function resolveInEnclosingScopes<T>(from: TSESTree.Node, matchIn
82
82
  * the scope that referenced the alias.
83
83
  */
84
84
  export declare function enclosingStatementLists(from: TSESTree.Node): (readonly TSESTree.Node[])[];
85
+ /**
86
+ * One statement container on the chain, with the binders guarding entry to it.
87
+ *
88
+ * {@link enclosingStatementLists} loses the nodes BETWEEN two containers, and
89
+ * those are exactly the ones that bind a name without holding a statement — a
90
+ * function's parameters and type parameters sit on the function, not on the
91
+ * block that is its body. A caller that materialises the chain rather than
92
+ * walking it once therefore cannot ask the question {@link
93
+ * resolveNameInEnclosingScopes} asks, unless the frames carry them.
94
+ *
95
+ * `barriers` holds the nodes crossed to reach this frame from the one inside
96
+ * it, so a binder among them shadows the name for this frame and every frame
97
+ * outside it.
98
+ */
99
+ export type ScopeFrame = {
100
+ statements: readonly TSESTree.Node[];
101
+ barriers: readonly TSESTree.Node[];
102
+ };
103
+ /**
104
+ * The statement containers enclosing a node, innermost outward, each carrying
105
+ * the binders crossed to reach it.
106
+ */
107
+ export declare function enclosingScopeFrames(from: TSESTree.Node): ScopeFrame[];
108
+ /**
109
+ * Which of TypeScript's two declaration spaces a name is being resolved in.
110
+ *
111
+ * The distinction is load-bearing rather than pedantic: `function f<Props>()`
112
+ * shadows a `type Props` for every type position inside `f` while leaving a
113
+ * `const Props` untouched, and a parameter `(batch) => …` does the reverse.
114
+ * Testing both spaces at once would decline on a collision that TypeScript does
115
+ * not consider one, turning a resolution hole into an over-decline.
116
+ */
117
+ export type BindingNamespace = 'type' | 'value';
118
+ /**
119
+ * Whether a container binds `name` somewhere OTHER than its statement list.
120
+ *
121
+ * {@link statementsOf} recognises five containers, so every binder that is not a
122
+ * statement — a type parameter, a function parameter, a `catch` parameter, a
123
+ * `for` head, a function or class expression's own name — introduces a scope the
124
+ * walk cannot see. That makes the walk step straight past a shadow and let an
125
+ * outer declaration answer for a name the reference does not denote, which is
126
+ * precisely the failure move 3 of this module's contract forbids: an unrelated
127
+ * `type ToClose = () => void` answered for the type parameter of
128
+ * `function useThing<ToClose>(…)`, and `no-direct-function-state` reported —
129
+ * and rewrote — code whose state type it had never seen (#2257).
130
+ *
131
+ * A shadow is deliberately not a resolution. The binder proves only that the
132
+ * name is taken here, never what it denotes, so a caller must treat it exactly
133
+ * as {@link BOUND_UNPROVABLE} — stop, and answer nothing.
134
+ */
135
+ export declare function bindsNameOutsideStatements(container: TSESTree.Node, name: string, namespace: BindingNamespace): boolean;
136
+ /**
137
+ * {@link resolveInEnclosingScopes}, stopped by the binders it cannot see.
138
+ *
139
+ * The name and the space it is resolved in have to be stated by the caller
140
+ * because the shadow test needs them and `matchIn` only closes over them. That
141
+ * is the point of the separate entry rather than an optional argument: a caller
142
+ * resolving a name gets the shadow test by writing the name down, instead of by
143
+ * remembering that a hazard exists.
144
+ */
145
+ export declare function resolveNameInEnclosingScopes<T>(from: TSESTree.Node, name: string, namespace: BindingNamespace, matchIn: (statements: readonly TSESTree.Node[], container: TSESTree.Node) => ScopeMatch<T>, fallback?: () => T | undefined): T | undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.enclosingStatementLists = exports.resolveInEnclosingScopes = exports.BOUND_UNPROVABLE = exports.declarationOf = exports.statementsOf = void 0;
3
+ exports.resolveNameInEnclosingScopes = exports.bindsNameOutsideStatements = exports.enclosingScopeFrames = exports.enclosingStatementLists = exports.resolveInEnclosingScopes = exports.BOUND_UNPROVABLE = exports.declarationOf = exports.statementsOf = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  /**
6
6
  * Same-file lexical resolution: what a name denotes at the point it is written.
@@ -128,4 +128,154 @@ function enclosingStatementLists(from) {
128
128
  return lists;
129
129
  }
130
130
  exports.enclosingStatementLists = enclosingStatementLists;
131
+ /**
132
+ * The statement containers enclosing a node, innermost outward, each carrying
133
+ * the binders crossed to reach it.
134
+ */
135
+ function enclosingScopeFrames(from) {
136
+ const frames = [];
137
+ let barriers = [];
138
+ let current = from;
139
+ while (current) {
140
+ const statements = statementsOf(current);
141
+ if (statements) {
142
+ frames.push({ statements, barriers });
143
+ barriers = [];
144
+ }
145
+ else {
146
+ barriers.push(current);
147
+ }
148
+ current = current.parent;
149
+ }
150
+ return frames;
151
+ }
152
+ exports.enclosingScopeFrames = enclosingScopeFrames;
153
+ /**
154
+ * Whether a pattern binds `name`, looking through destructuring and defaults.
155
+ */
156
+ function patternBindsName(node, name) {
157
+ if (!node) {
158
+ return false;
159
+ }
160
+ switch (node.type) {
161
+ case utils_1.AST_NODE_TYPES.Identifier:
162
+ return node.name === name;
163
+ case utils_1.AST_NODE_TYPES.ArrayPattern:
164
+ return node.elements.some((element) => patternBindsName(element, name));
165
+ case utils_1.AST_NODE_TYPES.ObjectPattern:
166
+ return node.properties.some((property) => patternBindsName(property.type === utils_1.AST_NODE_TYPES.RestElement
167
+ ? property.argument
168
+ : property.value, name));
169
+ case utils_1.AST_NODE_TYPES.AssignmentPattern:
170
+ return patternBindsName(node.left, name);
171
+ case utils_1.AST_NODE_TYPES.RestElement:
172
+ return patternBindsName(node.argument, name);
173
+ default:
174
+ return false;
175
+ }
176
+ }
177
+ /**
178
+ * Whether a `for` head declares `name`, for any of the three loop spellings.
179
+ */
180
+ function forHeadBindsName(node, name) {
181
+ const head = node.type === utils_1.AST_NODE_TYPES.ForStatement
182
+ ? node.init
183
+ : node.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
184
+ node.type === utils_1.AST_NODE_TYPES.ForInStatement
185
+ ? node.left
186
+ : undefined;
187
+ return head?.type === utils_1.AST_NODE_TYPES.VariableDeclaration
188
+ ? head.declarations.some((declarator) => patternBindsName(declarator.id, name))
189
+ : false;
190
+ }
191
+ /**
192
+ * Whether a container binds `name` somewhere OTHER than its statement list.
193
+ *
194
+ * {@link statementsOf} recognises five containers, so every binder that is not a
195
+ * statement — a type parameter, a function parameter, a `catch` parameter, a
196
+ * `for` head, a function or class expression's own name — introduces a scope the
197
+ * walk cannot see. That makes the walk step straight past a shadow and let an
198
+ * outer declaration answer for a name the reference does not denote, which is
199
+ * precisely the failure move 3 of this module's contract forbids: an unrelated
200
+ * `type ToClose = () => void` answered for the type parameter of
201
+ * `function useThing<ToClose>(…)`, and `no-direct-function-state` reported —
202
+ * and rewrote — code whose state type it had never seen (#2257).
203
+ *
204
+ * A shadow is deliberately not a resolution. The binder proves only that the
205
+ * name is taken here, never what it denotes, so a caller must treat it exactly
206
+ * as {@link BOUND_UNPROVABLE} — stop, and answer nothing.
207
+ */
208
+ function bindsNameOutsideStatements(container, name, namespace) {
209
+ const node = container;
210
+ if (namespace === 'type') {
211
+ // `typeParameters` names two unrelated things: the DECLARATION that binds
212
+ // `<T>` on a function or class, and the INSTANTIATION that supplies `<Foo>`
213
+ // at a call, `new`, or type reference. Only the first binds anything, and
214
+ // the second holds type nodes carrying no `name` at all, so reading one as
215
+ // the other throws on any ancestor that passes type arguments.
216
+ const declared = node.typeParameters?.type === utils_1.AST_NODE_TYPES.TSTypeParameterDeclaration
217
+ ? node.typeParameters.params
218
+ : undefined;
219
+ return (declared?.some((parameter) => parameter.name.name === name) === true ||
220
+ (container.type === utils_1.AST_NODE_TYPES.ClassExpression &&
221
+ node.id?.name === name));
222
+ }
223
+ if ((container.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
224
+ container.type === utils_1.AST_NODE_TYPES.ClassExpression) &&
225
+ node.id?.name === name) {
226
+ return true;
227
+ }
228
+ if (container.type === utils_1.AST_NODE_TYPES.CatchClause &&
229
+ patternBindsName(container.param, name)) {
230
+ return true;
231
+ }
232
+ if (forHeadBindsName(container, name)) {
233
+ return true;
234
+ }
235
+ // `TSTypeParameterDeclaration` also carries `params`, of a node kind no
236
+ // pattern arm matches, so a type parameter cannot leak into this space.
237
+ return (isFunctionLike(container) &&
238
+ node.params?.some((parameter) => patternBindsName(parameter, name)) === true);
239
+ }
240
+ exports.bindsNameOutsideStatements = bindsNameOutsideStatements;
241
+ function isFunctionLike(node) {
242
+ return (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
243
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
244
+ node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
245
+ node.type === utils_1.AST_NODE_TYPES.TSDeclareFunction ||
246
+ node.type === utils_1.AST_NODE_TYPES.TSEmptyBodyFunctionExpression);
247
+ }
248
+ /**
249
+ * {@link resolveInEnclosingScopes}, stopped by the binders it cannot see.
250
+ *
251
+ * The name and the space it is resolved in have to be stated by the caller
252
+ * because the shadow test needs them and `matchIn` only closes over them. That
253
+ * is the point of the separate entry rather than an optional argument: a caller
254
+ * resolving a name gets the shadow test by writing the name down, instead of by
255
+ * remembering that a hazard exists.
256
+ */
257
+ function resolveNameInEnclosingScopes(from, name, namespace, matchIn, fallback) {
258
+ let current = from;
259
+ while (current) {
260
+ const statements = statementsOf(current);
261
+ if (statements) {
262
+ const match = matchIn(statements, current);
263
+ if (match === exports.BOUND_UNPROVABLE) {
264
+ return undefined;
265
+ }
266
+ if (match !== undefined) {
267
+ return match;
268
+ }
269
+ }
270
+ // Tested after the statement list, so a container that both declares the
271
+ // name and binds it — `function f<T>() { type T = …; }` — resolves to the
272
+ // declaration the reference actually denotes.
273
+ if (bindsNameOutsideStatements(current, name, namespace)) {
274
+ return undefined;
275
+ }
276
+ current = current.parent;
277
+ }
278
+ return fallback?.();
279
+ }
280
+ exports.resolveNameInEnclosingScopes = resolveNameInEnclosingScopes;
131
281
  //# sourceMappingURL=lexicalScope.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.199",
3
+ "version": "1.20.200",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,90 @@
1
1
  [
2
+ {
3
+ "version": "1.20.200",
4
+ "date": "2026-09-01T14:50:44.328Z",
5
+ "rules": [
6
+ {
7
+ "name": "enforce-firestore-doc-ref-generic",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 2258
11
+ ],
12
+ "summary": "stop type resolution at a shadowing type parameter (closes #2258)"
13
+ },
14
+ {
15
+ "name": "enforce-firestore-set-merge",
16
+ "changeType": "fix",
17
+ "issues": [
18
+ 2266
19
+ ],
20
+ "summary": "stop class resolution at a shadowing parameter (closes #2266)"
21
+ },
22
+ {
23
+ "name": "no-direct-function-state",
24
+ "changeType": "fix",
25
+ "issues": [
26
+ 2257
27
+ ],
28
+ "summary": "stop alias resolution at a shadowing type parameter (closes #2257)"
29
+ },
30
+ {
31
+ "name": "no-explicit-return-type",
32
+ "changeType": "fix",
33
+ "issues": [
34
+ 2265
35
+ ],
36
+ "summary": "stop function resolution at a shadowing parameter (closes #2265)"
37
+ },
38
+ {
39
+ "name": "no-firestore-object-arrays",
40
+ "changeType": "fix",
41
+ "issues": [
42
+ 2261
43
+ ],
44
+ "summary": "stop resolution at a shadowing type parameter (closes #2261)"
45
+ },
46
+ {
47
+ "name": "prefer-batch-operations",
48
+ "changeType": "fix",
49
+ "issues": [
50
+ 2264
51
+ ],
52
+ "summary": "stop setter resolution at a shadowing parameter (closes #2264)"
53
+ },
54
+ {
55
+ "name": "prefer-spread-over-reassembly",
56
+ "changeType": "fix",
57
+ "issues": [
58
+ 2259
59
+ ],
60
+ "summary": "stop resolution at a shadowing type parameter (closes #2259)"
61
+ },
62
+ {
63
+ "name": "prevent-children-clobber",
64
+ "changeType": "fix",
65
+ "issues": [
66
+ 2263
67
+ ],
68
+ "summary": "stop alias resolution at a shadowing parameter (closes #2263)"
69
+ },
70
+ {
71
+ "name": "require-hooks-default-params",
72
+ "changeType": "fix",
73
+ "issues": [
74
+ 2262
75
+ ],
76
+ "summary": "stop type resolution at a shadowing type parameter (closes #2262)"
77
+ },
78
+ {
79
+ "name": "require-props-composition",
80
+ "changeType": "fix",
81
+ "issues": [
82
+ 2260
83
+ ],
84
+ "summary": "stop props resolution at a shadowing type parameter (closes #2260)"
85
+ }
86
+ ]
87
+ },
2
88
  {
3
89
  "version": "1.20.199",
4
90
  "date": "2026-09-01T09:29:27.682Z",