@blumintinc/eslint-plugin-blumint 1.20.165 → 1.20.167
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
|
@@ -822,12 +822,55 @@ function isPropertyComplex(prop, checker, tsNode, ts, treatAnyAsComplex, parentT
|
|
|
822
822
|
}
|
|
823
823
|
return shouldTreatAnyAsComplex(prop, propType, ts, treatAnyAsComplex, parentTypeFlags, checker);
|
|
824
824
|
}
|
|
825
|
+
/**
|
|
826
|
+
* Returns true when `declaration` was written by a dependency rather than by the
|
|
827
|
+
* component's author: a declaration file, or any file under `node_modules`.
|
|
828
|
+
* Keys on the same `getSourceFile().fileName` inspection the React and DOM
|
|
829
|
+
* origin gates use.
|
|
830
|
+
*/
|
|
831
|
+
function isExternalDeclaration(declaration) {
|
|
832
|
+
const sourceFile = declaration.getSourceFile?.();
|
|
833
|
+
if (!sourceFile)
|
|
834
|
+
return false;
|
|
835
|
+
const fileName = sourceFile.fileName ?? '';
|
|
836
|
+
return (sourceFile.isDeclarationFile === true ||
|
|
837
|
+
/\.d\.[cm]?ts$/.test(fileName) ||
|
|
838
|
+
/[/\\]node_modules[/\\]/.test(fileName));
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Returns true when every declaration site of `prop` lives in a dependency.
|
|
842
|
+
*
|
|
843
|
+
* `checker.getPropertiesOfType` returns INHERITED members, so a props type that
|
|
844
|
+
* extends or intersects a library interface (MUI's `TypographyProps`, React's
|
|
845
|
+
* `HTMLAttributes`, …) surfaces that library's entire surface. Demanding the
|
|
846
|
+
* author name those in `compareDeeply` prescribes a remedy nobody can act on —
|
|
847
|
+
* the component neither declares nor receives them, and the lists run past a
|
|
848
|
+
* hundred names — so the only available exit is a blanket rule disable.
|
|
849
|
+
*
|
|
850
|
+
* The gate is `every` rather than "first declaration", so a prop the author
|
|
851
|
+
* redeclares alongside the library's (the intersection of two same-named
|
|
852
|
+
* members yields one symbol carrying BOTH declarations) still counts as
|
|
853
|
+
* authored and is still reported.
|
|
854
|
+
*
|
|
855
|
+
* A prop with no declarations at all is treated as authored. Synthesized
|
|
856
|
+
* symbols reach here from mapped and generic types over the component's own
|
|
857
|
+
* props, and reporting them preserves the rule's core behaviour; a missing
|
|
858
|
+
* declaration is not evidence of a dependency.
|
|
859
|
+
*/
|
|
860
|
+
function isExternallyDeclaredProperty(prop) {
|
|
861
|
+
const declarations = prop.declarations;
|
|
862
|
+
if (!declarations || declarations.length === 0)
|
|
863
|
+
return false;
|
|
864
|
+
return declarations.every(isExternalDeclaration);
|
|
865
|
+
}
|
|
825
866
|
function getComplexPropertiesFromType(type, checker, tsNode, ts, treatAnyAsComplex = false, parentTypeFlags = 0) {
|
|
826
867
|
const properties = checker.getPropertiesOfType(type);
|
|
827
868
|
const complexProps = [];
|
|
828
869
|
for (const prop of properties) {
|
|
829
870
|
if (isReservedReactPropName(prop.name))
|
|
830
871
|
continue;
|
|
872
|
+
if (isExternallyDeclaredProperty(prop))
|
|
873
|
+
continue;
|
|
831
874
|
if (isPropertyComplex(prop, checker, tsNode, ts, treatAnyAsComplex, parentTypeFlags)) {
|
|
832
875
|
complexProps.push(prop.name);
|
|
833
876
|
}
|
|
@@ -119,8 +119,8 @@ function getTypeReferenceName(node) {
|
|
|
119
119
|
/**
|
|
120
120
|
* Recursively check if a TS type node composes with the given propsTypeName
|
|
121
121
|
* via Pick/Omit (at any level of intersection / Readonly wrapping, union arm,
|
|
122
|
-
* named-alias indirection, or nested in a TSTypeLiteral
|
|
123
|
-
* annotation).
|
|
122
|
+
* array or tuple element, named-alias indirection, or nested in a TSTypeLiteral
|
|
123
|
+
* property's type annotation).
|
|
124
124
|
*
|
|
125
125
|
* `scope` (when supplied) is the node the alias lookup walks outward from, which
|
|
126
126
|
* enables resolving a locally-declared named type alias to its definition, so
|
|
@@ -185,6 +185,46 @@ function typeNodeComposesWithProps(typeNode, propsTypeName, scope, seenAliases =
|
|
|
185
185
|
// case, where every arm composes with the single shared child.
|
|
186
186
|
return typeNode.types.some((t) => typeNodeComposesWithProps(t, propsTypeName, scope, seenAliases));
|
|
187
187
|
}
|
|
188
|
+
case utils_1.AST_NODE_TYPES.TSArrayType: {
|
|
189
|
+
// A list renderer's composition lives in the ELEMENT type: a parent
|
|
190
|
+
// declaring `buttons: readonly ActionButtonProps[]` and spreading one
|
|
191
|
+
// element onto each child forwards the child's ENTIRE prop object, which
|
|
192
|
+
// is the same DRY guarantee a direct `Pick`/`Omit` prop gives — the
|
|
193
|
+
// composition is simply one level of indirection away, inside the array.
|
|
194
|
+
// The child's contract belongs to the element type there, so demanding a
|
|
195
|
+
// whole-props Pick/Omit on the parent would name a composition a list
|
|
196
|
+
// renderer must not have (issue #2038). This is the array analogue of the
|
|
197
|
+
// union-arm unwrapping issue #1343 established.
|
|
198
|
+
return typeNodeComposesWithProps(typeNode.elementType, propsTypeName, scope, seenAliases);
|
|
199
|
+
}
|
|
200
|
+
case utils_1.AST_NODE_TYPES.TSTupleType: {
|
|
201
|
+
// A tuple is a fixed-length list, and each slot is handed to a child
|
|
202
|
+
// exactly as an array element is, so any slot that composes composes.
|
|
203
|
+
return typeNode.elementTypes.some((element) => typeNodeComposesWithProps(element, propsTypeName, scope, seenAliases));
|
|
204
|
+
}
|
|
205
|
+
case utils_1.AST_NODE_TYPES.TSNamedTupleMember: {
|
|
206
|
+
// A tuple slot's label names the slot, never its surface.
|
|
207
|
+
return typeNodeComposesWithProps(typeNode.elementType, propsTypeName, scope, seenAliases);
|
|
208
|
+
}
|
|
209
|
+
case utils_1.AST_NODE_TYPES.TSOptionalType:
|
|
210
|
+
case utils_1.AST_NODE_TYPES.TSRestType: {
|
|
211
|
+
// `?` and `...` govern how many slots a tuple carries, not what a slot
|
|
212
|
+
// carries, so the decorated type is the surface to test.
|
|
213
|
+
return typeNodeComposesWithProps(typeNode.typeAnnotation, propsTypeName, scope, seenAliases);
|
|
214
|
+
}
|
|
215
|
+
case utils_1.AST_NODE_TYPES.TSTypeOperator: {
|
|
216
|
+
// `readonly T[]` describes the same prop surface as `T[]`: the modifier
|
|
217
|
+
// constrains mutation of the container, never the shape of its elements.
|
|
218
|
+
//
|
|
219
|
+
// Only `readonly` unwraps. `keyof ChildProps` is the child's KEY union — a
|
|
220
|
+
// set of strings that hands the child nothing — and `unique symbol` is a
|
|
221
|
+
// nominal token, so crediting either would let a parent that merely names
|
|
222
|
+
// the child's keys pass as composing with its props.
|
|
223
|
+
if (typeNode.operator !== 'readonly' || !typeNode.typeAnnotation) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
return typeNodeComposesWithProps(typeNode.typeAnnotation, propsTypeName, scope, seenAliases);
|
|
227
|
+
}
|
|
188
228
|
case utils_1.AST_NODE_TYPES.TSTypeLiteral: {
|
|
189
229
|
// Check property signatures for nested composition
|
|
190
230
|
// e.g. { iconProps?: Omit<GradientIconButtonProps, 'IconComponent'> }
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.167",
|
|
4
|
+
"date": "2026-08-18T04:28:02.499Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "require-props-composition",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2038
|
|
11
|
+
],
|
|
12
|
+
"summary": "credit composition through an array element (closes #2038)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.20.166",
|
|
18
|
+
"date": "2026-08-18T03:18:38.188Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "memo-compare-deeply-complex-props",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
2037
|
|
25
|
+
],
|
|
26
|
+
"summary": "exempt props declared by a dependency (closes #2037)"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
2
30
|
{
|
|
3
31
|
"version": "1.20.165",
|
|
4
32
|
"date": "2026-08-18T00:07:35.027Z",
|