@blumintinc/eslint-plugin-blumint 1.16.2 → 1.17.1
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 +21 -0
- package/lib/index.js +64 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +30 -14
- package/lib/rules/enforce-cloud-function-id-length.d.ts +5 -0
- package/lib/rules/enforce-cloud-function-id-length.js +104 -0
- package/lib/rules/enforce-is-prefix-validators.d.ts +13 -0
- package/lib/rules/enforce-is-prefix-validators.js +304 -0
- package/lib/rules/enforce-m3-sentence-case.d.ts +12 -0
- package/lib/rules/enforce-m3-sentence-case.js +430 -0
- package/lib/rules/enforce-snapshot-state-narrowing.d.ts +10 -0
- package/lib/rules/enforce-snapshot-state-narrowing.js +281 -0
- package/lib/rules/enforce-types-directory-placement.d.ts +9 -0
- package/lib/rules/enforce-types-directory-placement.js +276 -0
- package/lib/rules/no-direct-function-state.d.ts +8 -0
- package/lib/rules/no-direct-function-state.js +285 -0
- package/lib/rules/no-fill-template-mutation.d.ts +1 -0
- package/lib/rules/no-fill-template-mutation.js +324 -0
- package/lib/rules/no-hungarian.js +18 -26
- package/lib/rules/no-portal-inside-tooltip.d.ts +10 -0
- package/lib/rules/no-portal-inside-tooltip.js +219 -0
- package/lib/rules/no-redundant-boolean-callback-props.d.ts +11 -0
- package/lib/rules/no-redundant-boolean-callback-props.js +355 -0
- package/lib/rules/no-satisfies-in-frontend-bundle.d.ts +8 -0
- package/lib/rules/no-satisfies-in-frontend-bundle.js +126 -0
- package/lib/rules/no-single-dismiss-dialog-button.d.ts +7 -0
- package/lib/rules/no-single-dismiss-dialog-button.js +127 -0
- package/lib/rules/no-stablehash-react-nodes.d.ts +1 -0
- package/lib/rules/no-stablehash-react-nodes.js +325 -0
- package/lib/rules/parallelize-loop-awaits.d.ts +8 -0
- package/lib/rules/parallelize-loop-awaits.js +582 -0
- package/lib/rules/prefer-flat-transform-each-keys.d.ts +1 -0
- package/lib/rules/prefer-flat-transform-each-keys.js +228 -0
- package/lib/rules/prefer-getter-over-parameterless-method.d.ts +1 -0
- package/lib/rules/prefer-getter-over-parameterless-method.js +44 -0
- package/lib/rules/prefer-spread-over-reassembly.d.ts +5 -0
- package/lib/rules/prefer-spread-over-reassembly.js +401 -0
- package/lib/rules/prefer-sx-prop-over-system-props.d.ts +9 -0
- package/lib/rules/prefer-sx-prop-over-system-props.js +401 -0
- package/lib/rules/prefer-use-base62-id.d.ts +8 -0
- package/lib/rules/prefer-use-base62-id.js +483 -0
- package/lib/rules/prefer-use-theme.d.ts +1 -0
- package/lib/rules/prefer-use-theme.js +206 -0
- package/lib/rules/prefer-utility-function-own-file.d.ts +9 -0
- package/lib/rules/prefer-utility-function-own-file.js +505 -0
- package/lib/rules/react-memoize-literals.js +106 -1
- package/lib/rules/require-props-composition.d.ts +10 -0
- package/lib/rules/require-props-composition.js +433 -0
- package/lib/rules/require-server-timestamp-for-firestore-dates.d.ts +9 -0
- package/lib/rules/require-server-timestamp-for-firestore-dates.js +313 -0
- package/package.json +1 -1
- package/release-manifest.json +212 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preferSpreadOverReassembly = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const DEFAULT_MIN_FIELDS = 2;
|
|
7
|
+
/**
|
|
8
|
+
* Collects all identifier references (not declarations) used anywhere in a
|
|
9
|
+
* subtree. Used to detect when a destructured binding is consumed for purposes
|
|
10
|
+
* other than being forwarded directly.
|
|
11
|
+
*/
|
|
12
|
+
function collectIdentifierUses(node, names) {
|
|
13
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
14
|
+
names.add(node.name);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (const key of Object.keys(node)) {
|
|
18
|
+
if (key === 'parent')
|
|
19
|
+
continue;
|
|
20
|
+
const value = node[key];
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
for (const child of value) {
|
|
23
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
24
|
+
collectIdentifierUses(child, names);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (value && typeof value === 'object' && 'type' in value) {
|
|
29
|
+
collectIdentifierUses(value, names);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns the names of the simple (non-renamed, no default, top-level)
|
|
35
|
+
* destructured properties in an ObjectPattern, or null if the pattern has any
|
|
36
|
+
* feature that makes spread replacement unsafe (rest elements, default values,
|
|
37
|
+
* renamed bindings, nested patterns, computed keys).
|
|
38
|
+
*/
|
|
39
|
+
function getSimpleDestructuredNames(pattern) {
|
|
40
|
+
const names = [];
|
|
41
|
+
for (const prop of pattern.properties) {
|
|
42
|
+
// Rest element — the developer explicitly chose to separate props.
|
|
43
|
+
if (prop.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (prop.type !== utils_1.AST_NODE_TYPES.Property) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
// Computed key — cannot determine the name statically.
|
|
50
|
+
if (prop.computed) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Renamed binding: { a: b } — would change semantics.
|
|
54
|
+
if (!prop.shorthand) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
// Default value: { a = 1 } — spread would bypass the default.
|
|
58
|
+
if (prop.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
// Nested destructuring: { a: { b } } — not top-level.
|
|
62
|
+
if (prop.value.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
|
|
63
|
+
prop.value.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (prop.value.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
names.push(prop.value.name);
|
|
70
|
+
}
|
|
71
|
+
return names;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* For a JSX element, returns the set of destructured names that are forwarded
|
|
75
|
+
* with identical key names (e.g. `hits={hits}`, `isLoading={isLoading}`).
|
|
76
|
+
* Returns null if:
|
|
77
|
+
* - Any destructured name is forwarded to more than one place (multiple targets).
|
|
78
|
+
* - Any destructured name is found in a conditional/computed spread expression.
|
|
79
|
+
*/
|
|
80
|
+
function collectJsxForwardedFields(openingElement, destructuredNames) {
|
|
81
|
+
const forwarded = [];
|
|
82
|
+
for (const attr of openingElement.attributes) {
|
|
83
|
+
// Spread attribute — check if any destructured name is used inside it.
|
|
84
|
+
if (attr.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute) {
|
|
85
|
+
const usedInSpread = new Set();
|
|
86
|
+
collectIdentifierUses(attr.argument, usedInSpread);
|
|
87
|
+
for (const name of destructuredNames) {
|
|
88
|
+
if (usedInSpread.has(name)) {
|
|
89
|
+
// The field is used in a conditional/computed spread — not safe.
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (attr.type !== utils_1.AST_NODE_TYPES.JSXAttribute)
|
|
96
|
+
continue;
|
|
97
|
+
const attrName = attr.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier ? attr.name.name : null;
|
|
98
|
+
if (!attrName)
|
|
99
|
+
continue;
|
|
100
|
+
if (!destructuredNames.has(attrName))
|
|
101
|
+
continue;
|
|
102
|
+
// The attribute name matches a destructured name — check it is forwarded identically.
|
|
103
|
+
if (attr.value === null) {
|
|
104
|
+
// Boolean shorthand `isLoading` — not an identical forward (no value node).
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (attr.value.type !== utils_1.AST_NODE_TYPES.JSXExpressionContainer)
|
|
108
|
+
continue;
|
|
109
|
+
const expr = attr.value.expression;
|
|
110
|
+
if (expr.type !== utils_1.AST_NODE_TYPES.Identifier || expr.name !== attrName) {
|
|
111
|
+
// Transformed or renamed forward — not eligible for spread.
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
forwarded.push({ name: attrName, node: attr });
|
|
115
|
+
}
|
|
116
|
+
return forwarded;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* For an object expression, returns the set of destructured names that are
|
|
120
|
+
* forwarded with identical (shorthand) key names.
|
|
121
|
+
*/
|
|
122
|
+
function collectObjectForwardedFields(objectExpression, destructuredNames) {
|
|
123
|
+
const forwarded = [];
|
|
124
|
+
for (const prop of objectExpression.properties) {
|
|
125
|
+
// Spread element in object — check if any destructured name is used.
|
|
126
|
+
if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
127
|
+
const usedInSpread = new Set();
|
|
128
|
+
collectIdentifierUses(prop.argument, usedInSpread);
|
|
129
|
+
for (const name of destructuredNames) {
|
|
130
|
+
if (usedInSpread.has(name)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (prop.type !== utils_1.AST_NODE_TYPES.Property)
|
|
137
|
+
continue;
|
|
138
|
+
// Only shorthand properties qualify as identical forwards: { a, b, c }.
|
|
139
|
+
if (!prop.shorthand)
|
|
140
|
+
continue;
|
|
141
|
+
if (prop.computed)
|
|
142
|
+
continue;
|
|
143
|
+
const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier ? prop.key.name : null;
|
|
144
|
+
if (!key || !destructuredNames.has(key))
|
|
145
|
+
continue;
|
|
146
|
+
forwarded.push({ name: key, node: prop });
|
|
147
|
+
}
|
|
148
|
+
return forwarded;
|
|
149
|
+
}
|
|
150
|
+
function getSingleTarget(fn) {
|
|
151
|
+
const body = fn.body;
|
|
152
|
+
// Concise arrow: `(props) => <X />`
|
|
153
|
+
if (body.type === utils_1.AST_NODE_TYPES.JSXElement) {
|
|
154
|
+
return {
|
|
155
|
+
kind: 'jsx',
|
|
156
|
+
openingElement: body.openingElement,
|
|
157
|
+
jsxElement: body,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Concise arrow returning object literal: `({ x, y }) => ({ x, y, extra: 1 })`
|
|
161
|
+
// The parser represents the parenthesized object as an ObjectExpression body.
|
|
162
|
+
if (body.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
163
|
+
return { kind: 'object', expression: body };
|
|
164
|
+
}
|
|
165
|
+
if (body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
// Require exactly one statement in the block, which must be a return.
|
|
169
|
+
if (body.body.length !== 1) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const stmt = body.body[0];
|
|
173
|
+
if (stmt.type !== utils_1.AST_NODE_TYPES.ReturnStatement || !stmt.argument) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const arg = stmt.argument;
|
|
177
|
+
if (arg.type === utils_1.AST_NODE_TYPES.JSXElement) {
|
|
178
|
+
return {
|
|
179
|
+
kind: 'jsx',
|
|
180
|
+
openingElement: arg.openingElement,
|
|
181
|
+
jsxElement: arg,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// Parenthesized JSX in arrow: `(props) => (<X />)` — arg may wrap with
|
|
185
|
+
// TSAsExpression or similar. We unwrap one level of TSAsExpression / TSSatisfiesExpression.
|
|
186
|
+
if ((arg.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
187
|
+
arg.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression) &&
|
|
188
|
+
arg.expression.type === utils_1.AST_NODE_TYPES.JSXElement) {
|
|
189
|
+
const jsx = arg.expression;
|
|
190
|
+
return {
|
|
191
|
+
kind: 'jsx',
|
|
192
|
+
openingElement: jsx.openingElement,
|
|
193
|
+
jsxElement: jsx,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (arg.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
197
|
+
return { kind: 'object', expression: arg };
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Checks whether any of the destructured names is used anywhere in the
|
|
203
|
+
* function body OUTSIDE of the forwarded attributes we already identified.
|
|
204
|
+
* If a name is used elsewhere (conditional logic, side effects, other
|
|
205
|
+
* expressions), we must not flag.
|
|
206
|
+
*/
|
|
207
|
+
function isNameUsedOutsideForwarding(fnBody, name, forwardedNodes) {
|
|
208
|
+
// Walk the entire function body AST, skip the forwarded nodes.
|
|
209
|
+
const stack = [fnBody];
|
|
210
|
+
while (stack.length) {
|
|
211
|
+
const current = stack.pop();
|
|
212
|
+
if (!current)
|
|
213
|
+
continue;
|
|
214
|
+
if (forwardedNodes.has(current)) {
|
|
215
|
+
// Skip the subtree of a forwarded attribute/property — usage there is
|
|
216
|
+
// accounted for by the forwarding.
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (current.type === utils_1.AST_NODE_TYPES.Identifier && current.name === name) {
|
|
220
|
+
// Identifier found outside forwarded nodes — the field is used
|
|
221
|
+
// elsewhere.
|
|
222
|
+
//
|
|
223
|
+
// However, JSX opening element attribute names are not references to the
|
|
224
|
+
// destructured bindings, so we must exclude them. The parent check:
|
|
225
|
+
// - If inside JSXAttribute.name => not a reference.
|
|
226
|
+
const parent = current.parent;
|
|
227
|
+
const isJsxAttrName = parent !== undefined &&
|
|
228
|
+
parent.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
|
|
229
|
+
parent.name === current;
|
|
230
|
+
if (isJsxAttrName) {
|
|
231
|
+
// This is an attribute name, not a value reference — skip.
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const key of Object.keys(current)) {
|
|
238
|
+
if (key === 'parent')
|
|
239
|
+
continue;
|
|
240
|
+
const value = current[key];
|
|
241
|
+
if (Array.isArray(value)) {
|
|
242
|
+
for (const child of value) {
|
|
243
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
244
|
+
stack.push(child);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (value && typeof value === 'object' && 'type' in value) {
|
|
249
|
+
stack.push(value);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Finds a non-colliding name for the props parameter given the set of names
|
|
257
|
+
* already in scope (inferred from the forwarded field names themselves and the
|
|
258
|
+
* identifier `props`).
|
|
259
|
+
*/
|
|
260
|
+
function freshPropsName(existingNames) {
|
|
261
|
+
if (!existingNames.has('props'))
|
|
262
|
+
return 'props';
|
|
263
|
+
let i = 0;
|
|
264
|
+
while (existingNames.has(`props${i}`))
|
|
265
|
+
i++;
|
|
266
|
+
return `props${i}`;
|
|
267
|
+
}
|
|
268
|
+
exports.preferSpreadOverReassembly = (0, createRule_1.createRule)({
|
|
269
|
+
name: 'prefer-spread-over-reassembly',
|
|
270
|
+
meta: {
|
|
271
|
+
type: 'suggestion',
|
|
272
|
+
docs: {
|
|
273
|
+
description: 'Prefer spread syntax over destructure-then-reassemble when all destructured fields are forwarded identically to a single target',
|
|
274
|
+
recommended: 'error',
|
|
275
|
+
},
|
|
276
|
+
fixable: 'code',
|
|
277
|
+
schema: [
|
|
278
|
+
{
|
|
279
|
+
type: 'object',
|
|
280
|
+
properties: {
|
|
281
|
+
minFields: {
|
|
282
|
+
type: 'number',
|
|
283
|
+
default: DEFAULT_MIN_FIELDS,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
additionalProperties: false,
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
messages: {
|
|
290
|
+
preferSpread: 'Prefer spread over destructure-then-reassemble: replace the destructured parameter with a single identifier and use spread syntax on the target. ' +
|
|
291
|
+
'This avoids silent bugs when new fields are added to the type.',
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
defaultOptions: [{ minFields: DEFAULT_MIN_FIELDS }],
|
|
295
|
+
create(context, [options]) {
|
|
296
|
+
const minFields = options?.minFields ?? DEFAULT_MIN_FIELDS;
|
|
297
|
+
const sourceCode = context.getSourceCode();
|
|
298
|
+
function checkFunction(fn) {
|
|
299
|
+
// Must have exactly one parameter that is an ObjectPattern.
|
|
300
|
+
if (fn.params.length !== 1)
|
|
301
|
+
return;
|
|
302
|
+
const param = fn.params[0];
|
|
303
|
+
if (param.type !== utils_1.AST_NODE_TYPES.ObjectPattern)
|
|
304
|
+
return;
|
|
305
|
+
const destructuredNames = getSimpleDestructuredNames(param);
|
|
306
|
+
if (!destructuredNames || destructuredNames.length < minFields)
|
|
307
|
+
return;
|
|
308
|
+
const namesSet = new Set(destructuredNames);
|
|
309
|
+
// Find the single target element/object in the function body.
|
|
310
|
+
const target = getSingleTarget(fn);
|
|
311
|
+
if (!target)
|
|
312
|
+
return;
|
|
313
|
+
// Collect which destructured fields are forwarded identically.
|
|
314
|
+
let forwarded;
|
|
315
|
+
if (target.kind === 'jsx') {
|
|
316
|
+
forwarded = collectJsxForwardedFields(target.openingElement, namesSet);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
forwarded = collectObjectForwardedFields(target.expression, namesSet);
|
|
320
|
+
}
|
|
321
|
+
if (!forwarded)
|
|
322
|
+
return;
|
|
323
|
+
// We need at least minFields to be forwarded identically.
|
|
324
|
+
if (forwarded.length < minFields)
|
|
325
|
+
return;
|
|
326
|
+
// Build a set of the forwarded nodes so we can exclude them from the
|
|
327
|
+
// "used elsewhere" check.
|
|
328
|
+
const forwardedNodeSet = new Set(forwarded.map((f) => f.node));
|
|
329
|
+
// Ensure none of the forwarded names is used anywhere else in the body.
|
|
330
|
+
for (const { name } of forwarded) {
|
|
331
|
+
if (isNameUsedOutsideForwarding(fn.body, name, forwardedNodeSet)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// All forwarded names must represent ALL destructured names (per issue
|
|
336
|
+
// spec: "only flag when ALL destructured fields [...] are passed to a
|
|
337
|
+
// single target"). If some destructured fields are not forwarded at all,
|
|
338
|
+
// they must be used somewhere — the check above would have caught that.
|
|
339
|
+
// But we also want to be conservative: only flag if ALL destructured
|
|
340
|
+
// names are accounted for (either forwarded or... but the spec says they
|
|
341
|
+
// should all go to the same target).
|
|
342
|
+
const forwardedNamesSet = new Set(forwarded.map((f) => f.name));
|
|
343
|
+
for (const name of destructuredNames) {
|
|
344
|
+
if (!forwardedNamesSet.has(name)) {
|
|
345
|
+
// This destructured name is not forwarded to the target — it might
|
|
346
|
+
// be used elsewhere (which would have been caught above) or it is
|
|
347
|
+
// truly unused. Either way, don't flag.
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
context.report({
|
|
352
|
+
node: param,
|
|
353
|
+
messageId: 'preferSpread',
|
|
354
|
+
fix(fixer) {
|
|
355
|
+
const fixes = [];
|
|
356
|
+
// Choose a fresh name for the props parameter that does not collide
|
|
357
|
+
// with any binding currently in scope.
|
|
358
|
+
const propsName = freshPropsName(namesSet);
|
|
359
|
+
// 1. Replace the destructured parameter with `props` (or fresh name).
|
|
360
|
+
fixes.push(fixer.replaceText(param, propsName));
|
|
361
|
+
if (target.kind === 'jsx') {
|
|
362
|
+
// 2a. Build the new JSX opening element text.
|
|
363
|
+
const attrs = target.openingElement.attributes;
|
|
364
|
+
const nonForwardedAttrs = attrs.filter((a) => !forwardedNodeSet.has(a));
|
|
365
|
+
// Determine if the JSX element is self-closing.
|
|
366
|
+
const isSelfClosing = target.openingElement.selfClosing;
|
|
367
|
+
const tagName = sourceCode.getText(target.openingElement.name);
|
|
368
|
+
// Build new attribute list: spread first, then remaining attrs.
|
|
369
|
+
const spreadAttr = `{...${propsName}}`;
|
|
370
|
+
const remainingAttrTexts = nonForwardedAttrs.map((a) => sourceCode.getText(a));
|
|
371
|
+
const allAttrTexts = [spreadAttr, ...remainingAttrTexts];
|
|
372
|
+
const attrsText = allAttrTexts.length > 0 ? ' ' + allAttrTexts.join(' ') : '';
|
|
373
|
+
const newOpeningText = isSelfClosing
|
|
374
|
+
? `<${tagName}${attrsText} />`
|
|
375
|
+
: `<${tagName}${attrsText}>`;
|
|
376
|
+
fixes.push(fixer.replaceText(target.openingElement, newOpeningText));
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
// 2b. Build the new object expression text.
|
|
380
|
+
const props = target.expression.properties;
|
|
381
|
+
const nonForwardedProps = props.filter((p) => !forwardedNodeSet.has(p));
|
|
382
|
+
const nonForwardedTexts = nonForwardedProps.map((p) => sourceCode.getText(p));
|
|
383
|
+
const allPropTexts = [`...${propsName}`, ...nonForwardedTexts];
|
|
384
|
+
const newObjText = `{ ${allPropTexts.join(', ')} }`;
|
|
385
|
+
fixes.push(fixer.replaceText(target.expression, newObjText));
|
|
386
|
+
}
|
|
387
|
+
return fixes;
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
ArrowFunctionExpression(node) {
|
|
393
|
+
checkFunction(node);
|
|
394
|
+
},
|
|
395
|
+
FunctionExpression(node) {
|
|
396
|
+
checkFunction(node);
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
//# sourceMappingURL=prefer-spread-over-reassembly.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type Options = [
|
|
3
|
+
{
|
|
4
|
+
components?: string[];
|
|
5
|
+
allowedProps?: string[];
|
|
6
|
+
}
|
|
7
|
+
];
|
|
8
|
+
export declare const preferSxPropOverSystemProps: TSESLint.RuleModule<"preferSxProp", Options, TSESLint.RuleListener>;
|
|
9
|
+
export {};
|