@blumintinc/eslint-plugin-blumint 1.13.0 → 1.15.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 +169 -139
- package/lib/index.js +200 -9
- package/lib/rules/array-methods-this-context.js +1 -1
- package/lib/rules/avoid-utils-directory.js +0 -4
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +43 -4
- package/lib/rules/dynamic-https-errors.d.ts +1 -1
- package/lib/rules/dynamic-https-errors.js +134 -44
- package/lib/rules/enforce-assert-safe-object-key.js +11 -11
- package/lib/rules/enforce-boolean-naming-prefixes.js +11 -235
- 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-date-ttime.d.ts +1 -0
- package/lib/rules/enforce-date-ttime.js +156 -0
- 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-dynamic-firebase-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
- 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-f-extension-for-entry-points.d.ts +8 -0
- package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
- 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 +4 -4
- package/lib/rules/enforce-id-capitalization.js +1 -1
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.js +71 -21
- 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 +44 -18
- 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 +3818 -4644
- 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 +66 -23
- 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 +801 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +415 -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 +215 -12
- package/lib/rules/no-circular-references.d.ts +2 -1
- package/lib/rules/no-circular-references.js +31 -32
- 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 +527 -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 +52 -7
- package/lib/rules/no-excessive-parent-chain.js +4 -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 +465 -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.d.ts +2 -2
- package/lib/rules/no-passthrough-getters.js +120 -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 +508 -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 +344 -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 +143 -59
- 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 +118 -8
- 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 +221 -172
- package/lib/rules/prefer-settings-object.js +27 -10
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
- 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-usememo-over-useeffect-usestate.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 +540 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +614 -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 +155 -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 +18 -10
- 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 +47 -2
- package/lib/utils/ASTHelpers.js +370 -112
- 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
|
@@ -106,7 +106,7 @@ function isNonPrimitiveWithTypes(context, expr) {
|
|
|
106
106
|
}
|
|
107
107
|
function collectMemoizedIdentifiers(context) {
|
|
108
108
|
const memoized = new Set();
|
|
109
|
-
const sourceCode = context.
|
|
109
|
+
const sourceCode = context.sourceCode;
|
|
110
110
|
const program = sourceCode.ast;
|
|
111
111
|
function visit(node) {
|
|
112
112
|
if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
@@ -156,7 +156,7 @@ function collectMemoizedIdentifiers(context) {
|
|
|
156
156
|
}
|
|
157
157
|
function ensureDeepCompareImportFixes(context, fixer) {
|
|
158
158
|
const fixes = [];
|
|
159
|
-
const sourceCode = context.
|
|
159
|
+
const sourceCode = context.sourceCode;
|
|
160
160
|
const program = sourceCode.ast;
|
|
161
161
|
// If already imported anywhere, skip adding
|
|
162
162
|
const hasImport = program.body.some((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
@@ -227,7 +227,7 @@ function removeImportSpecifierFixes(sourceCode, fixer, importDecl, specifier) {
|
|
|
227
227
|
return fixes;
|
|
228
228
|
}
|
|
229
229
|
function isImportedIdentifier(context, name) {
|
|
230
|
-
const sourceCode = context.
|
|
230
|
+
const sourceCode = context.sourceCode;
|
|
231
231
|
const program = sourceCode.ast;
|
|
232
232
|
for (const node of program.body) {
|
|
233
233
|
if (node.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
|
|
@@ -319,7 +319,7 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
|
|
|
319
319
|
fixable: 'code',
|
|
320
320
|
schema: [],
|
|
321
321
|
messages: {
|
|
322
|
-
preferUseDeepCompareMemo: 'Use useDeepCompareMemo
|
|
322
|
+
preferUseDeepCompareMemo: 'Dependency array for "{{hook}}" includes objects/arrays/functions that change identity each render, so React treats them as changed and reruns the memoized computation, triggering avoidable renders. Use useDeepCompareMemo (or memoize those dependencies first) so comparisons use deep equality and the memo stays stable.',
|
|
323
323
|
},
|
|
324
324
|
},
|
|
325
325
|
defaultOptions: [],
|
|
@@ -386,6 +386,9 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
|
|
|
386
386
|
context.report({
|
|
387
387
|
node,
|
|
388
388
|
messageId: 'preferUseDeepCompareMemo',
|
|
389
|
+
data: {
|
|
390
|
+
hook: 'useMemo',
|
|
391
|
+
},
|
|
389
392
|
fix(fixer) {
|
|
390
393
|
const fixes = [];
|
|
391
394
|
// Replace callee
|
|
@@ -398,7 +401,7 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
|
|
|
398
401
|
// Ensure import exists
|
|
399
402
|
fixes.push(...ensureDeepCompareImportFixes(context, fixer));
|
|
400
403
|
// Clean up now-unused React/useMemo imports if safe
|
|
401
|
-
const sourceCode = context.
|
|
404
|
+
const sourceCode = context.sourceCode;
|
|
402
405
|
const program = sourceCode.ast;
|
|
403
406
|
const reactImport = program.body.find((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
404
407
|
n.source.value === 'react');
|
|
@@ -166,7 +166,7 @@ function getMemoizedFunctionDescription(node) {
|
|
|
166
166
|
return 'this callback value';
|
|
167
167
|
}
|
|
168
168
|
function reportAndFix(node, context) {
|
|
169
|
-
const sourceCode = context.
|
|
169
|
+
const sourceCode = context.sourceCode;
|
|
170
170
|
const useMemoCallback = node.arguments[0];
|
|
171
171
|
const dependencyArray = node.arguments[1]
|
|
172
172
|
? sourceCode.getText(node.arguments[1])
|
|
@@ -11,7 +11,7 @@ exports.preferUseMemoOverUseEffectUseState = (0, createRule_1.createRule)({
|
|
|
11
11
|
recommended: 'error',
|
|
12
12
|
},
|
|
13
13
|
messages: {
|
|
14
|
-
preferUseMemo: '
|
|
14
|
+
preferUseMemo: 'Use useMemo to compute derived state "{{stateName}}" instead of useEffect + useState to avoid extra render cycles and stale snapshots.',
|
|
15
15
|
},
|
|
16
16
|
schema: [],
|
|
17
17
|
},
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.preferUtilityFunctionOverPrivateStatic = void 0;
|
|
4
4
|
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const getMethodName_1 = require("../utils/getMethodName");
|
|
5
6
|
exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
|
|
6
7
|
name: 'prefer-utility-function-over-private-static',
|
|
7
8
|
meta: {
|
|
@@ -12,7 +13,7 @@ exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
|
|
|
12
13
|
},
|
|
13
14
|
schema: [],
|
|
14
15
|
messages: {
|
|
15
|
-
preferUtilityFunctionOverPrivateStatic: 'Private static
|
|
16
|
+
preferUtilityFunctionOverPrivateStatic: 'Private static method "{{methodName}}" in class "{{className}}" does not use class state. Keeping class-agnostic helpers as private statics hides reusable logic and signals coupling that is not present. Extract this logic into a standalone utility function (module-level or shared) and import it where needed so it can be reused and unit tested independently.',
|
|
16
17
|
},
|
|
17
18
|
},
|
|
18
19
|
defaultOptions: [],
|
|
@@ -47,9 +48,32 @@ exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
|
|
|
47
48
|
}
|
|
48
49
|
return false;
|
|
49
50
|
};
|
|
51
|
+
const getClassName = (method) => {
|
|
52
|
+
const classNode = method.parent?.parent;
|
|
53
|
+
if (classNode &&
|
|
54
|
+
(classNode.type === 'ClassDeclaration' ||
|
|
55
|
+
classNode.type === 'ClassExpression')) {
|
|
56
|
+
if (classNode.id && classNode.id.type === 'Identifier') {
|
|
57
|
+
return classNode.id.name;
|
|
58
|
+
}
|
|
59
|
+
if (classNode.type === 'ClassExpression' &&
|
|
60
|
+
classNode.parent &&
|
|
61
|
+
classNode.parent.type === 'VariableDeclarator' &&
|
|
62
|
+
classNode.parent.id.type === 'Identifier') {
|
|
63
|
+
return classNode.parent.id.name;
|
|
64
|
+
}
|
|
65
|
+
if (classNode.type === 'ClassExpression' &&
|
|
66
|
+
classNode.parent &&
|
|
67
|
+
classNode.parent.type === 'AssignmentExpression' &&
|
|
68
|
+
classNode.parent.left.type === 'Identifier') {
|
|
69
|
+
return classNode.parent.left.name;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return 'this class';
|
|
73
|
+
};
|
|
50
74
|
return {
|
|
51
75
|
'MethodDefinition[static=true][accessibility="private"]'(node) {
|
|
52
|
-
const sourceCode = context.
|
|
76
|
+
const sourceCode = context.sourceCode;
|
|
53
77
|
const methodBody = node.value.body;
|
|
54
78
|
if (!methodBody) {
|
|
55
79
|
return;
|
|
@@ -65,9 +89,17 @@ exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
|
|
|
65
89
|
const usesThis = hasThisReference(methodBody);
|
|
66
90
|
// If the method doesn't use 'this', it's a good candidate for extraction
|
|
67
91
|
if (!usesThis) {
|
|
92
|
+
const methodName = (0, getMethodName_1.getMethodName)(node, sourceCode, {
|
|
93
|
+
computedFallbackToText: false,
|
|
94
|
+
}) || '<unknown>';
|
|
95
|
+
const className = getClassName(node);
|
|
68
96
|
context.report({
|
|
69
97
|
node,
|
|
70
98
|
messageId: 'preferUtilityFunctionOverPrivateStatic',
|
|
99
|
+
data: {
|
|
100
|
+
methodName,
|
|
101
|
+
className,
|
|
102
|
+
},
|
|
71
103
|
});
|
|
72
104
|
}
|
|
73
105
|
},
|
|
@@ -0,0 +1,540 @@
|
|
|
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, context) {
|
|
48
|
+
const name = resolveFunctionName(node);
|
|
49
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
|
|
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 ??
|
|
270
|
+
context.parserServices;
|
|
271
|
+
if (!services?.program || !services?.esTreeNodeToTSNodeMap) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const checker = services.program.getTypeChecker();
|
|
276
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(binding.identifier);
|
|
277
|
+
if (!tsNode)
|
|
278
|
+
return true;
|
|
279
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
280
|
+
const hasChildren = typeHasChildrenProperty(checker, type);
|
|
281
|
+
if (hasChildren === false)
|
|
282
|
+
return false;
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function hasExplicitChildren(element) {
|
|
290
|
+
if (element.openingElement.selfClosing)
|
|
291
|
+
return false;
|
|
292
|
+
return element.children.some((child) => {
|
|
293
|
+
if (child.type === utils_1.AST_NODE_TYPES.JSXText) {
|
|
294
|
+
return child.value.trim().length > 0;
|
|
295
|
+
}
|
|
296
|
+
if (child.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
297
|
+
return child.expression.type !== utils_1.AST_NODE_TYPES.JSXEmptyExpression;
|
|
298
|
+
}
|
|
299
|
+
return true; // JSXElement, JSXFragment, JSXSpreadChild, etc.
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function nodeReferencesChildren(node, propsObjectNames, childrenValueNames) {
|
|
303
|
+
const stack = [node];
|
|
304
|
+
while (stack.length) {
|
|
305
|
+
const current = stack.pop();
|
|
306
|
+
if (current.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
307
|
+
if (childrenValueNames.has(current.name))
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
else if (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
311
|
+
if (!current.computed &&
|
|
312
|
+
current.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
313
|
+
current.property.name === 'children' &&
|
|
314
|
+
current.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
315
|
+
propsObjectNames.has(current.object.name)) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (current.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
320
|
+
stack.push(current.expression);
|
|
321
|
+
}
|
|
322
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
323
|
+
for (const key of Object.keys(current)) {
|
|
324
|
+
if (key === 'parent')
|
|
325
|
+
continue;
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
327
|
+
const value = current[key];
|
|
328
|
+
if (!value)
|
|
329
|
+
continue;
|
|
330
|
+
if (Array.isArray(value)) {
|
|
331
|
+
for (const child of value) {
|
|
332
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
333
|
+
stack.push(child);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else if (typeof value === 'object' && 'type' in value) {
|
|
338
|
+
stack.push(value);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
function childrenRenderSpreadChildren(children, propsObjectNames, childrenValueNames) {
|
|
345
|
+
for (const child of children) {
|
|
346
|
+
if (child.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
347
|
+
if (nodeReferencesChildren(child.expression, propsObjectNames, childrenValueNames)) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else if (child.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
352
|
+
child.type === utils_1.AST_NODE_TYPES.JSXFragment ||
|
|
353
|
+
child.type === utils_1.AST_NODE_TYPES.JSXSpreadChild) {
|
|
354
|
+
if (nodeReferencesChildren(child, propsObjectNames, childrenValueNames)) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else if (child.type === utils_1.AST_NODE_TYPES.JSXText) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
function collectPropsObjectNamesForChildrenSourceIds(sourceIds, stack) {
|
|
365
|
+
const names = new Set();
|
|
366
|
+
for (const ctx of stack) {
|
|
367
|
+
for (const [name, binding] of ctx.bindings) {
|
|
368
|
+
if (sourceIds.has(binding.childrenSourceId)) {
|
|
369
|
+
names.add(name);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return names;
|
|
374
|
+
}
|
|
375
|
+
function collectChildrenValueNamesForChildrenSourceIds(sourceIds, stack) {
|
|
376
|
+
const names = new Set();
|
|
377
|
+
for (const ctx of stack) {
|
|
378
|
+
for (const [name, sourceId] of ctx.childrenValueSourceIds) {
|
|
379
|
+
if (sourceIds.has(sourceId)) {
|
|
380
|
+
names.add(name);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return names;
|
|
385
|
+
}
|
|
386
|
+
exports.preventChildrenClobber = (0, createRule_1.createRule)({
|
|
387
|
+
name: 'prevent-children-clobber',
|
|
388
|
+
meta: {
|
|
389
|
+
type: 'problem',
|
|
390
|
+
docs: {
|
|
391
|
+
description: 'Prevent JSX spreads from silently discarding props.children',
|
|
392
|
+
recommended: 'error',
|
|
393
|
+
requiresTypeChecking: false,
|
|
394
|
+
},
|
|
395
|
+
schema: [],
|
|
396
|
+
messages: {
|
|
397
|
+
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.",
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
defaultOptions: [],
|
|
401
|
+
create(context) {
|
|
402
|
+
const sourceCode = context.sourceCode ??
|
|
403
|
+
context.getSourceCode();
|
|
404
|
+
const aliasMap = new Map();
|
|
405
|
+
for (const node of sourceCode.ast.body) {
|
|
406
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
407
|
+
aliasMap.set(node.id.name, node.typeAnnotation);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const functionStack = [];
|
|
411
|
+
return {
|
|
412
|
+
':function'(node) {
|
|
413
|
+
const ctx = {
|
|
414
|
+
isComponent: isComponentLike(node, context),
|
|
415
|
+
bindings: new Map(),
|
|
416
|
+
propsLikeIdentifiers: new Set(),
|
|
417
|
+
childrenValueSourceIds: new Map(),
|
|
418
|
+
};
|
|
419
|
+
if (ctx.isComponent) {
|
|
420
|
+
for (const param of node.params) {
|
|
421
|
+
recordParamBindings(param, ctx, aliasMap);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
functionStack.push(ctx);
|
|
425
|
+
},
|
|
426
|
+
':function:exit'() {
|
|
427
|
+
functionStack.pop();
|
|
428
|
+
},
|
|
429
|
+
VariableDeclarator(node) {
|
|
430
|
+
const componentCtx = findNearestComponentContext(functionStack);
|
|
431
|
+
if (!componentCtx)
|
|
432
|
+
return;
|
|
433
|
+
const id = node.id;
|
|
434
|
+
const init = node.init;
|
|
435
|
+
if (id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
436
|
+
init?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
437
|
+
const sourceBinding = findBinding(init.name, functionStack);
|
|
438
|
+
const typeExcludes = typeAnnotationExcludesProperty(id.typeAnnotation, 'children', aliasMap);
|
|
439
|
+
if (sourceBinding) {
|
|
440
|
+
componentCtx.bindings.set(id.name, {
|
|
441
|
+
identifier: id,
|
|
442
|
+
childrenExcluded: sourceBinding.childrenExcluded,
|
|
443
|
+
typeAnnotationExcludesProperty: sourceBinding.typeAnnotationExcludesProperty || typeExcludes,
|
|
444
|
+
childrenSourceId: sourceBinding.childrenSourceId,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
else if (isPropsLike(init.name, functionStack)) {
|
|
448
|
+
const propsLikeBinding = findBinding(init.name, functionStack);
|
|
449
|
+
componentCtx.bindings.set(id.name, {
|
|
450
|
+
identifier: id,
|
|
451
|
+
childrenExcluded: false,
|
|
452
|
+
typeAnnotationExcludesProperty: typeExcludes,
|
|
453
|
+
childrenSourceId: propsLikeBinding?.childrenSourceId ?? init.name,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (isPropsLike(init.name, functionStack)) {
|
|
457
|
+
componentCtx.propsLikeIdentifiers.add(id.name);
|
|
458
|
+
}
|
|
459
|
+
const childSourceId = findChildrenValueSourceId(init.name, functionStack);
|
|
460
|
+
if (childSourceId) {
|
|
461
|
+
componentCtx.childrenValueSourceIds.set(id.name, childSourceId);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
else if (id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
465
|
+
init &&
|
|
466
|
+
(init.type === utils_1.AST_NODE_TYPES.MemberExpression ||
|
|
467
|
+
init.type === utils_1.AST_NODE_TYPES.ChainExpression)) {
|
|
468
|
+
const member = init.type === utils_1.AST_NODE_TYPES.ChainExpression
|
|
469
|
+
? init.expression
|
|
470
|
+
: init;
|
|
471
|
+
if (member.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
472
|
+
!member.computed &&
|
|
473
|
+
member.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
474
|
+
member.property.name === 'children' &&
|
|
475
|
+
member.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
476
|
+
const sourceBinding = findBinding(member.object.name, functionStack);
|
|
477
|
+
if (sourceBinding) {
|
|
478
|
+
componentCtx.childrenValueSourceIds.set(id.name, sourceBinding.childrenSourceId);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else if (id.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
483
|
+
init?.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
484
|
+
isPropsLike(init.name, functionStack)) {
|
|
485
|
+
const initBinding = findBinding(init.name, functionStack);
|
|
486
|
+
const sourceChildrenSourceId = initBinding?.childrenSourceId ?? init.name;
|
|
487
|
+
recordChildrenValueBindingsFromPattern(id, componentCtx, sourceChildrenSourceId);
|
|
488
|
+
collectRestBindingsFromPattern(id, componentCtx, id.typeAnnotation ?? null, aliasMap, sourceChildrenSourceId);
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
JSXElement(node) {
|
|
492
|
+
const componentCtx = findNearestComponentContext(functionStack);
|
|
493
|
+
if (!componentCtx)
|
|
494
|
+
return;
|
|
495
|
+
if (!hasExplicitChildren(node))
|
|
496
|
+
return;
|
|
497
|
+
const spreadNamesInOrder = [];
|
|
498
|
+
for (const attr of node.openingElement.attributes) {
|
|
499
|
+
if (attr.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute &&
|
|
500
|
+
attr.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
501
|
+
spreadNamesInOrder.push(attr.argument.name);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (spreadNamesInOrder.length === 0)
|
|
505
|
+
return;
|
|
506
|
+
const offendingSpreads = [];
|
|
507
|
+
for (const name of spreadNamesInOrder) {
|
|
508
|
+
const binding = findBinding(name, functionStack);
|
|
509
|
+
if (!binding)
|
|
510
|
+
continue;
|
|
511
|
+
if (!bindingMayContainChildren(binding, context)) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
offendingSpreads.push({
|
|
515
|
+
name,
|
|
516
|
+
childrenSourceId: binding.childrenSourceId,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
if (offendingSpreads.length === 0)
|
|
520
|
+
return;
|
|
521
|
+
const lastOffendingChildrenSourceId = offendingSpreads[offendingSpreads.length - 1].childrenSourceId;
|
|
522
|
+
const lastSourceIds = new Set([lastOffendingChildrenSourceId]);
|
|
523
|
+
const propsObjectNames = collectPropsObjectNamesForChildrenSourceIds(lastSourceIds, functionStack);
|
|
524
|
+
const childrenValueNames = collectChildrenValueNamesForChildrenSourceIds(lastSourceIds, functionStack);
|
|
525
|
+
if (childrenRenderSpreadChildren(node.children, propsObjectNames, childrenValueNames)) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const clobberedNames = Array.from(new Set(offendingSpreads
|
|
529
|
+
.filter((spread) => spread.childrenSourceId === lastOffendingChildrenSourceId)
|
|
530
|
+
.map((spread) => spread.name)));
|
|
531
|
+
context.report({
|
|
532
|
+
node: node.openingElement,
|
|
533
|
+
messageId: 'childrenClobbered',
|
|
534
|
+
data: { spreadNames: clobberedNames.join(', ') },
|
|
535
|
+
});
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
//# sourceMappingURL=prevent-children-clobber.js.map
|