@blumintinc/eslint-plugin-blumint 1.13.0 → 1.14.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 +170 -139
- package/lib/index.js +190 -1
- package/lib/rules/array-methods-this-context.js +1 -1
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +1 -1
- package/lib/rules/dynamic-https-errors.js +3 -4
- package/lib/rules/enforce-assert-safe-object-key.js +11 -11
- package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
- package/lib/rules/enforce-callback-memo.js +1 -1
- package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
- package/lib/rules/enforce-console-error.js +35 -7
- package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
- package/lib/rules/enforce-dynamic-file-naming.js +53 -28
- package/lib/rules/enforce-early-destructuring.d.ts +2 -0
- package/lib/rules/enforce-early-destructuring.js +980 -0
- package/lib/rules/enforce-empty-object-check.d.ts +11 -0
- package/lib/rules/enforce-empty-object-check.js +502 -0
- package/lib/rules/enforce-exported-function-types.js +1 -1
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
- package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
- package/lib/rules/enforce-firestore-facade.js +4 -2
- package/lib/rules/enforce-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.js +1 -1
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.js +2 -6
- package/lib/rules/enforce-memoize-getters.js +39 -15
- package/lib/rules/enforce-microdiff.js +1 -1
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +4 -0
- package/lib/rules/enforce-props-argument-name.js +2 -2
- package/lib/rules/enforce-safe-stringify.js +1 -1
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-singular-type-names.js +1 -1
- package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
- package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
- package/lib/rules/enforce-storage-context.d.ts +9 -0
- package/lib/rules/enforce-storage-context.js +646 -0
- package/lib/rules/enforce-timestamp-now.js +23 -7
- package/lib/rules/enforce-transform-memoization.d.ts +4 -0
- package/lib/rules/enforce-transform-memoization.js +416 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
- package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
- package/lib/rules/enforce-unique-cursor-headers.js +365 -0
- package/lib/rules/enforce-verb-noun-naming.js +5 -4
- package/lib/rules/ensure-pointer-events-none.js +28 -2
- package/lib/rules/export-if-in-doubt.js +27 -4
- package/lib/rules/extract-global-constants.js +40 -13
- package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
- package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- package/lib/rules/global-const-style.js +50 -19
- package/lib/rules/jsdoc-above-field.d.ts +11 -0
- package/lib/rules/jsdoc-above-field.js +208 -0
- package/lib/rules/key-only-outermost-element.js +21 -3
- package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
- package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
- package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
- package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +410 -0
- package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
- package/lib/rules/no-always-true-false-conditions.js +68 -8
- package/lib/rules/no-array-length-in-deps.js +9 -3
- package/lib/rules/no-async-foreach.js +210 -12
- package/lib/rules/no-circular-references.js +18 -17
- package/lib/rules/no-class-instance-destructuring.js +55 -12
- package/lib/rules/no-complex-cloud-params.js +7 -3
- package/lib/rules/no-compositing-layer-props.js +2 -2
- package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
- package/lib/rules/no-console-error.d.ts +9 -0
- package/lib/rules/no-console-error.js +525 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
- package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
- package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
- package/lib/rules/no-entire-object-hook-deps.js +4 -6
- package/lib/rules/no-excessive-parent-chain.js +1 -1
- package/lib/rules/no-explicit-return-type.d.ts +2 -1
- package/lib/rules/no-explicit-return-type.js +215 -26
- package/lib/rules/no-filter-without-return.js +5 -1
- package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
- package/lib/rules/no-firestore-jest-mock.js +126 -8
- package/lib/rules/no-firestore-object-arrays.js +67 -12
- package/lib/rules/no-handler-suffix.d.ts +12 -0
- package/lib/rules/no-handler-suffix.js +305 -0
- package/lib/rules/no-hungarian.js +1 -1
- package/lib/rules/no-inline-component-prop.d.ts +10 -0
- package/lib/rules/no-inline-component-prop.js +456 -0
- package/lib/rules/no-jsx-in-hooks.js +6 -1
- package/lib/rules/no-jsx-whitespace-literal.js +8 -2
- package/lib/rules/no-margin-properties.js +6 -6
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
- package/lib/rules/no-misused-switch-case.js +22 -1
- package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
- package/lib/rules/no-mixed-firestore-transactions.js +296 -34
- package/lib/rules/no-mock-firebase-admin.js +5 -2
- package/lib/rules/no-object-values-on-strings.js +10 -2
- package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
- package/lib/rules/no-passthrough-getters.js +38 -2
- package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
- package/lib/rules/no-redundant-annotation-assertion.js +402 -0
- package/lib/rules/no-redundant-param-types.js +16 -6
- package/lib/rules/no-redundant-this-params.d.ts +3 -0
- package/lib/rules/no-redundant-this-params.js +459 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
- package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
- package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
- package/lib/rules/no-restricted-properties-fix.js +11 -10
- package/lib/rules/no-separate-loading-state.js +7 -16
- package/lib/rules/no-stale-state-across-await.js +1 -1
- package/lib/rules/no-type-assertion-returns.js +43 -49
- package/lib/rules/no-undefined-null-passthrough.js +61 -31
- package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
- package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
- package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
- package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
- package/lib/rules/no-unnecessary-destructuring.js +14 -4
- package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unused-props.d.ts +2 -2
- package/lib/rules/no-unused-props.js +295 -91
- package/lib/rules/no-unused-usestate.js +6 -2
- package/lib/rules/no-useless-fragment.js +28 -2
- package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
- package/lib/rules/no-useless-usememo-primitives.js +393 -0
- package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
- package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
- package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
- package/lib/rules/omit-index-html.d.ts +2 -1
- package/lib/rules/omit-index-html.js +62 -7
- package/lib/rules/optimize-object-boolean-conditions.js +6 -4
- package/lib/rules/parallelize-async-operations.js +24 -5
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +32 -5
- package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
- package/lib/rules/prefer-clone-deep.js +3 -3
- package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
- package/lib/rules/prefer-destructuring-no-class.js +244 -59
- package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
- package/lib/rules/prefer-docsetter-setall.js +243 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
- package/lib/rules/prefer-fragment-component.js +1 -1
- package/lib/rules/prefer-fragment-shorthand.js +2 -1
- package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
- package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
- package/lib/rules/prefer-global-router-state-key.js +88 -13
- package/lib/rules/prefer-memoized-props.d.ts +3 -0
- package/lib/rules/prefer-memoized-props.js +445 -0
- package/lib/rules/prefer-next-dynamic.js +60 -69
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
- package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
- package/lib/rules/prefer-params-over-parent-id.js +220 -171
- package/lib/rules/prefer-settings-object.js +2 -2
- package/lib/rules/prefer-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
- package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
- package/lib/rules/prevent-children-clobber.d.ts +2 -0
- package/lib/rules/prevent-children-clobber.js +536 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +495 -0
- package/lib/rules/react-usememo-should-be-component.js +2 -2
- package/lib/rules/require-hooks-default-params.js +17 -16
- package/lib/rules/require-https-error-cause.d.ts +4 -0
- package/lib/rules/require-https-error-cause.js +136 -0
- package/lib/rules/require-https-error.js +43 -21
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +1 -1
- package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
- package/lib/rules/require-memoize-jsx-returners.js +485 -0
- package/lib/rules/require-usememo-object-literals.js +2 -3
- package/lib/rules/test-file-location-enforcement.js +1 -1
- package/lib/rules/use-latest-callback.js +6 -5
- package/lib/rules/vertically-group-related-functions.d.ts +16 -0
- package/lib/rules/vertically-group-related-functions.js +480 -0
- package/lib/utils/ASTHelpers.d.ts +13 -1
- package/lib/utils/ASTHelpers.js +24 -0
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +3 -1
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.preventChildrenClobber = void 0;
|
|
27
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
28
|
+
const ts = __importStar(require("typescript"));
|
|
29
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
30
|
+
const createRule_1 = require("../utils/createRule");
|
|
31
|
+
function resolveFunctionName(node) {
|
|
32
|
+
if ('id' in node && node.id?.name) {
|
|
33
|
+
return node.id.name;
|
|
34
|
+
}
|
|
35
|
+
if (node.type === utils_1.AST_NODE_TYPES.FunctionExpression &&
|
|
36
|
+
node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
37
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
38
|
+
return node.parent.id.name;
|
|
39
|
+
}
|
|
40
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
41
|
+
node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
42
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
43
|
+
return node.parent.id.name;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function isComponentLike(node) {
|
|
48
|
+
const name = resolveFunctionName(node);
|
|
49
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return ASTHelpers_1.ASTHelpers.returnsJSX(node.body);
|
|
53
|
+
}
|
|
54
|
+
function patternHasChildrenProperty(pattern) {
|
|
55
|
+
return pattern.properties.some((prop) => {
|
|
56
|
+
if (prop.type !== utils_1.AST_NODE_TYPES.Property)
|
|
57
|
+
return false;
|
|
58
|
+
if (prop.computed)
|
|
59
|
+
return false;
|
|
60
|
+
const key = prop.key;
|
|
61
|
+
if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
62
|
+
return key.name === 'children';
|
|
63
|
+
}
|
|
64
|
+
if (key.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
65
|
+
return key.value === 'children';
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function typeNodeContainsLiteral(node, literalValue) {
|
|
71
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSLiteralType) {
|
|
72
|
+
return node.literal.type === utils_1.AST_NODE_TYPES.Literal
|
|
73
|
+
? node.literal.value === literalValue
|
|
74
|
+
: false;
|
|
75
|
+
}
|
|
76
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSUnionType) {
|
|
77
|
+
return node.types.some((t) => typeNodeContainsLiteral(t, literalValue));
|
|
78
|
+
}
|
|
79
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTupleType) {
|
|
80
|
+
return node.elementTypes.some((t) => typeNodeContainsLiteral(t, literalValue));
|
|
81
|
+
}
|
|
82
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSArrayType) {
|
|
83
|
+
return typeNodeContainsLiteral(node.elementType, literalValue);
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
function typeNodeExcludesProperty(node, propertyName, aliasMap, seen = new Set()) {
|
|
88
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
89
|
+
const typeName = node.typeName.type === utils_1.AST_NODE_TYPES.Identifier
|
|
90
|
+
? node.typeName.name
|
|
91
|
+
: null;
|
|
92
|
+
if (typeName === 'Omit' && node.typeParameters?.params?.[1]) {
|
|
93
|
+
const excluded = node.typeParameters.params[1];
|
|
94
|
+
if (typeNodeContainsLiteral(excluded, propertyName) ||
|
|
95
|
+
typeNodeExcludesProperty(excluded, propertyName, aliasMap, seen)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (node.typeParameters?.params) {
|
|
100
|
+
return node.typeParameters.params.some((param) => typeNodeExcludesProperty(param, propertyName, aliasMap, seen));
|
|
101
|
+
}
|
|
102
|
+
if (typeName && aliasMap?.has(typeName) && !seen.has(typeName)) {
|
|
103
|
+
seen.add(typeName);
|
|
104
|
+
const alias = aliasMap.get(typeName);
|
|
105
|
+
if (alias &&
|
|
106
|
+
typeNodeExcludesProperty(alias, propertyName, aliasMap, seen)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSUnionType) {
|
|
112
|
+
return node.types.every((typeNode) => typeNodeExcludesProperty(typeNode, propertyName, aliasMap, seen));
|
|
113
|
+
}
|
|
114
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
|
|
115
|
+
return node.types.every((typeNode) => typeNodeExcludesProperty(typeNode, propertyName, aliasMap, seen));
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
function typeAnnotationExcludesProperty(annotation, propertyName, aliasMap) {
|
|
120
|
+
if (!annotation)
|
|
121
|
+
return false;
|
|
122
|
+
return typeNodeExcludesProperty(annotation.typeAnnotation, propertyName, aliasMap);
|
|
123
|
+
}
|
|
124
|
+
function collectRestBindingsFromPattern(pattern, ctx, annotation, aliasMap, sourceChildrenSourceId) {
|
|
125
|
+
const childrenPresent = patternHasChildrenProperty(pattern);
|
|
126
|
+
for (const prop of pattern.properties) {
|
|
127
|
+
if (prop.type === utils_1.AST_NODE_TYPES.RestElement &&
|
|
128
|
+
prop.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
129
|
+
ctx.propsLikeIdentifiers.add(prop.argument.name);
|
|
130
|
+
ctx.bindings.set(prop.argument.name, {
|
|
131
|
+
identifier: prop.argument,
|
|
132
|
+
childrenExcluded: childrenPresent,
|
|
133
|
+
typeAnnotationExcludesProperty: typeAnnotationExcludesProperty(annotation, 'children', aliasMap),
|
|
134
|
+
childrenSourceId: sourceChildrenSourceId ?? prop.argument.name,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
else if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
138
|
+
prop.value.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
139
|
+
collectRestBindingsFromPattern(prop.value, ctx, null, aliasMap);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function recordChildrenValueBindingsFromPattern(pattern, ctx, sourceChildrenSourceId) {
|
|
144
|
+
for (const prop of pattern.properties) {
|
|
145
|
+
if (prop.type !== utils_1.AST_NODE_TYPES.Property)
|
|
146
|
+
continue;
|
|
147
|
+
if (prop.computed)
|
|
148
|
+
continue;
|
|
149
|
+
const key = prop.key;
|
|
150
|
+
const keyName = key.type === utils_1.AST_NODE_TYPES.Identifier
|
|
151
|
+
? key.name
|
|
152
|
+
: key.type === utils_1.AST_NODE_TYPES.Literal && typeof key.value === 'string'
|
|
153
|
+
? key.value
|
|
154
|
+
: null;
|
|
155
|
+
if (keyName !== 'children')
|
|
156
|
+
continue;
|
|
157
|
+
const value = prop.value;
|
|
158
|
+
if (value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
159
|
+
ctx.childrenValueSourceIds.set(value.name, sourceChildrenSourceId);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (value.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
|
|
163
|
+
value.left.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
164
|
+
ctx.childrenValueSourceIds.set(value.left.name, sourceChildrenSourceId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function recordParamBindings(param, ctx, aliasMap) {
|
|
169
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
170
|
+
ctx.propsLikeIdentifiers.add(param.name);
|
|
171
|
+
ctx.bindings.set(param.name, {
|
|
172
|
+
identifier: param,
|
|
173
|
+
childrenExcluded: false,
|
|
174
|
+
typeAnnotationExcludesProperty: typeAnnotationExcludesProperty(param.typeAnnotation, 'children', aliasMap),
|
|
175
|
+
childrenSourceId: param.name,
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
|
|
180
|
+
param.left.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
181
|
+
ctx.propsLikeIdentifiers.add(param.left.name);
|
|
182
|
+
ctx.bindings.set(param.left.name, {
|
|
183
|
+
identifier: param.left,
|
|
184
|
+
childrenExcluded: false,
|
|
185
|
+
typeAnnotationExcludesProperty: typeAnnotationExcludesProperty(param.typeAnnotation, 'children', aliasMap),
|
|
186
|
+
childrenSourceId: param.left.name,
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
|
|
191
|
+
param.left.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
192
|
+
collectRestBindingsFromPattern(param.left, ctx, param.typeAnnotation, aliasMap);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
196
|
+
collectRestBindingsFromPattern(param, ctx, param.typeAnnotation, aliasMap);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function findNearestComponentContext(stack) {
|
|
200
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
201
|
+
if (stack[i].isComponent)
|
|
202
|
+
return stack[i];
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
function findBinding(name, stack) {
|
|
207
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
208
|
+
const binding = stack[i].bindings.get(name);
|
|
209
|
+
if (binding)
|
|
210
|
+
return binding;
|
|
211
|
+
}
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
function findChildrenValueSourceId(name, stack) {
|
|
215
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
216
|
+
const sourceId = stack[i].childrenValueSourceIds.get(name);
|
|
217
|
+
if (sourceId)
|
|
218
|
+
return sourceId;
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
function isPropsLike(name, stack) {
|
|
223
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
224
|
+
if (stack[i].propsLikeIdentifiers.has(name))
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
function typeHasChildrenProperty(checker, type) {
|
|
230
|
+
if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
const apparent = checker.getApparentType(type);
|
|
234
|
+
const directProp = type.getProperty?.('children') ??
|
|
235
|
+
checker.getPropertyOfType(type, 'children') ??
|
|
236
|
+
checker.getPropertyOfType(apparent, 'children');
|
|
237
|
+
if (directProp) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (type.isUnion?.()) {
|
|
241
|
+
let sawUnknown = false;
|
|
242
|
+
for (const member of type.types) {
|
|
243
|
+
const result = typeHasChildrenProperty(checker, member);
|
|
244
|
+
if (result)
|
|
245
|
+
return true;
|
|
246
|
+
if (result === null)
|
|
247
|
+
sawUnknown = true;
|
|
248
|
+
}
|
|
249
|
+
return sawUnknown ? null : false;
|
|
250
|
+
}
|
|
251
|
+
if (type.isIntersection?.()) {
|
|
252
|
+
let sawUnknown = false;
|
|
253
|
+
for (const member of type.types) {
|
|
254
|
+
const result = typeHasChildrenProperty(checker, member);
|
|
255
|
+
if (result)
|
|
256
|
+
return true;
|
|
257
|
+
if (result === null)
|
|
258
|
+
sawUnknown = true;
|
|
259
|
+
}
|
|
260
|
+
return sawUnknown ? null : false;
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
function bindingMayContainChildren(binding, context) {
|
|
265
|
+
if (binding.childrenExcluded)
|
|
266
|
+
return false;
|
|
267
|
+
if (binding.typeAnnotationExcludesProperty)
|
|
268
|
+
return false;
|
|
269
|
+
const services = context.sourceCode?.parserServices ?? context.parserServices;
|
|
270
|
+
if (!services?.program || !services?.esTreeNodeToTSNodeMap) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const checker = services.program.getTypeChecker();
|
|
275
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(binding.identifier);
|
|
276
|
+
if (!tsNode)
|
|
277
|
+
return true;
|
|
278
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
279
|
+
const hasChildren = typeHasChildrenProperty(checker, type);
|
|
280
|
+
if (hasChildren === false)
|
|
281
|
+
return false;
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function hasExplicitChildren(element) {
|
|
289
|
+
if (element.openingElement.selfClosing)
|
|
290
|
+
return false;
|
|
291
|
+
return element.children.some((child) => {
|
|
292
|
+
if (child.type === utils_1.AST_NODE_TYPES.JSXText) {
|
|
293
|
+
return child.value.trim().length > 0;
|
|
294
|
+
}
|
|
295
|
+
if (child.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
296
|
+
return child.expression.type !== utils_1.AST_NODE_TYPES.JSXEmptyExpression;
|
|
297
|
+
}
|
|
298
|
+
return true; // JSXElement, JSXFragment, JSXSpreadChild, etc.
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
function nodeReferencesChildren(node, propsObjectNames, childrenValueNames) {
|
|
302
|
+
const stack = [node];
|
|
303
|
+
while (stack.length) {
|
|
304
|
+
const current = stack.pop();
|
|
305
|
+
if (current.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
306
|
+
if (childrenValueNames.has(current.name))
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
else if (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
310
|
+
if (!current.computed &&
|
|
311
|
+
current.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
312
|
+
current.property.name === 'children' &&
|
|
313
|
+
current.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
314
|
+
propsObjectNames.has(current.object.name)) {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else if (current.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
319
|
+
stack.push(current.expression);
|
|
320
|
+
}
|
|
321
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
322
|
+
for (const key of Object.keys(current)) {
|
|
323
|
+
if (key === 'parent')
|
|
324
|
+
continue;
|
|
325
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
326
|
+
const value = current[key];
|
|
327
|
+
if (!value)
|
|
328
|
+
continue;
|
|
329
|
+
if (Array.isArray(value)) {
|
|
330
|
+
for (const child of value) {
|
|
331
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
332
|
+
stack.push(child);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else if (typeof value === 'object' && 'type' in value) {
|
|
337
|
+
stack.push(value);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
function childrenRenderSpreadChildren(children, propsObjectNames, childrenValueNames) {
|
|
344
|
+
for (const child of children) {
|
|
345
|
+
if (child.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
346
|
+
if (nodeReferencesChildren(child.expression, propsObjectNames, childrenValueNames)) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
else if (child.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
351
|
+
child.type === utils_1.AST_NODE_TYPES.JSXFragment ||
|
|
352
|
+
child.type === utils_1.AST_NODE_TYPES.JSXSpreadChild) {
|
|
353
|
+
if (nodeReferencesChildren(child, propsObjectNames, childrenValueNames)) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else if (child.type === utils_1.AST_NODE_TYPES.JSXText) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
function collectPropsObjectNamesForChildrenSourceIds(sourceIds, stack) {
|
|
364
|
+
const names = new Set();
|
|
365
|
+
for (const ctx of stack) {
|
|
366
|
+
for (const [name, binding] of ctx.bindings) {
|
|
367
|
+
if (sourceIds.has(binding.childrenSourceId)) {
|
|
368
|
+
names.add(name);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return names;
|
|
373
|
+
}
|
|
374
|
+
function collectChildrenValueNamesForChildrenSourceIds(sourceIds, stack) {
|
|
375
|
+
const names = new Set();
|
|
376
|
+
for (const ctx of stack) {
|
|
377
|
+
for (const [name, sourceId] of ctx.childrenValueSourceIds) {
|
|
378
|
+
if (sourceIds.has(sourceId)) {
|
|
379
|
+
names.add(name);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return names;
|
|
384
|
+
}
|
|
385
|
+
exports.preventChildrenClobber = (0, createRule_1.createRule)({
|
|
386
|
+
name: 'prevent-children-clobber',
|
|
387
|
+
meta: {
|
|
388
|
+
type: 'problem',
|
|
389
|
+
docs: {
|
|
390
|
+
description: 'Prevent JSX spreads from silently discarding props.children',
|
|
391
|
+
recommended: 'error',
|
|
392
|
+
requiresTypeChecking: false,
|
|
393
|
+
},
|
|
394
|
+
schema: [],
|
|
395
|
+
messages: {
|
|
396
|
+
childrenClobbered: "Children clobber detected: JSX spreads {{spreadNames}} which may already contain children, but the element also declares its own children. The spread children are discarded. Destructure and render children explicitly (e.g., `{ children, ...rest }` and include `{children}`) or add `'children'` to an `Omit<>` if this component should not accept children.",
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
defaultOptions: [],
|
|
400
|
+
create(context) {
|
|
401
|
+
const sourceCode = context.sourceCode ??
|
|
402
|
+
context.getSourceCode();
|
|
403
|
+
const aliasMap = new Map();
|
|
404
|
+
for (const node of sourceCode.ast.body) {
|
|
405
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
406
|
+
aliasMap.set(node.id.name, node.typeAnnotation);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const functionStack = [];
|
|
410
|
+
return {
|
|
411
|
+
':function'(node) {
|
|
412
|
+
const ctx = {
|
|
413
|
+
isComponent: isComponentLike(node),
|
|
414
|
+
bindings: new Map(),
|
|
415
|
+
propsLikeIdentifiers: new Set(),
|
|
416
|
+
childrenValueSourceIds: new Map(),
|
|
417
|
+
};
|
|
418
|
+
if (ctx.isComponent) {
|
|
419
|
+
for (const param of node.params) {
|
|
420
|
+
recordParamBindings(param, ctx, aliasMap);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
functionStack.push(ctx);
|
|
424
|
+
},
|
|
425
|
+
':function:exit'() {
|
|
426
|
+
functionStack.pop();
|
|
427
|
+
},
|
|
428
|
+
VariableDeclarator(node) {
|
|
429
|
+
const componentCtx = findNearestComponentContext(functionStack);
|
|
430
|
+
if (!componentCtx)
|
|
431
|
+
return;
|
|
432
|
+
const id = node.id;
|
|
433
|
+
const init = node.init;
|
|
434
|
+
if (id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
435
|
+
init?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
436
|
+
const sourceBinding = findBinding(init.name, functionStack);
|
|
437
|
+
const typeExcludes = typeAnnotationExcludesProperty(id.typeAnnotation, 'children', aliasMap);
|
|
438
|
+
if (sourceBinding) {
|
|
439
|
+
componentCtx.bindings.set(id.name, {
|
|
440
|
+
identifier: id,
|
|
441
|
+
childrenExcluded: sourceBinding.childrenExcluded,
|
|
442
|
+
typeAnnotationExcludesProperty: sourceBinding.typeAnnotationExcludesProperty || typeExcludes,
|
|
443
|
+
childrenSourceId: sourceBinding.childrenSourceId,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
else if (isPropsLike(init.name, functionStack)) {
|
|
447
|
+
const propsLikeBinding = findBinding(init.name, functionStack);
|
|
448
|
+
componentCtx.bindings.set(id.name, {
|
|
449
|
+
identifier: id,
|
|
450
|
+
childrenExcluded: false,
|
|
451
|
+
typeAnnotationExcludesProperty: typeExcludes,
|
|
452
|
+
childrenSourceId: propsLikeBinding?.childrenSourceId ?? init.name,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if (isPropsLike(init.name, functionStack)) {
|
|
456
|
+
componentCtx.propsLikeIdentifiers.add(id.name);
|
|
457
|
+
}
|
|
458
|
+
const childSourceId = findChildrenValueSourceId(init.name, functionStack);
|
|
459
|
+
if (childSourceId) {
|
|
460
|
+
componentCtx.childrenValueSourceIds.set(id.name, childSourceId);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
else if (id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
464
|
+
init &&
|
|
465
|
+
(init.type === utils_1.AST_NODE_TYPES.MemberExpression ||
|
|
466
|
+
init.type === utils_1.AST_NODE_TYPES.ChainExpression)) {
|
|
467
|
+
const member = init.type === utils_1.AST_NODE_TYPES.ChainExpression
|
|
468
|
+
? init.expression
|
|
469
|
+
: init;
|
|
470
|
+
if (member.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
471
|
+
!member.computed &&
|
|
472
|
+
member.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
473
|
+
member.property.name === 'children' &&
|
|
474
|
+
member.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
475
|
+
const sourceBinding = findBinding(member.object.name, functionStack);
|
|
476
|
+
if (sourceBinding) {
|
|
477
|
+
componentCtx.childrenValueSourceIds.set(id.name, sourceBinding.childrenSourceId);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
else if (id.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
482
|
+
init?.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
483
|
+
isPropsLike(init.name, functionStack)) {
|
|
484
|
+
const initBinding = findBinding(init.name, functionStack);
|
|
485
|
+
const sourceChildrenSourceId = initBinding?.childrenSourceId ?? init.name;
|
|
486
|
+
recordChildrenValueBindingsFromPattern(id, componentCtx, sourceChildrenSourceId);
|
|
487
|
+
collectRestBindingsFromPattern(id, componentCtx, id.typeAnnotation ?? null, aliasMap, sourceChildrenSourceId);
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
JSXElement(node) {
|
|
491
|
+
const componentCtx = findNearestComponentContext(functionStack);
|
|
492
|
+
if (!componentCtx)
|
|
493
|
+
return;
|
|
494
|
+
if (!hasExplicitChildren(node))
|
|
495
|
+
return;
|
|
496
|
+
const spreadNamesInOrder = [];
|
|
497
|
+
for (const attr of node.openingElement.attributes) {
|
|
498
|
+
if (attr.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute &&
|
|
499
|
+
attr.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
500
|
+
spreadNamesInOrder.push(attr.argument.name);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (spreadNamesInOrder.length === 0)
|
|
504
|
+
return;
|
|
505
|
+
const offendingSpreads = [];
|
|
506
|
+
for (const name of spreadNamesInOrder) {
|
|
507
|
+
const binding = findBinding(name, functionStack);
|
|
508
|
+
if (!binding)
|
|
509
|
+
continue;
|
|
510
|
+
if (!bindingMayContainChildren(binding, context)) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
offendingSpreads.push({ name, childrenSourceId: binding.childrenSourceId });
|
|
514
|
+
}
|
|
515
|
+
if (offendingSpreads.length === 0)
|
|
516
|
+
return;
|
|
517
|
+
const lastOffendingChildrenSourceId = offendingSpreads[offendingSpreads.length - 1].childrenSourceId;
|
|
518
|
+
const lastSourceIds = new Set([lastOffendingChildrenSourceId]);
|
|
519
|
+
const propsObjectNames = collectPropsObjectNamesForChildrenSourceIds(lastSourceIds, functionStack);
|
|
520
|
+
const childrenValueNames = collectChildrenValueNamesForChildrenSourceIds(lastSourceIds, functionStack);
|
|
521
|
+
if (childrenRenderSpreadChildren(node.children, propsObjectNames, childrenValueNames)) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const clobberedNames = Array.from(new Set(offendingSpreads
|
|
525
|
+
.filter((spread) => spread.childrenSourceId === lastOffendingChildrenSourceId)
|
|
526
|
+
.map((spread) => spread.name)));
|
|
527
|
+
context.report({
|
|
528
|
+
node: node.openingElement,
|
|
529
|
+
messageId: 'childrenClobbered',
|
|
530
|
+
data: { spreadNames: clobberedNames.join(', ') },
|
|
531
|
+
});
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
//# sourceMappingURL=prevent-children-clobber.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type MessageIds = 'componentLiteral' | 'nestedHookLiteral' | 'hookReturnLiteral' | 'memoizeLiteralSuggestion';
|
|
3
|
+
export declare const reactMemoizeLiterals: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
|
|
4
|
+
export {};
|