eslint-plugin-react-x 5.24.0-beta.0 → 5.24.0-beta.2
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/dist/index.js +759 -740
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -145,7 +145,7 @@ const rules$6 = {
|
|
|
145
145
|
//#endregion
|
|
146
146
|
//#region package.json
|
|
147
147
|
var name$6 = "eslint-plugin-react-x";
|
|
148
|
-
var version = "5.24.0-beta.
|
|
148
|
+
var version = "5.24.0-beta.2";
|
|
149
149
|
|
|
150
150
|
//#endregion
|
|
151
151
|
//#region src/utils/create-rule.ts
|
|
@@ -1451,9 +1451,9 @@ function isRefLikeName$1(name) {
|
|
|
1451
1451
|
return name === "ref" || name.endsWith("Ref");
|
|
1452
1452
|
}
|
|
1453
1453
|
function hasRefLikeNameInChain(node) {
|
|
1454
|
-
const
|
|
1455
|
-
if (!Check.isExpression(
|
|
1456
|
-
return Extract.getMemberChain(
|
|
1454
|
+
const expr = Extract.unwrap(node);
|
|
1455
|
+
if (!Check.isExpression(expr)) return false;
|
|
1456
|
+
return Extract.getMemberChain(expr).some((member) => Check.isIdentifier(member) && isRefLikeName$1(member.name));
|
|
1457
1457
|
}
|
|
1458
1458
|
function isInitializedFromCall(context, node, isCall) {
|
|
1459
1459
|
const root = Extract.getMemberChain(node).at(0);
|
|
@@ -2093,694 +2093,111 @@ function create$41(context) {
|
|
|
2093
2093
|
}
|
|
2094
2094
|
|
|
2095
2095
|
//#endregion
|
|
2096
|
-
//#region
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
meta: {
|
|
2100
|
-
type: "suggestion",
|
|
2101
|
-
docs: { description: "Disallows class components except for error boundaries." },
|
|
2102
|
-
messages: { default: "Avoid using class components. Use function components instead." },
|
|
2103
|
-
schema: []
|
|
2104
|
-
},
|
|
2105
|
-
name: RULE_NAME$40,
|
|
2106
|
-
create: create$40,
|
|
2107
|
-
defaultOptions: []
|
|
2108
|
-
});
|
|
2109
|
-
function create$40(context) {
|
|
2110
|
-
if (!context.sourceCode.text.includes("Component")) return {};
|
|
2111
|
-
const { api, visitor } = core.getClassComponentCollector(context);
|
|
2112
|
-
return merge(visitor, { "Program:exit"(program) {
|
|
2113
|
-
for (const { id, name = "anonymous", node: component } of api.getAllComponents(program)) {
|
|
2114
|
-
if (component.body.body.some((m) => core.isComponentDidCatch(m) || core.isGetDerivedStateFromError(m))) continue;
|
|
2115
|
-
const classToken = context.sourceCode.getFirstToken(component, { filter: (token) => token.value === "class" });
|
|
2116
|
-
context.report({
|
|
2117
|
-
data: { name },
|
|
2118
|
-
loc: (id ?? classToken ?? component).loc,
|
|
2119
|
-
messageId: "default",
|
|
2120
|
-
node: component
|
|
2121
|
-
});
|
|
2122
|
-
}
|
|
2123
|
-
} });
|
|
2124
|
-
}
|
|
2125
|
-
|
|
2126
|
-
//#endregion
|
|
2127
|
-
//#region src/rules/no-clone-element/no-clone-element.ts
|
|
2128
|
-
const RULE_NAME$39 = "no-clone-element";
|
|
2129
|
-
var no_clone_element_default = createRule({
|
|
2130
|
-
meta: {
|
|
2131
|
-
type: "suggestion",
|
|
2132
|
-
docs: { description: "Disallows 'cloneElement'." },
|
|
2133
|
-
messages: { default: "Using 'cloneElement' is uncommon and can lead to fragile code. Use alternatives instead." },
|
|
2134
|
-
schema: []
|
|
2135
|
-
},
|
|
2136
|
-
name: RULE_NAME$39,
|
|
2137
|
-
create: create$39,
|
|
2138
|
-
defaultOptions: []
|
|
2139
|
-
});
|
|
2140
|
-
function create$39(context) {
|
|
2141
|
-
return { CallExpression(node) {
|
|
2142
|
-
if (core.isCloneElementCall(context, node)) context.report({
|
|
2143
|
-
messageId: "default",
|
|
2144
|
-
node
|
|
2145
|
-
});
|
|
2146
|
-
} };
|
|
2096
|
+
//#region ../../.pkgs/eff/dist/index.js
|
|
2097
|
+
function not(predicate) {
|
|
2098
|
+
return (data) => !predicate(data);
|
|
2147
2099
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
//#region src/rules/no-component-will-mount/no-component-will-mount.ts
|
|
2151
|
-
const RULE_NAME$38 = "no-component-will-mount";
|
|
2152
|
-
var no_component_will_mount_default = createRule({
|
|
2153
|
-
meta: {
|
|
2154
|
-
type: "problem",
|
|
2155
|
-
docs: { description: "Replaces usage of 'componentWillMount' with 'UNSAFE_componentWillMount'." },
|
|
2156
|
-
fixable: "code",
|
|
2157
|
-
messages: { default: "[Deprecated] Use 'UNSAFE_componentWillMount' instead." },
|
|
2158
|
-
schema: []
|
|
2159
|
-
},
|
|
2160
|
-
name: RULE_NAME$38,
|
|
2161
|
-
create: create$38,
|
|
2162
|
-
defaultOptions: []
|
|
2163
|
-
});
|
|
2164
|
-
function create$38(context) {
|
|
2165
|
-
if (!context.sourceCode.text.includes("componentWillMount")) return {};
|
|
2166
|
-
const { api, visitor } = core.getClassComponentCollector(context);
|
|
2167
|
-
return merge(visitor, { "Program:exit"(program) {
|
|
2168
|
-
for (const { node: component } of api.getAllComponents(program)) {
|
|
2169
|
-
const { body } = component.body;
|
|
2170
|
-
for (const member of body) if (core.isComponentWillMount(member)) context.report({
|
|
2171
|
-
fix(fixer) {
|
|
2172
|
-
if (!("key" in member)) return null;
|
|
2173
|
-
return fixer.replaceText(member.key, "UNSAFE_componentWillMount");
|
|
2174
|
-
},
|
|
2175
|
-
messageId: "default",
|
|
2176
|
-
node: member
|
|
2177
|
-
});
|
|
2178
|
-
}
|
|
2179
|
-
} });
|
|
2100
|
+
function or(a, b) {
|
|
2101
|
+
return (data) => a(data) || b(data);
|
|
2180
2102
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
messages: { default: "[Deprecated] Use 'UNSAFE_componentWillReceiveProps' instead." },
|
|
2191
|
-
schema: []
|
|
2192
|
-
},
|
|
2193
|
-
name: RULE_NAME$37,
|
|
2194
|
-
create: create$37,
|
|
2195
|
-
defaultOptions: []
|
|
2196
|
-
});
|
|
2197
|
-
function create$37(context) {
|
|
2198
|
-
if (!context.sourceCode.text.includes("componentWillReceiveProps")) return {};
|
|
2199
|
-
const { api, visitor } = core.getClassComponentCollector(context);
|
|
2200
|
-
return merge(visitor, { "Program:exit"(program) {
|
|
2201
|
-
for (const { node: component } of api.getAllComponents(program)) {
|
|
2202
|
-
const { body } = component.body;
|
|
2203
|
-
for (const member of body) if (core.isComponentWillReceiveProps(member)) context.report({
|
|
2204
|
-
fix(fixer) {
|
|
2205
|
-
if (!("key" in member)) return null;
|
|
2206
|
-
return fixer.replaceText(member.key, "UNSAFE_componentWillReceiveProps");
|
|
2207
|
-
},
|
|
2208
|
-
messageId: "default",
|
|
2209
|
-
node: member
|
|
2210
|
-
});
|
|
2211
|
-
}
|
|
2212
|
-
} });
|
|
2103
|
+
/**
|
|
2104
|
+
* A function that checks if the passed parameter is a string and narrows its type accordingly.
|
|
2105
|
+
*
|
|
2106
|
+
* @param data - The variable to check.
|
|
2107
|
+
* @returns True if the passed input is a string, false otherwise.
|
|
2108
|
+
* @category guards
|
|
2109
|
+
*/
|
|
2110
|
+
function isString(data) {
|
|
2111
|
+
return typeof data === "string";
|
|
2213
2112
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2113
|
+
/**
|
|
2114
|
+
* Applies a `pipe` method's variadic arguments to an initial value from left
|
|
2115
|
+
* to right.
|
|
2116
|
+
*
|
|
2117
|
+
* **When to use**
|
|
2118
|
+
*
|
|
2119
|
+
* Use to implement a custom `.pipe(...)` method from JavaScript's `arguments`
|
|
2120
|
+
* object.
|
|
2121
|
+
*
|
|
2122
|
+
* **Details**
|
|
2123
|
+
*
|
|
2124
|
+
* This helper is intended for implementing `Pipeable.pipe` methods that
|
|
2125
|
+
* receive JavaScript's `arguments` object. With no functions it returns the
|
|
2126
|
+
* original value; otherwise it feeds each result into the next function.
|
|
2127
|
+
*
|
|
2128
|
+
* **Example** (Implementing a pipe method)
|
|
2129
|
+
*
|
|
2130
|
+
* ```ts
|
|
2131
|
+
* import { Pipeable } from "effect"
|
|
2132
|
+
*
|
|
2133
|
+
* class NumberBox {
|
|
2134
|
+
* constructor(readonly value: number) {}
|
|
2135
|
+
*
|
|
2136
|
+
* pipe(..._fns: ReadonlyArray<(value: number) => number>): number {
|
|
2137
|
+
* return Pipeable.pipeArguments(this.value, arguments) as number
|
|
2138
|
+
* }
|
|
2139
|
+
* }
|
|
2140
|
+
*
|
|
2141
|
+
* const result = new NumberBox(5).pipe(
|
|
2142
|
+
* (n) => n + 2,
|
|
2143
|
+
* (n) => n * 3
|
|
2144
|
+
* )
|
|
2145
|
+
* console.log(result) // 21
|
|
2146
|
+
* ```
|
|
2147
|
+
*
|
|
2148
|
+
* @category combinators
|
|
2149
|
+
* @since 2.0.0
|
|
2150
|
+
*/
|
|
2151
|
+
const pipeArguments = (self, args) => {
|
|
2152
|
+
switch (args.length) {
|
|
2153
|
+
case 0: return self;
|
|
2154
|
+
case 1: return args[0](self);
|
|
2155
|
+
case 2: return args[1](args[0](self));
|
|
2156
|
+
case 3: return args[2](args[1](args[0](self)));
|
|
2157
|
+
case 4: return args[3](args[2](args[1](args[0](self))));
|
|
2158
|
+
case 5: return args[4](args[3](args[2](args[1](args[0](self)))));
|
|
2159
|
+
case 6: return args[5](args[4](args[3](args[2](args[1](args[0](self))))));
|
|
2160
|
+
case 7: return args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))));
|
|
2161
|
+
case 8: return args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self))))))));
|
|
2162
|
+
case 9: return args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))))));
|
|
2163
|
+
default: {
|
|
2164
|
+
let ret = self;
|
|
2165
|
+
for (let i = 0, len = args.length; i < len; i++) ret = args[i](ret);
|
|
2166
|
+
return ret;
|
|
2244
2167
|
}
|
|
2245
|
-
} });
|
|
2246
|
-
}
|
|
2247
|
-
|
|
2248
|
-
//#endregion
|
|
2249
|
-
//#region src/rules/no-context-provider/no-context-provider.ts
|
|
2250
|
-
const RULE_NAME$35 = "no-context-provider";
|
|
2251
|
-
var no_context_provider_default = createRule({
|
|
2252
|
-
meta: {
|
|
2253
|
-
type: "suggestion",
|
|
2254
|
-
docs: { description: "Replaces usage of '<Context.Provider>' with '<Context>'." },
|
|
2255
|
-
fixable: "code",
|
|
2256
|
-
hasSuggestions: true,
|
|
2257
|
-
messages: {
|
|
2258
|
-
default: "In React 19, you can render '<Context>' as a provider instead of '<Context.Provider>'.",
|
|
2259
|
-
replace: "Replace '<Context.Provider>' with '<Context>'."
|
|
2260
|
-
},
|
|
2261
|
-
schema: []
|
|
2262
|
-
},
|
|
2263
|
-
name: RULE_NAME$35,
|
|
2264
|
-
create: create$35,
|
|
2265
|
-
defaultOptions: []
|
|
2266
|
-
});
|
|
2267
|
-
function create$35(context) {
|
|
2268
|
-
if (!context.sourceCode.text.includes("Provider")) return {};
|
|
2269
|
-
const { version } = getSettingsFromContext(context);
|
|
2270
|
-
if (compare(version, "19.0.0", "<")) return {};
|
|
2271
|
-
return { JSXElement(node) {
|
|
2272
|
-
const parts = getElementFullType(node).split(".");
|
|
2273
|
-
const selfName = parts.pop();
|
|
2274
|
-
const contextFullName = parts.join(".");
|
|
2275
|
-
const contextSelfName = parts.pop();
|
|
2276
|
-
if (selfName !== "Provider") return;
|
|
2277
|
-
if (contextSelfName == null || !contextSelfName.endsWith("Context")) return;
|
|
2278
|
-
context.report({
|
|
2279
|
-
messageId: "default",
|
|
2280
|
-
node,
|
|
2281
|
-
suggest: [{
|
|
2282
|
-
fix(fixer) {
|
|
2283
|
-
if (!core.isFunctionComponentNameLoose(contextSelfName)) return null;
|
|
2284
|
-
const openingElement = node.openingElement;
|
|
2285
|
-
const closingElement = node.closingElement;
|
|
2286
|
-
if (closingElement == null) return fixer.replaceText(openingElement.name, contextFullName);
|
|
2287
|
-
return [fixer.replaceText(openingElement.name, contextFullName), fixer.replaceText(closingElement.name, contextFullName)];
|
|
2288
|
-
},
|
|
2289
|
-
messageId: "replace"
|
|
2290
|
-
}]
|
|
2291
|
-
});
|
|
2292
|
-
} };
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
//#endregion
|
|
2296
|
-
//#region src/rules/no-create-ref/no-create-ref.ts
|
|
2297
|
-
const RULE_NAME$34 = "no-create-ref";
|
|
2298
|
-
var no_create_ref_default = createRule({
|
|
2299
|
-
meta: {
|
|
2300
|
-
type: "suggestion",
|
|
2301
|
-
docs: { description: "Disallows 'createRef' in function components and Hooks." },
|
|
2302
|
-
messages: { default: "[Deprecated] Use 'useRef' instead." },
|
|
2303
|
-
schema: []
|
|
2304
|
-
},
|
|
2305
|
-
name: RULE_NAME$34,
|
|
2306
|
-
create: create$34,
|
|
2307
|
-
defaultOptions: []
|
|
2308
|
-
});
|
|
2309
|
-
function create$34(context) {
|
|
2310
|
-
if (!context.sourceCode.text.includes("createRef")) return {};
|
|
2311
|
-
const fc = core.getFunctionComponentCollector(context);
|
|
2312
|
-
const hc = core.getHookCollector(context);
|
|
2313
|
-
const createRefCalls = /* @__PURE__ */ new Set();
|
|
2314
|
-
return merge(fc.visitor, hc.visitor, {
|
|
2315
|
-
CallExpression(node) {
|
|
2316
|
-
if (!core.isCreateRefCall(context, node)) return;
|
|
2317
|
-
createRefCalls.add(node);
|
|
2318
|
-
},
|
|
2319
|
-
"Program:exit"(node) {
|
|
2320
|
-
const comps = fc.api.getAllComponents(node);
|
|
2321
|
-
const hooks = hc.api.getAllHooks(node);
|
|
2322
|
-
const funcs = [...comps, ...hooks];
|
|
2323
|
-
for (const call of createRefCalls) {
|
|
2324
|
-
if (Traverse.findParent(call, (n) => funcs.some((f) => f.node === n)) == null) continue;
|
|
2325
|
-
context.report({
|
|
2326
|
-
messageId: "default",
|
|
2327
|
-
node: call
|
|
2328
|
-
});
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
});
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
//#endregion
|
|
2335
|
-
//#region src/rules/no-direct-mutation-state/no-direct-mutation-state.ts
|
|
2336
|
-
const RULE_NAME$33 = "no-direct-mutation-state";
|
|
2337
|
-
var no_direct_mutation_state_default = createRule({
|
|
2338
|
-
meta: {
|
|
2339
|
-
type: "problem",
|
|
2340
|
-
docs: { description: "Disallows direct mutation of 'this.state'." },
|
|
2341
|
-
messages: { default: "Do not mutate state directly. Use 'setState()' instead." },
|
|
2342
|
-
schema: []
|
|
2343
|
-
},
|
|
2344
|
-
name: RULE_NAME$33,
|
|
2345
|
-
create: create$33,
|
|
2346
|
-
defaultOptions: []
|
|
2347
|
-
});
|
|
2348
|
-
function isConstructorFunction(node) {
|
|
2349
|
-
return Check.isOneOf([AST_NODE_TYPES.FunctionDeclaration, AST_NODE_TYPES.FunctionExpression])(node) && Check.isPropertyOrMethod(node.parent) && Check.isIdentifier(node.parent.key, "constructor");
|
|
2350
|
-
}
|
|
2351
|
-
function isAssignmentToThisState(node) {
|
|
2352
|
-
const { left } = node;
|
|
2353
|
-
let current = Extract.unwrap(left);
|
|
2354
|
-
while (current.type === AST_NODE_TYPES.MemberExpression) {
|
|
2355
|
-
const object = Extract.unwrap(current.object);
|
|
2356
|
-
const property = current.property;
|
|
2357
|
-
if (object.type === AST_NODE_TYPES.ThisExpression && Check.isIdentifier(property, "state")) return true;
|
|
2358
|
-
current = object;
|
|
2359
|
-
}
|
|
2360
|
-
return false;
|
|
2361
|
-
}
|
|
2362
|
-
function create$33(context) {
|
|
2363
|
-
return { AssignmentExpression(node) {
|
|
2364
|
-
if (!isAssignmentToThisState(node)) return;
|
|
2365
|
-
const fact = Traverse.findParent(node, Check.isClass);
|
|
2366
|
-
if (fact == null) return;
|
|
2367
|
-
if (core.isClassComponent(fact) && context.sourceCode.getScope(node).block !== Traverse.findParent(node, isConstructorFunction)) context.report({
|
|
2368
|
-
messageId: "default",
|
|
2369
|
-
node
|
|
2370
|
-
});
|
|
2371
|
-
} };
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
//#endregion
|
|
2375
|
-
//#region src/rules/no-duplicate-key/no-duplicate-key.ts
|
|
2376
|
-
const RULE_NAME$32 = "no-duplicate-key";
|
|
2377
|
-
var no_duplicate_key_default = createRule({
|
|
2378
|
-
meta: {
|
|
2379
|
-
type: "problem",
|
|
2380
|
-
docs: { description: "Prevents duplicate 'key' props on sibling elements when rendering lists." },
|
|
2381
|
-
messages: { default: "The 'key' prop must be unique to its sibling elements." },
|
|
2382
|
-
schema: []
|
|
2383
|
-
},
|
|
2384
|
-
name: RULE_NAME$32,
|
|
2385
|
-
create: create$32,
|
|
2386
|
-
defaultOptions: []
|
|
2387
|
-
});
|
|
2388
|
-
function create$32(context) {
|
|
2389
|
-
if (!context.sourceCode.text.includes("key=")) return {};
|
|
2390
|
-
const keyedEntries = /* @__PURE__ */ new Map();
|
|
2391
|
-
function isKeyValueEqual(a, b) {
|
|
2392
|
-
const aValue = a.value;
|
|
2393
|
-
const bValue = b.value;
|
|
2394
|
-
if (aValue == null || bValue == null) return false;
|
|
2395
|
-
return Compare.isEqual(aValue, bValue);
|
|
2396
2168
|
}
|
|
2397
|
-
|
|
2398
|
-
"JSXAttribute[name.name='key']"(node) {
|
|
2399
|
-
const jsxElement = node.parent.parent;
|
|
2400
|
-
switch (jsxElement.parent.type) {
|
|
2401
|
-
case AST_NODE_TYPES.ArrayExpression:
|
|
2402
|
-
case AST_NODE_TYPES.JSXElement:
|
|
2403
|
-
case AST_NODE_TYPES.JSXFragment: {
|
|
2404
|
-
const root = jsxElement.parent;
|
|
2405
|
-
const prevKeys = keyedEntries.get(root)?.keys ?? [];
|
|
2406
|
-
keyedEntries.set(root, {
|
|
2407
|
-
hasDuplicate: prevKeys.some((prevKey) => isKeyValueEqual(prevKey, node)),
|
|
2408
|
-
keys: [...prevKeys, node],
|
|
2409
|
-
root: jsxElement.parent
|
|
2410
|
-
});
|
|
2411
|
-
break;
|
|
2412
|
-
}
|
|
2413
|
-
default: {
|
|
2414
|
-
const call = Traverse.findParent(jsxElement, (n) => {
|
|
2415
|
-
if (n.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
2416
|
-
const callee = Extract.unwrap(n.callee);
|
|
2417
|
-
if (callee.type !== AST_NODE_TYPES.MemberExpression) return false;
|
|
2418
|
-
return Check.isIdentifier(callee.property, "map");
|
|
2419
|
-
});
|
|
2420
|
-
const iter = Traverse.findParent(jsxElement, Check.isFunction, (n) => n === call);
|
|
2421
|
-
if (!Check.isFunction(iter)) return;
|
|
2422
|
-
const arg0 = call?.arguments[0];
|
|
2423
|
-
if (call == null || arg0 == null) return;
|
|
2424
|
-
if (Extract.unwrap(arg0) !== iter) return;
|
|
2425
|
-
keyedEntries.set(call, {
|
|
2426
|
-
hasDuplicate: node.value?.type === AST_NODE_TYPES.Literal,
|
|
2427
|
-
keys: [node],
|
|
2428
|
-
root: call
|
|
2429
|
-
});
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
},
|
|
2433
|
-
"Program:exit"() {
|
|
2434
|
-
for (const { hasDuplicate, keys } of keyedEntries.values()) {
|
|
2435
|
-
if (!hasDuplicate) continue;
|
|
2436
|
-
for (const key of keys) context.report({
|
|
2437
|
-
data: { value: context.sourceCode.getText(key) },
|
|
2438
|
-
messageId: "default",
|
|
2439
|
-
node: key
|
|
2440
|
-
});
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
};
|
|
2444
|
-
}
|
|
2445
|
-
|
|
2446
|
-
//#endregion
|
|
2447
|
-
//#region src/rules/no-forward-ref/no-forward-ref.ts
|
|
2448
|
-
const RULE_NAME$31 = "no-forward-ref";
|
|
2449
|
-
var no_forward_ref_default = createRule({
|
|
2450
|
-
meta: {
|
|
2451
|
-
type: "suggestion",
|
|
2452
|
-
docs: { description: "Replaces usage of 'forwardRef' with passing 'ref' as a prop." },
|
|
2453
|
-
fixable: "code",
|
|
2454
|
-
hasSuggestions: true,
|
|
2455
|
-
messages: {
|
|
2456
|
-
default: "In React 19, 'forwardRef' is no longer necessary. Pass 'ref' as a prop instead.",
|
|
2457
|
-
replace: "Replace 'forwardRef' with passing 'ref' as a prop."
|
|
2458
|
-
},
|
|
2459
|
-
schema: []
|
|
2460
|
-
},
|
|
2461
|
-
name: RULE_NAME$31,
|
|
2462
|
-
create: create$31,
|
|
2463
|
-
defaultOptions: []
|
|
2464
|
-
});
|
|
2465
|
-
function create$31(context) {
|
|
2466
|
-
if (!context.sourceCode.text.includes("forwardRef")) return {};
|
|
2467
|
-
const { version } = getSettingsFromContext(context);
|
|
2468
|
-
if (compare(version, "19.0.0", "<")) return {};
|
|
2469
|
-
return { CallExpression(node) {
|
|
2470
|
-
if (!core.isForwardRefCall(context, node)) return;
|
|
2471
|
-
const id = core.getFunctionId(node);
|
|
2472
|
-
const suggest = couldFix(context, node) ? [{
|
|
2473
|
-
fix: buildFix(context, node),
|
|
2474
|
-
messageId: "replace"
|
|
2475
|
-
}] : [];
|
|
2476
|
-
context.report({
|
|
2477
|
-
messageId: "default",
|
|
2478
|
-
node: id ?? node,
|
|
2479
|
-
suggest
|
|
2480
|
-
});
|
|
2481
|
-
} };
|
|
2482
|
-
}
|
|
2169
|
+
};
|
|
2483
2170
|
/**
|
|
2484
|
-
*
|
|
2485
|
-
* the usage of `forwardRef` with passing `ref` as a prop
|
|
2171
|
+
* Reusable prototype that implements `Pipeable.pipe`.
|
|
2486
2172
|
*
|
|
2487
|
-
*
|
|
2488
|
-
*
|
|
2489
|
-
*
|
|
2173
|
+
* **When to use**
|
|
2174
|
+
*
|
|
2175
|
+
* Use when classes or object prototypes can reuse this value when they need the
|
|
2176
|
+
* standard pipe implementation backed by `pipeArguments`.
|
|
2177
|
+
*
|
|
2178
|
+
* @category prototypes
|
|
2179
|
+
* @since 3.15.0
|
|
2490
2180
|
*/
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
const callee = Extract.unwrap(node.callee);
|
|
2495
|
-
switch (callee.type) {
|
|
2496
|
-
case AST_NODE_TYPES.Identifier: return isInitializedFromReact(callee.name, initialScope, importSource);
|
|
2497
|
-
case AST_NODE_TYPES.MemberExpression: return Check.isIdentifier(callee.object) && isInitializedFromReact(callee.object.name, initialScope, importSource);
|
|
2498
|
-
default: return false;
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2181
|
+
const Prototype = { pipe() {
|
|
2182
|
+
return pipeArguments(this, arguments);
|
|
2183
|
+
} };
|
|
2501
2184
|
/**
|
|
2502
|
-
*
|
|
2503
|
-
*
|
|
2504
|
-
*
|
|
2505
|
-
*
|
|
2185
|
+
* Provides a base constructor whose instances implement the standard `Pipeable.pipe`
|
|
2186
|
+
* method.
|
|
2187
|
+
*
|
|
2188
|
+
* **When to use**
|
|
2189
|
+
*
|
|
2190
|
+
* Use when you need to define a class that supports Effect-style method
|
|
2191
|
+
* chaining through `.pipe(...)`.
|
|
2192
|
+
*
|
|
2193
|
+
* @category constructors
|
|
2194
|
+
* @since 3.15.0
|
|
2506
2195
|
*/
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
fixer.removeRange([node.range[0], componentNode.range[0]]),
|
|
2513
|
-
fixer.removeRange([componentNode.range[1], node.range[1]]),
|
|
2514
|
-
...buildFixForComponentProps(context, fixer, componentNode, node.typeArguments?.params ?? [])
|
|
2515
|
-
];
|
|
2516
|
-
};
|
|
2517
|
-
}
|
|
2518
|
-
/**
|
|
2519
|
-
* Generates fixes for the component's props and ref arguments
|
|
2520
|
-
* @param context The rule context
|
|
2521
|
-
* @param fixer The rule fixer
|
|
2522
|
-
* @param node The function component node
|
|
2523
|
-
* @param typeArguments The type arguments from the `forwardRef` call
|
|
2524
|
-
* @returns An array of fixes for the component's signature
|
|
2525
|
-
*/
|
|
2526
|
-
function buildFixForComponentProps(context, fixer, node, typeArguments) {
|
|
2527
|
-
const getText = (node) => context.sourceCode.getText(node);
|
|
2528
|
-
const [arg0, arg1] = node.params;
|
|
2529
|
-
const [typeArg0, typeArg1] = typeArguments;
|
|
2530
|
-
if (arg0 == null) {
|
|
2531
|
-
const openParen = context.sourceCode.getFirstToken(node, { filter: (t) => t.value === "(" });
|
|
2532
|
-
if (openParen == null) return [];
|
|
2533
|
-
if (typeArg0 == null || typeArg1 == null) return [];
|
|
2534
|
-
const typeArg0Text = getText(typeArg0);
|
|
2535
|
-
const typeArg1Text = getText(typeArg1);
|
|
2536
|
-
return [fixer.insertTextAfter(openParen, `{ ref }: ${typeArg1Text} & { ref?: React.RefObject<${typeArg0Text} | null> }`)];
|
|
2537
|
-
}
|
|
2538
|
-
const fixedArg0Text = match(arg0).with({ type: AST_NODE_TYPES.Identifier }, (n) => `...${n.name}`).with({ type: AST_NODE_TYPES.ObjectPattern }, (n) => n.properties.map(getText).join(", ")).otherwise(() => null);
|
|
2539
|
-
const fixedArg1Text = match(arg1).with(P.nullish, () => "ref").with({
|
|
2540
|
-
type: AST_NODE_TYPES.Identifier,
|
|
2541
|
-
name: "ref"
|
|
2542
|
-
}, () => "ref").with({
|
|
2543
|
-
type: AST_NODE_TYPES.Identifier,
|
|
2544
|
-
name: P.string
|
|
2545
|
-
}, (n) => `ref: ${n.name}`).otherwise(() => null);
|
|
2546
|
-
if (fixedArg0Text == null || fixedArg1Text == null) return [];
|
|
2547
|
-
if (typeArg0 == null || typeArg1 == null) return [fixer.replaceText(arg0, [
|
|
2548
|
-
"{",
|
|
2549
|
-
fixedArg1Text + ",",
|
|
2550
|
-
fixedArg0Text,
|
|
2551
|
-
"}"
|
|
2552
|
-
].join(" ")), ...arg1 == null ? [] : [fixer.remove(arg1), fixer.removeRange([arg0.range[1], arg1.range[0]])]];
|
|
2553
|
-
const typeArg0Text = getText(typeArg0);
|
|
2554
|
-
const typeArg1Text = getText(typeArg1);
|
|
2555
|
-
return [fixer.replaceText(arg0, [
|
|
2556
|
-
"{",
|
|
2557
|
-
fixedArg1Text + ",",
|
|
2558
|
-
fixedArg0Text,
|
|
2559
|
-
"}:",
|
|
2560
|
-
typeArg1Text,
|
|
2561
|
-
"&",
|
|
2562
|
-
"{",
|
|
2563
|
-
`ref?:`,
|
|
2564
|
-
`React.RefObject<${typeArg0Text} | null>`,
|
|
2565
|
-
"}"
|
|
2566
|
-
].join(" ")), ...arg1 == null ? [] : [fixer.remove(arg1), fixer.removeRange([arg0.range[1], arg1.range[0]])]];
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
|
-
//#endregion
|
|
2570
|
-
//#region src/utils/create-implicit-prop-listener.ts
|
|
2571
|
-
/**
|
|
2572
|
-
* Creates a listener that detects JSX spread attributes implicitly passing a given prop
|
|
2573
|
-
* (ex: 'children', 'key', 'ref') to a component.
|
|
2574
|
-
*
|
|
2575
|
-
* For each union constituent of the spread argument's type, the prop is reported unless:
|
|
2576
|
-
* 1. The prop symbol's fully qualified name is allowed (pass-through of React internally defined props)
|
|
2577
|
-
* 2. The fully qualified name of the prop's type symbol (or its alias) is allowed (React type aliases)
|
|
2578
|
-
*
|
|
2579
|
-
* @param context The ESLint rule context
|
|
2580
|
-
* @param options The prop name, the allow-list predicates (receive lowercased fully qualified names), and the report callback
|
|
2581
|
-
* @param options.name The name of the prop to detect
|
|
2582
|
-
* @param options.isAllowedProp A predicate that receives the prop symbol's lowercased fully qualified name and returns `true` if it is allowed
|
|
2583
|
-
* @param options.isAllowedType A predicate that receives the prop's type symbol's lowercased fully qualified name and returns `true` if it is allowed
|
|
2584
|
-
* @param options.onImplicitProp A callback invoked with the spread attribute node when an implicit prop is detected
|
|
2585
|
-
* @returns A rule listener reporting on JSX spread attributes
|
|
2586
|
-
*/
|
|
2587
|
-
function createImplicitPropListener(context, options) {
|
|
2588
|
-
const { name, isAllowedProp, isAllowedType, onImplicitProp } = options;
|
|
2589
|
-
const services = ESLintUtils.getParserServices(context, false);
|
|
2590
|
-
const checker = services.program.getTypeChecker();
|
|
2591
|
-
const getFqn = (symbol) => core.getFullyQualifiedNameEx(checker, symbol).toLowerCase();
|
|
2592
|
-
return { JSXSpreadAttribute(node) {
|
|
2593
|
-
for (const type of unionConstituents(getConstrainedTypeAtLocation(services, node.argument))) {
|
|
2594
|
-
const prop = type.getProperty(name);
|
|
2595
|
-
if (prop == null) continue;
|
|
2596
|
-
if (isAllowedProp(getFqn(prop))) continue;
|
|
2597
|
-
const propType = checker.getTypeOfSymbol(prop);
|
|
2598
|
-
const propTypeSymbol = propType.aliasSymbol ?? propType.symbol;
|
|
2599
|
-
if (propTypeSymbol != null && isAllowedType(getFqn(propTypeSymbol))) continue;
|
|
2600
|
-
onImplicitProp(node);
|
|
2601
|
-
}
|
|
2602
|
-
} };
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
//#endregion
|
|
2606
|
-
//#region src/rules/no-implicit-children/no-implicit-children.ts
|
|
2607
|
-
const RULE_NAME$30 = "no-implicit-children";
|
|
2608
|
-
const RE_REACT_CHILDREN_TYPE = /react\.(reactnode|reactelement|reactportal)$/i;
|
|
2609
|
-
var no_implicit_children_default = createRule({
|
|
2610
|
-
meta: {
|
|
2611
|
-
type: "problem",
|
|
2612
|
-
docs: { description: "Prevents implicitly passing the 'children' prop to components." },
|
|
2613
|
-
messages: { default: "This spread attribute implicitly passes the 'children' prop to a component, this could lead to unexpected behavior. If you intend to pass the 'children' prop, use 'children={value}'." },
|
|
2614
|
-
schema: []
|
|
2615
|
-
},
|
|
2616
|
-
name: RULE_NAME$30,
|
|
2617
|
-
create: create$30,
|
|
2618
|
-
defaultOptions: []
|
|
2619
|
-
});
|
|
2620
|
-
function create$30(context) {
|
|
2621
|
-
return createImplicitPropListener(context, {
|
|
2622
|
-
name: "children",
|
|
2623
|
-
isAllowedProp: (fqn) => fqn.endsWith("attributes.children") || fqn.endsWith("propswithchildren.children"),
|
|
2624
|
-
isAllowedType: (fqn) => RE_REACT_CHILDREN_TYPE.test(fqn) || fqn === "jsx.element",
|
|
2625
|
-
onImplicitProp(node) {
|
|
2626
|
-
context.report({
|
|
2627
|
-
messageId: "default",
|
|
2628
|
-
node
|
|
2629
|
-
});
|
|
2630
|
-
}
|
|
2631
|
-
});
|
|
2632
|
-
}
|
|
2633
|
-
|
|
2634
|
-
//#endregion
|
|
2635
|
-
//#region src/rules/no-implicit-key/no-implicit-key.ts
|
|
2636
|
-
const RULE_NAME$29 = "no-implicit-key";
|
|
2637
|
-
var no_implicit_key_default = createRule({
|
|
2638
|
-
meta: {
|
|
2639
|
-
type: "problem",
|
|
2640
|
-
docs: { description: "Prevents implicitly passing the 'key' prop to components." },
|
|
2641
|
-
messages: { default: "This spread attribute implicitly passes the 'key' prop to a component, this could lead to unexpected behavior. If you intend to pass the 'key' prop, use 'key={value}'." },
|
|
2642
|
-
schema: []
|
|
2643
|
-
},
|
|
2644
|
-
name: RULE_NAME$29,
|
|
2645
|
-
create: create$29,
|
|
2646
|
-
defaultOptions: []
|
|
2647
|
-
});
|
|
2648
|
-
function create$29(context) {
|
|
2649
|
-
return createImplicitPropListener(context, {
|
|
2650
|
-
name: "key",
|
|
2651
|
-
isAllowedProp: (fqn) => fqn.endsWith("react.attributes.key"),
|
|
2652
|
-
isAllowedType: (fqn) => fqn.endsWith("react.key"),
|
|
2653
|
-
onImplicitProp(node) {
|
|
2654
|
-
context.report({
|
|
2655
|
-
messageId: "default",
|
|
2656
|
-
node
|
|
2657
|
-
});
|
|
2658
|
-
}
|
|
2659
|
-
});
|
|
2660
|
-
}
|
|
2661
|
-
|
|
2662
|
-
//#endregion
|
|
2663
|
-
//#region src/rules/no-implicit-ref/no-implicit-ref.ts
|
|
2664
|
-
const RULE_NAME$28 = "no-implicit-ref";
|
|
2665
|
-
const RE_REACT_REF_TYPE = /react\.(ref|legacyref|refcallback|refobject)$/i;
|
|
2666
|
-
var no_implicit_ref_default = createRule({
|
|
2667
|
-
meta: {
|
|
2668
|
-
type: "problem",
|
|
2669
|
-
docs: { description: "Prevents implicitly passing the 'ref' prop to components." },
|
|
2670
|
-
messages: { default: "This spread attribute implicitly passes the 'ref' prop to a component, this could lead to unexpected behavior. If you intend to pass the 'ref' prop, use 'ref={value}'." },
|
|
2671
|
-
schema: []
|
|
2672
|
-
},
|
|
2673
|
-
name: RULE_NAME$28,
|
|
2674
|
-
create: create$28,
|
|
2675
|
-
defaultOptions: []
|
|
2676
|
-
});
|
|
2677
|
-
function create$28(context) {
|
|
2678
|
-
return createImplicitPropListener(context, {
|
|
2679
|
-
name: "ref",
|
|
2680
|
-
isAllowedProp: (fqn) => fqn.endsWith("attributes.ref"),
|
|
2681
|
-
isAllowedType: (fqn) => RE_REACT_REF_TYPE.test(fqn),
|
|
2682
|
-
onImplicitProp(node) {
|
|
2683
|
-
context.report({
|
|
2684
|
-
messageId: "default",
|
|
2685
|
-
node
|
|
2686
|
-
});
|
|
2687
|
-
}
|
|
2688
|
-
});
|
|
2689
|
-
}
|
|
2690
|
-
|
|
2691
|
-
//#endregion
|
|
2692
|
-
//#region ../../.pkgs/eff/dist/index.js
|
|
2693
|
-
function not(predicate) {
|
|
2694
|
-
return (data) => !predicate(data);
|
|
2695
|
-
}
|
|
2696
|
-
/**
|
|
2697
|
-
* Applies a `pipe` method's variadic arguments to an initial value from left
|
|
2698
|
-
* to right.
|
|
2699
|
-
*
|
|
2700
|
-
* **When to use**
|
|
2701
|
-
*
|
|
2702
|
-
* Use to implement a custom `.pipe(...)` method from JavaScript's `arguments`
|
|
2703
|
-
* object.
|
|
2704
|
-
*
|
|
2705
|
-
* **Details**
|
|
2706
|
-
*
|
|
2707
|
-
* This helper is intended for implementing `Pipeable.pipe` methods that
|
|
2708
|
-
* receive JavaScript's `arguments` object. With no functions it returns the
|
|
2709
|
-
* original value; otherwise it feeds each result into the next function.
|
|
2710
|
-
*
|
|
2711
|
-
* **Example** (Implementing a pipe method)
|
|
2712
|
-
*
|
|
2713
|
-
* ```ts
|
|
2714
|
-
* import { Pipeable } from "effect"
|
|
2715
|
-
*
|
|
2716
|
-
* class NumberBox {
|
|
2717
|
-
* constructor(readonly value: number) {}
|
|
2718
|
-
*
|
|
2719
|
-
* pipe(..._fns: ReadonlyArray<(value: number) => number>): number {
|
|
2720
|
-
* return Pipeable.pipeArguments(this.value, arguments) as number
|
|
2721
|
-
* }
|
|
2722
|
-
* }
|
|
2723
|
-
*
|
|
2724
|
-
* const result = new NumberBox(5).pipe(
|
|
2725
|
-
* (n) => n + 2,
|
|
2726
|
-
* (n) => n * 3
|
|
2727
|
-
* )
|
|
2728
|
-
* console.log(result) // 21
|
|
2729
|
-
* ```
|
|
2730
|
-
*
|
|
2731
|
-
* @category combinators
|
|
2732
|
-
* @since 2.0.0
|
|
2733
|
-
*/
|
|
2734
|
-
const pipeArguments = (self, args) => {
|
|
2735
|
-
switch (args.length) {
|
|
2736
|
-
case 0: return self;
|
|
2737
|
-
case 1: return args[0](self);
|
|
2738
|
-
case 2: return args[1](args[0](self));
|
|
2739
|
-
case 3: return args[2](args[1](args[0](self)));
|
|
2740
|
-
case 4: return args[3](args[2](args[1](args[0](self))));
|
|
2741
|
-
case 5: return args[4](args[3](args[2](args[1](args[0](self)))));
|
|
2742
|
-
case 6: return args[5](args[4](args[3](args[2](args[1](args[0](self))))));
|
|
2743
|
-
case 7: return args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))));
|
|
2744
|
-
case 8: return args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self))))))));
|
|
2745
|
-
case 9: return args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))))));
|
|
2746
|
-
default: {
|
|
2747
|
-
let ret = self;
|
|
2748
|
-
for (let i = 0, len = args.length; i < len; i++) ret = args[i](ret);
|
|
2749
|
-
return ret;
|
|
2750
|
-
}
|
|
2751
|
-
}
|
|
2752
|
-
};
|
|
2753
|
-
/**
|
|
2754
|
-
* Reusable prototype that implements `Pipeable.pipe`.
|
|
2755
|
-
*
|
|
2756
|
-
* **When to use**
|
|
2757
|
-
*
|
|
2758
|
-
* Use when classes or object prototypes can reuse this value when they need the
|
|
2759
|
-
* standard pipe implementation backed by `pipeArguments`.
|
|
2760
|
-
*
|
|
2761
|
-
* @category prototypes
|
|
2762
|
-
* @since 3.15.0
|
|
2763
|
-
*/
|
|
2764
|
-
const Prototype = { pipe() {
|
|
2765
|
-
return pipeArguments(this, arguments);
|
|
2766
|
-
} };
|
|
2767
|
-
/**
|
|
2768
|
-
* Provides a base constructor whose instances implement the standard `Pipeable.pipe`
|
|
2769
|
-
* method.
|
|
2770
|
-
*
|
|
2771
|
-
* **When to use**
|
|
2772
|
-
*
|
|
2773
|
-
* Use when you need to define a class that supports Effect-style method
|
|
2774
|
-
* chaining through `.pipe(...)`.
|
|
2775
|
-
*
|
|
2776
|
-
* @category constructors
|
|
2777
|
-
* @since 3.15.0
|
|
2778
|
-
*/
|
|
2779
|
-
const Class = (function() {
|
|
2780
|
-
function PipeableBase() {}
|
|
2781
|
-
PipeableBase.prototype = Prototype;
|
|
2782
|
-
return PipeableBase;
|
|
2783
|
-
})();
|
|
2196
|
+
const Class = (function() {
|
|
2197
|
+
function PipeableBase() {}
|
|
2198
|
+
PipeableBase.prototype = Prototype;
|
|
2199
|
+
return PipeableBase;
|
|
2200
|
+
})();
|
|
2784
2201
|
/**
|
|
2785
2202
|
* Provides small helpers for defining and reusing TypeScript functions.
|
|
2786
2203
|
*
|
|
@@ -3137,68 +2554,664 @@ function flow(ab, bc, cd, de, ef, fg, gh, hi, ij) {
|
|
|
3137
2554
|
* console.log(typeof buildUser) // "function"
|
|
3138
2555
|
* ```
|
|
3139
2556
|
*
|
|
3140
|
-
* @category utility types
|
|
3141
|
-
* @since 2.0.0
|
|
2557
|
+
* @category utility types
|
|
2558
|
+
* @since 2.0.0
|
|
2559
|
+
*/
|
|
2560
|
+
const hole = cast(absurd);
|
|
2561
|
+
function getOrInsertComputed(map, key, callback) {
|
|
2562
|
+
if (map.has(key)) return map.get(key);
|
|
2563
|
+
const value = callback(key);
|
|
2564
|
+
map.set(key, value);
|
|
2565
|
+
return value;
|
|
2566
|
+
}
|
|
2567
|
+
/**
|
|
2568
|
+
* Drops the longest prefix of elements from an array that satisfy the given predicate.
|
|
2569
|
+
*
|
|
2570
|
+
* Supports both data-first and data-last (`pipe`-friendly) call styles.
|
|
2571
|
+
*
|
|
2572
|
+
* @param pred - The predicate to test each element with.
|
|
2573
|
+
* @returns A new array without the matching prefix.
|
|
2574
|
+
* @example
|
|
2575
|
+
* ```ts
|
|
2576
|
+
* import * as assert from "node:assert"
|
|
2577
|
+
* import { dropWhile, pipe } from "@local/eff"
|
|
2578
|
+
*
|
|
2579
|
+
* // data-first
|
|
2580
|
+
* assert.deepStrictEqual(dropWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [3, 2, 1])
|
|
2581
|
+
*
|
|
2582
|
+
* // data-last
|
|
2583
|
+
* assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], dropWhile((n: number) => n < 3)), [3, 2, 1])
|
|
2584
|
+
* ```
|
|
2585
|
+
* @category array
|
|
2586
|
+
*/
|
|
2587
|
+
const dropWhile = dual(2, (xs, pred) => {
|
|
2588
|
+
const len = xs.length;
|
|
2589
|
+
let idx = 0;
|
|
2590
|
+
while (idx < len && pred(xs[idx])) idx++;
|
|
2591
|
+
return xs.slice(idx);
|
|
2592
|
+
});
|
|
2593
|
+
/**
|
|
2594
|
+
* Takes the longest prefix of elements from an array that satisfy the given predicate.
|
|
2595
|
+
*
|
|
2596
|
+
* Supports both data-first and data-last (`pipe`-friendly) call styles.
|
|
2597
|
+
*
|
|
2598
|
+
* @param pred - The predicate to test each element with.
|
|
2599
|
+
* @returns A new array containing only the matching prefix.
|
|
2600
|
+
* @example
|
|
2601
|
+
* ```ts
|
|
2602
|
+
* import * as assert from "node:assert"
|
|
2603
|
+
* import { pipe, takeWhile } from "@local/eff"
|
|
2604
|
+
*
|
|
2605
|
+
* // data-first
|
|
2606
|
+
* assert.deepStrictEqual(takeWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [1, 2])
|
|
2607
|
+
*
|
|
2608
|
+
* // data-last
|
|
2609
|
+
* assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], takeWhile((n: number) => n < 3)), [1, 2])
|
|
2610
|
+
* ```
|
|
2611
|
+
* @category array
|
|
2612
|
+
*/
|
|
2613
|
+
const takeWhile = dual(2, (xs, pred) => {
|
|
2614
|
+
const len = xs.length;
|
|
2615
|
+
let idx = 0;
|
|
2616
|
+
while (idx < len && pred(xs[idx])) idx++;
|
|
2617
|
+
return xs.slice(0, idx);
|
|
2618
|
+
});
|
|
2619
|
+
|
|
2620
|
+
//#endregion
|
|
2621
|
+
//#region src/rules/no-class-component/no-class-component.ts
|
|
2622
|
+
const RULE_NAME$40 = "no-class-component";
|
|
2623
|
+
var no_class_component_default = createRule({
|
|
2624
|
+
meta: {
|
|
2625
|
+
type: "suggestion",
|
|
2626
|
+
docs: { description: "Disallows class components except for error boundaries." },
|
|
2627
|
+
messages: { default: "Avoid using class components. Use function components instead." },
|
|
2628
|
+
schema: []
|
|
2629
|
+
},
|
|
2630
|
+
name: RULE_NAME$40,
|
|
2631
|
+
create: create$40,
|
|
2632
|
+
defaultOptions: []
|
|
2633
|
+
});
|
|
2634
|
+
function create$40(context) {
|
|
2635
|
+
if (!context.sourceCode.text.includes("Component")) return {};
|
|
2636
|
+
const { api, visitor } = core.getClassComponentCollector(context);
|
|
2637
|
+
return merge(visitor, { "Program:exit"(program) {
|
|
2638
|
+
for (const { id, name = "anonymous", node: component } of api.getAllComponents(program)) {
|
|
2639
|
+
if (component.body.body.some(or(core.isComponentDidCatch, core.isGetDerivedStateFromError))) continue;
|
|
2640
|
+
const classToken = context.sourceCode.getFirstToken(component, { filter: (token) => token.value === "class" });
|
|
2641
|
+
context.report({
|
|
2642
|
+
data: { name },
|
|
2643
|
+
loc: (id ?? classToken ?? component).loc,
|
|
2644
|
+
messageId: "default",
|
|
2645
|
+
node: component
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
} });
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
//#endregion
|
|
2652
|
+
//#region src/rules/no-clone-element/no-clone-element.ts
|
|
2653
|
+
const RULE_NAME$39 = "no-clone-element";
|
|
2654
|
+
var no_clone_element_default = createRule({
|
|
2655
|
+
meta: {
|
|
2656
|
+
type: "suggestion",
|
|
2657
|
+
docs: { description: "Disallows 'cloneElement'." },
|
|
2658
|
+
messages: { default: "Using 'cloneElement' is uncommon and can lead to fragile code. Use alternatives instead." },
|
|
2659
|
+
schema: []
|
|
2660
|
+
},
|
|
2661
|
+
name: RULE_NAME$39,
|
|
2662
|
+
create: create$39,
|
|
2663
|
+
defaultOptions: []
|
|
2664
|
+
});
|
|
2665
|
+
function create$39(context) {
|
|
2666
|
+
return { CallExpression(node) {
|
|
2667
|
+
if (core.isCloneElementCall(context, node)) context.report({
|
|
2668
|
+
messageId: "default",
|
|
2669
|
+
node
|
|
2670
|
+
});
|
|
2671
|
+
} };
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
//#endregion
|
|
2675
|
+
//#region src/rules/no-component-will-mount/no-component-will-mount.ts
|
|
2676
|
+
const RULE_NAME$38 = "no-component-will-mount";
|
|
2677
|
+
var no_component_will_mount_default = createRule({
|
|
2678
|
+
meta: {
|
|
2679
|
+
type: "problem",
|
|
2680
|
+
docs: { description: "Replaces usage of 'componentWillMount' with 'UNSAFE_componentWillMount'." },
|
|
2681
|
+
fixable: "code",
|
|
2682
|
+
messages: { default: "[Deprecated] Use 'UNSAFE_componentWillMount' instead." },
|
|
2683
|
+
schema: []
|
|
2684
|
+
},
|
|
2685
|
+
name: RULE_NAME$38,
|
|
2686
|
+
create: create$38,
|
|
2687
|
+
defaultOptions: []
|
|
2688
|
+
});
|
|
2689
|
+
function create$38(context) {
|
|
2690
|
+
if (!context.sourceCode.text.includes("componentWillMount")) return {};
|
|
2691
|
+
const { api, visitor } = core.getClassComponentCollector(context);
|
|
2692
|
+
return merge(visitor, { "Program:exit"(program) {
|
|
2693
|
+
for (const { node: component } of api.getAllComponents(program)) {
|
|
2694
|
+
const { body } = component.body;
|
|
2695
|
+
for (const member of body) if (core.isComponentWillMount(member)) context.report({
|
|
2696
|
+
fix(fixer) {
|
|
2697
|
+
if (!("key" in member)) return null;
|
|
2698
|
+
return fixer.replaceText(member.key, "UNSAFE_componentWillMount");
|
|
2699
|
+
},
|
|
2700
|
+
messageId: "default",
|
|
2701
|
+
node: member
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
} });
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
//#endregion
|
|
2708
|
+
//#region src/rules/no-component-will-receive-props/no-component-will-receive-props.ts
|
|
2709
|
+
const RULE_NAME$37 = "no-component-will-receive-props";
|
|
2710
|
+
var no_component_will_receive_props_default = createRule({
|
|
2711
|
+
meta: {
|
|
2712
|
+
type: "problem",
|
|
2713
|
+
docs: { description: "Replaces usage of 'componentWillReceiveProps' with 'UNSAFE_componentWillReceiveProps'." },
|
|
2714
|
+
fixable: "code",
|
|
2715
|
+
messages: { default: "[Deprecated] Use 'UNSAFE_componentWillReceiveProps' instead." },
|
|
2716
|
+
schema: []
|
|
2717
|
+
},
|
|
2718
|
+
name: RULE_NAME$37,
|
|
2719
|
+
create: create$37,
|
|
2720
|
+
defaultOptions: []
|
|
2721
|
+
});
|
|
2722
|
+
function create$37(context) {
|
|
2723
|
+
if (!context.sourceCode.text.includes("componentWillReceiveProps")) return {};
|
|
2724
|
+
const { api, visitor } = core.getClassComponentCollector(context);
|
|
2725
|
+
return merge(visitor, { "Program:exit"(program) {
|
|
2726
|
+
for (const { node: component } of api.getAllComponents(program)) {
|
|
2727
|
+
const { body } = component.body;
|
|
2728
|
+
for (const member of body) if (core.isComponentWillReceiveProps(member)) context.report({
|
|
2729
|
+
fix(fixer) {
|
|
2730
|
+
if (!("key" in member)) return null;
|
|
2731
|
+
return fixer.replaceText(member.key, "UNSAFE_componentWillReceiveProps");
|
|
2732
|
+
},
|
|
2733
|
+
messageId: "default",
|
|
2734
|
+
node: member
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
} });
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
//#endregion
|
|
2741
|
+
//#region src/rules/no-component-will-update/no-component-will-update.ts
|
|
2742
|
+
const RULE_NAME$36 = "no-component-will-update";
|
|
2743
|
+
var no_component_will_update_default = createRule({
|
|
2744
|
+
meta: {
|
|
2745
|
+
type: "problem",
|
|
2746
|
+
docs: { description: "Replaces usage of 'componentWillUpdate' with 'UNSAFE_componentWillUpdate'." },
|
|
2747
|
+
fixable: "code",
|
|
2748
|
+
messages: { default: "[Deprecated] Use 'UNSAFE_componentWillUpdate' instead." },
|
|
2749
|
+
schema: []
|
|
2750
|
+
},
|
|
2751
|
+
name: RULE_NAME$36,
|
|
2752
|
+
create: create$36,
|
|
2753
|
+
defaultOptions: []
|
|
2754
|
+
});
|
|
2755
|
+
function create$36(context) {
|
|
2756
|
+
if (!context.sourceCode.text.includes("componentWillUpdate")) return {};
|
|
2757
|
+
const { api, visitor } = core.getClassComponentCollector(context);
|
|
2758
|
+
return merge(visitor, { "Program:exit"(program) {
|
|
2759
|
+
for (const { node: component } of api.getAllComponents(program)) {
|
|
2760
|
+
const { body } = component.body;
|
|
2761
|
+
for (const member of body) if (core.isComponentWillUpdate(member)) context.report({
|
|
2762
|
+
fix(fixer) {
|
|
2763
|
+
if (!("key" in member)) return null;
|
|
2764
|
+
return fixer.replaceText(member.key, "UNSAFE_componentWillUpdate");
|
|
2765
|
+
},
|
|
2766
|
+
messageId: "default",
|
|
2767
|
+
node: member
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
} });
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
//#endregion
|
|
2774
|
+
//#region src/rules/no-context-provider/no-context-provider.ts
|
|
2775
|
+
const RULE_NAME$35 = "no-context-provider";
|
|
2776
|
+
var no_context_provider_default = createRule({
|
|
2777
|
+
meta: {
|
|
2778
|
+
type: "suggestion",
|
|
2779
|
+
docs: { description: "Replaces usage of '<Context.Provider>' with '<Context>'." },
|
|
2780
|
+
fixable: "code",
|
|
2781
|
+
hasSuggestions: true,
|
|
2782
|
+
messages: {
|
|
2783
|
+
default: "In React 19, you can render '<Context>' as a provider instead of '<Context.Provider>'.",
|
|
2784
|
+
replace: "Replace '<Context.Provider>' with '<Context>'."
|
|
2785
|
+
},
|
|
2786
|
+
schema: []
|
|
2787
|
+
},
|
|
2788
|
+
name: RULE_NAME$35,
|
|
2789
|
+
create: create$35,
|
|
2790
|
+
defaultOptions: []
|
|
2791
|
+
});
|
|
2792
|
+
function create$35(context) {
|
|
2793
|
+
if (!context.sourceCode.text.includes("Provider")) return {};
|
|
2794
|
+
const { version } = getSettingsFromContext(context);
|
|
2795
|
+
if (compare(version, "19.0.0", "<")) return {};
|
|
2796
|
+
return { JSXElement(node) {
|
|
2797
|
+
const parts = getElementFullType(node).split(".");
|
|
2798
|
+
const selfName = parts.pop();
|
|
2799
|
+
const contextFullName = parts.join(".");
|
|
2800
|
+
const contextSelfName = parts.pop();
|
|
2801
|
+
if (selfName !== "Provider") return;
|
|
2802
|
+
if (contextSelfName == null || !contextSelfName.endsWith("Context")) return;
|
|
2803
|
+
context.report({
|
|
2804
|
+
messageId: "default",
|
|
2805
|
+
node,
|
|
2806
|
+
suggest: [{
|
|
2807
|
+
fix(fixer) {
|
|
2808
|
+
if (!core.isFunctionComponentNameLoose(contextSelfName)) return null;
|
|
2809
|
+
const openingElement = node.openingElement;
|
|
2810
|
+
const closingElement = node.closingElement;
|
|
2811
|
+
if (closingElement == null) return fixer.replaceText(openingElement.name, contextFullName);
|
|
2812
|
+
return [fixer.replaceText(openingElement.name, contextFullName), fixer.replaceText(closingElement.name, contextFullName)];
|
|
2813
|
+
},
|
|
2814
|
+
messageId: "replace"
|
|
2815
|
+
}]
|
|
2816
|
+
});
|
|
2817
|
+
} };
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
//#endregion
|
|
2821
|
+
//#region src/rules/no-create-ref/no-create-ref.ts
|
|
2822
|
+
const RULE_NAME$34 = "no-create-ref";
|
|
2823
|
+
var no_create_ref_default = createRule({
|
|
2824
|
+
meta: {
|
|
2825
|
+
type: "suggestion",
|
|
2826
|
+
docs: { description: "Disallows 'createRef' in function components and Hooks." },
|
|
2827
|
+
messages: { default: "[Deprecated] Use 'useRef' instead." },
|
|
2828
|
+
schema: []
|
|
2829
|
+
},
|
|
2830
|
+
name: RULE_NAME$34,
|
|
2831
|
+
create: create$34,
|
|
2832
|
+
defaultOptions: []
|
|
2833
|
+
});
|
|
2834
|
+
function create$34(context) {
|
|
2835
|
+
if (!context.sourceCode.text.includes("createRef")) return {};
|
|
2836
|
+
const fc = core.getFunctionComponentCollector(context);
|
|
2837
|
+
const hc = core.getHookCollector(context);
|
|
2838
|
+
const createRefCalls = /* @__PURE__ */ new Set();
|
|
2839
|
+
return merge(fc.visitor, hc.visitor, {
|
|
2840
|
+
CallExpression(node) {
|
|
2841
|
+
if (!core.isCreateRefCall(context, node)) return;
|
|
2842
|
+
createRefCalls.add(node);
|
|
2843
|
+
},
|
|
2844
|
+
"Program:exit"(node) {
|
|
2845
|
+
const comps = fc.api.getAllComponents(node);
|
|
2846
|
+
const hooks = hc.api.getAllHooks(node);
|
|
2847
|
+
const funcs = [...comps, ...hooks];
|
|
2848
|
+
for (const call of createRefCalls) {
|
|
2849
|
+
if (Traverse.findParent(call, (n) => funcs.some((f) => f.node === n)) == null) continue;
|
|
2850
|
+
context.report({
|
|
2851
|
+
messageId: "default",
|
|
2852
|
+
node: call
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
//#endregion
|
|
2860
|
+
//#region src/rules/no-direct-mutation-state/no-direct-mutation-state.ts
|
|
2861
|
+
const RULE_NAME$33 = "no-direct-mutation-state";
|
|
2862
|
+
var no_direct_mutation_state_default = createRule({
|
|
2863
|
+
meta: {
|
|
2864
|
+
type: "problem",
|
|
2865
|
+
docs: { description: "Disallows direct mutation of 'this.state'." },
|
|
2866
|
+
messages: { default: "Do not mutate state directly. Use 'setState()' instead." },
|
|
2867
|
+
schema: []
|
|
2868
|
+
},
|
|
2869
|
+
name: RULE_NAME$33,
|
|
2870
|
+
create: create$33,
|
|
2871
|
+
defaultOptions: []
|
|
2872
|
+
});
|
|
2873
|
+
function isConstructorFunction(node) {
|
|
2874
|
+
return Check.isOneOf([AST_NODE_TYPES.FunctionDeclaration, AST_NODE_TYPES.FunctionExpression])(node) && Check.isPropertyOrMethod(node.parent) && Check.isIdentifier(node.parent.key, "constructor");
|
|
2875
|
+
}
|
|
2876
|
+
function isAssignmentToThisState(node) {
|
|
2877
|
+
const { left } = node;
|
|
2878
|
+
let current = Extract.unwrap(left);
|
|
2879
|
+
while (current.type === AST_NODE_TYPES.MemberExpression) {
|
|
2880
|
+
const object = Extract.unwrap(current.object);
|
|
2881
|
+
const property = current.property;
|
|
2882
|
+
if (object.type === AST_NODE_TYPES.ThisExpression && Check.isIdentifier(property, "state")) return true;
|
|
2883
|
+
current = object;
|
|
2884
|
+
}
|
|
2885
|
+
return false;
|
|
2886
|
+
}
|
|
2887
|
+
function create$33(context) {
|
|
2888
|
+
return { AssignmentExpression(node) {
|
|
2889
|
+
if (!isAssignmentToThisState(node)) return;
|
|
2890
|
+
const fact = Traverse.findParent(node, Check.isClass);
|
|
2891
|
+
if (fact == null) return;
|
|
2892
|
+
if (core.isClassComponent(fact) && context.sourceCode.getScope(node).block !== Traverse.findParent(node, isConstructorFunction)) context.report({
|
|
2893
|
+
messageId: "default",
|
|
2894
|
+
node
|
|
2895
|
+
});
|
|
2896
|
+
} };
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
//#endregion
|
|
2900
|
+
//#region src/rules/no-duplicate-key/no-duplicate-key.ts
|
|
2901
|
+
const RULE_NAME$32 = "no-duplicate-key";
|
|
2902
|
+
var no_duplicate_key_default = createRule({
|
|
2903
|
+
meta: {
|
|
2904
|
+
type: "problem",
|
|
2905
|
+
docs: { description: "Prevents duplicate 'key' props on sibling elements when rendering lists." },
|
|
2906
|
+
messages: { default: "The 'key' prop must be unique to its sibling elements." },
|
|
2907
|
+
schema: []
|
|
2908
|
+
},
|
|
2909
|
+
name: RULE_NAME$32,
|
|
2910
|
+
create: create$32,
|
|
2911
|
+
defaultOptions: []
|
|
2912
|
+
});
|
|
2913
|
+
function create$32(context) {
|
|
2914
|
+
if (!context.sourceCode.text.includes("key=")) return {};
|
|
2915
|
+
const keyedEntries = /* @__PURE__ */ new Map();
|
|
2916
|
+
function isKeyValueEqual(a, b) {
|
|
2917
|
+
const aValue = a.value;
|
|
2918
|
+
const bValue = b.value;
|
|
2919
|
+
if (aValue == null || bValue == null) return false;
|
|
2920
|
+
return Compare.isEqual(aValue, bValue);
|
|
2921
|
+
}
|
|
2922
|
+
return {
|
|
2923
|
+
"JSXAttribute[name.name='key']"(node) {
|
|
2924
|
+
const jsxElement = node.parent.parent;
|
|
2925
|
+
switch (jsxElement.parent.type) {
|
|
2926
|
+
case AST_NODE_TYPES.ArrayExpression:
|
|
2927
|
+
case AST_NODE_TYPES.JSXElement:
|
|
2928
|
+
case AST_NODE_TYPES.JSXFragment: {
|
|
2929
|
+
const root = jsxElement.parent;
|
|
2930
|
+
const prevKeys = keyedEntries.get(root)?.keys ?? [];
|
|
2931
|
+
keyedEntries.set(root, {
|
|
2932
|
+
hasDuplicate: prevKeys.some((prevKey) => isKeyValueEqual(prevKey, node)),
|
|
2933
|
+
keys: [...prevKeys, node],
|
|
2934
|
+
root: jsxElement.parent
|
|
2935
|
+
});
|
|
2936
|
+
break;
|
|
2937
|
+
}
|
|
2938
|
+
default: {
|
|
2939
|
+
const call = Traverse.findParent(jsxElement, (n) => {
|
|
2940
|
+
if (n.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
2941
|
+
const callee = Extract.unwrap(n.callee);
|
|
2942
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) return false;
|
|
2943
|
+
return Check.isIdentifier(callee.property, "map");
|
|
2944
|
+
});
|
|
2945
|
+
const iter = Traverse.findParent(jsxElement, Check.isFunction, (n) => n === call);
|
|
2946
|
+
if (!Check.isFunction(iter)) return;
|
|
2947
|
+
const arg0 = call?.arguments[0];
|
|
2948
|
+
if (call == null || arg0 == null) return;
|
|
2949
|
+
if (Extract.unwrap(arg0) !== iter) return;
|
|
2950
|
+
keyedEntries.set(call, {
|
|
2951
|
+
hasDuplicate: node.value?.type === AST_NODE_TYPES.Literal,
|
|
2952
|
+
keys: [node],
|
|
2953
|
+
root: call
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
},
|
|
2958
|
+
"Program:exit"() {
|
|
2959
|
+
for (const { hasDuplicate, keys } of keyedEntries.values()) {
|
|
2960
|
+
if (!hasDuplicate) continue;
|
|
2961
|
+
for (const key of keys) context.report({
|
|
2962
|
+
data: { value: context.sourceCode.getText(key) },
|
|
2963
|
+
messageId: "default",
|
|
2964
|
+
node: key
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
//#endregion
|
|
2972
|
+
//#region src/rules/no-forward-ref/no-forward-ref.ts
|
|
2973
|
+
const RULE_NAME$31 = "no-forward-ref";
|
|
2974
|
+
var no_forward_ref_default = createRule({
|
|
2975
|
+
meta: {
|
|
2976
|
+
type: "suggestion",
|
|
2977
|
+
docs: { description: "Replaces usage of 'forwardRef' with passing 'ref' as a prop." },
|
|
2978
|
+
fixable: "code",
|
|
2979
|
+
hasSuggestions: true,
|
|
2980
|
+
messages: {
|
|
2981
|
+
default: "In React 19, 'forwardRef' is no longer necessary. Pass 'ref' as a prop instead.",
|
|
2982
|
+
replace: "Replace 'forwardRef' with passing 'ref' as a prop."
|
|
2983
|
+
},
|
|
2984
|
+
schema: []
|
|
2985
|
+
},
|
|
2986
|
+
name: RULE_NAME$31,
|
|
2987
|
+
create: create$31,
|
|
2988
|
+
defaultOptions: []
|
|
2989
|
+
});
|
|
2990
|
+
function create$31(context) {
|
|
2991
|
+
if (!context.sourceCode.text.includes("forwardRef")) return {};
|
|
2992
|
+
const { version } = getSettingsFromContext(context);
|
|
2993
|
+
if (compare(version, "19.0.0", "<")) return {};
|
|
2994
|
+
return { CallExpression(node) {
|
|
2995
|
+
if (!core.isForwardRefCall(context, node)) return;
|
|
2996
|
+
const id = core.getFunctionId(node);
|
|
2997
|
+
const suggest = couldFix(context, node) ? [{
|
|
2998
|
+
fix: buildFix(context, node),
|
|
2999
|
+
messageId: "replace"
|
|
3000
|
+
}] : [];
|
|
3001
|
+
context.report({
|
|
3002
|
+
messageId: "default",
|
|
3003
|
+
node: id ?? node,
|
|
3004
|
+
suggest
|
|
3005
|
+
});
|
|
3006
|
+
} };
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Determine whether the given CallExpression can be safely auto-fixed by replacing
|
|
3010
|
+
* the usage of `forwardRef` with passing `ref` as a prop
|
|
3011
|
+
*
|
|
3012
|
+
* @param context The rule context object
|
|
3013
|
+
* @param node The CallExpression node to check
|
|
3014
|
+
* @returns True if the call can be auto-fixed, false otherwise
|
|
3142
3015
|
*/
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
const
|
|
3147
|
-
|
|
3148
|
-
|
|
3016
|
+
function couldFix(context, node) {
|
|
3017
|
+
const { importSource } = getSettingsFromContext(context);
|
|
3018
|
+
const initialScope = context.sourceCode.getScope(node);
|
|
3019
|
+
const callee = Extract.unwrap(node.callee);
|
|
3020
|
+
switch (callee.type) {
|
|
3021
|
+
case AST_NODE_TYPES.Identifier: return isInitializedFromReact(callee.name, initialScope, importSource);
|
|
3022
|
+
case AST_NODE_TYPES.MemberExpression: return Check.isIdentifier(callee.object) && isInitializedFromReact(callee.object.name, initialScope, importSource);
|
|
3023
|
+
default: return false;
|
|
3024
|
+
}
|
|
3149
3025
|
}
|
|
3150
3026
|
/**
|
|
3151
|
-
*
|
|
3152
|
-
*
|
|
3153
|
-
*
|
|
3154
|
-
*
|
|
3155
|
-
* @param pred - The predicate to test each element with.
|
|
3156
|
-
* @returns A new array without the matching prefix.
|
|
3157
|
-
* @example
|
|
3158
|
-
* ```ts
|
|
3159
|
-
* import * as assert from "node:assert"
|
|
3160
|
-
* import { dropWhile, pipe } from "@local/eff"
|
|
3161
|
-
*
|
|
3162
|
-
* // data-first
|
|
3163
|
-
* assert.deepStrictEqual(dropWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [3, 2, 1])
|
|
3164
|
-
*
|
|
3165
|
-
* // data-last
|
|
3166
|
-
* assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], dropWhile((n: number) => n < 3)), [3, 2, 1])
|
|
3167
|
-
* ```
|
|
3168
|
-
* @category array
|
|
3027
|
+
* Generates the fix for the `forwardRef` call
|
|
3028
|
+
* @param context The rule context
|
|
3029
|
+
* @param node The `forwardRef` call expression
|
|
3030
|
+
* @returns A fixer function that applies the changes
|
|
3169
3031
|
*/
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3032
|
+
function buildFix(context, node) {
|
|
3033
|
+
return (fixer) => {
|
|
3034
|
+
const [componentNode] = node.arguments;
|
|
3035
|
+
if (componentNode == null || !Check.isFunction(componentNode)) return [];
|
|
3036
|
+
return [
|
|
3037
|
+
fixer.removeRange([node.range[0], componentNode.range[0]]),
|
|
3038
|
+
fixer.removeRange([componentNode.range[1], node.range[1]]),
|
|
3039
|
+
...buildFixForComponentProps(context, fixer, componentNode, node.typeArguments?.params ?? [])
|
|
3040
|
+
];
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3176
3043
|
/**
|
|
3177
|
-
*
|
|
3178
|
-
*
|
|
3179
|
-
*
|
|
3180
|
-
*
|
|
3181
|
-
* @param
|
|
3182
|
-
* @returns
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3044
|
+
* Generates fixes for the component's props and ref arguments
|
|
3045
|
+
* @param context The rule context
|
|
3046
|
+
* @param fixer The rule fixer
|
|
3047
|
+
* @param node The function component node
|
|
3048
|
+
* @param typeArguments The type arguments from the `forwardRef` call
|
|
3049
|
+
* @returns An array of fixes for the component's signature
|
|
3050
|
+
*/
|
|
3051
|
+
function buildFixForComponentProps(context, fixer, node, typeArguments) {
|
|
3052
|
+
const getText = (node) => context.sourceCode.getText(node);
|
|
3053
|
+
const [arg0, arg1] = node.params;
|
|
3054
|
+
const [typeArg0, typeArg1] = typeArguments;
|
|
3055
|
+
if (arg0 == null) {
|
|
3056
|
+
const openParen = context.sourceCode.getFirstToken(node, { filter: (t) => t.value === "(" });
|
|
3057
|
+
if (openParen == null) return [];
|
|
3058
|
+
if (typeArg0 == null || typeArg1 == null) return [];
|
|
3059
|
+
const typeArg0Text = getText(typeArg0);
|
|
3060
|
+
const typeArg1Text = getText(typeArg1);
|
|
3061
|
+
return [fixer.insertTextAfter(openParen, `{ ref }: ${typeArg1Text} & { ref?: React.RefObject<${typeArg0Text} | null> }`)];
|
|
3062
|
+
}
|
|
3063
|
+
const fixedArg0Text = match(arg0).with({ type: AST_NODE_TYPES.Identifier }, (n) => `...${n.name}`).with({ type: AST_NODE_TYPES.ObjectPattern }, (n) => n.properties.map(getText).join(", ")).otherwise(() => null);
|
|
3064
|
+
const fixedArg1Text = match(arg1).with(P.nullish, () => "ref").with({
|
|
3065
|
+
type: AST_NODE_TYPES.Identifier,
|
|
3066
|
+
name: "ref"
|
|
3067
|
+
}, () => "ref").with({
|
|
3068
|
+
type: AST_NODE_TYPES.Identifier,
|
|
3069
|
+
name: P.string
|
|
3070
|
+
}, (n) => `ref: ${n.name}`).otherwise(() => null);
|
|
3071
|
+
if (fixedArg0Text == null || fixedArg1Text == null) return [];
|
|
3072
|
+
if (typeArg0 == null || typeArg1 == null) return [fixer.replaceText(arg0, [
|
|
3073
|
+
"{",
|
|
3074
|
+
fixedArg1Text + ",",
|
|
3075
|
+
fixedArg0Text,
|
|
3076
|
+
"}"
|
|
3077
|
+
].join(" ")), ...arg1 == null ? [] : [fixer.remove(arg1), fixer.removeRange([arg0.range[1], arg1.range[0]])]];
|
|
3078
|
+
const typeArg0Text = getText(typeArg0);
|
|
3079
|
+
const typeArg1Text = getText(typeArg1);
|
|
3080
|
+
return [fixer.replaceText(arg0, [
|
|
3081
|
+
"{",
|
|
3082
|
+
fixedArg1Text + ",",
|
|
3083
|
+
fixedArg0Text,
|
|
3084
|
+
"}:",
|
|
3085
|
+
typeArg1Text,
|
|
3086
|
+
"&",
|
|
3087
|
+
"{",
|
|
3088
|
+
`ref?:`,
|
|
3089
|
+
`React.RefObject<${typeArg0Text} | null>`,
|
|
3090
|
+
"}"
|
|
3091
|
+
].join(" ")), ...arg1 == null ? [] : [fixer.remove(arg1), fixer.removeRange([arg0.range[1], arg1.range[0]])]];
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
//#endregion
|
|
3095
|
+
//#region src/utils/create-implicit-prop-listener.ts
|
|
3096
|
+
/**
|
|
3097
|
+
* Creates a listener that detects JSX spread attributes implicitly passing a given prop
|
|
3098
|
+
* (ex: 'children', 'key', 'ref') to a component.
|
|
3187
3099
|
*
|
|
3188
|
-
*
|
|
3189
|
-
*
|
|
3100
|
+
* For each union constituent of the spread argument's type, the prop is reported unless:
|
|
3101
|
+
* 1. The prop symbol's fully qualified name is allowed (pass-through of React internally defined props)
|
|
3102
|
+
* 2. The fully qualified name of the prop's type symbol (or its alias) is allowed (React type aliases)
|
|
3190
3103
|
*
|
|
3191
|
-
*
|
|
3192
|
-
*
|
|
3193
|
-
*
|
|
3194
|
-
* @
|
|
3104
|
+
* @param context The ESLint rule context
|
|
3105
|
+
* @param options The prop name, the allow-list predicates (receive lowercased fully qualified names), and the report callback
|
|
3106
|
+
* @param options.name The name of the prop to detect
|
|
3107
|
+
* @param options.isAllowedProp A predicate that receives the prop symbol's lowercased fully qualified name and returns `true` if it is allowed
|
|
3108
|
+
* @param options.isAllowedType A predicate that receives the prop's type symbol's lowercased fully qualified name and returns `true` if it is allowed
|
|
3109
|
+
* @param options.onImplicitProp A callback invoked with the spread attribute node when an implicit prop is detected
|
|
3110
|
+
* @returns A rule listener reporting on JSX spread attributes
|
|
3195
3111
|
*/
|
|
3196
|
-
|
|
3197
|
-
const
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3112
|
+
function createImplicitPropListener(context, options) {
|
|
3113
|
+
const { name, isAllowedProp, isAllowedType, onImplicitProp } = options;
|
|
3114
|
+
const services = ESLintUtils.getParserServices(context, false);
|
|
3115
|
+
const checker = services.program.getTypeChecker();
|
|
3116
|
+
const getFqn = (symbol) => core.getFullyQualifiedNameEx(checker, symbol).toLowerCase();
|
|
3117
|
+
return { JSXSpreadAttribute(node) {
|
|
3118
|
+
for (const type of unionConstituents(getConstrainedTypeAtLocation(services, node.argument))) {
|
|
3119
|
+
const prop = type.getProperty(name);
|
|
3120
|
+
if (prop == null) continue;
|
|
3121
|
+
if (isAllowedProp(getFqn(prop))) continue;
|
|
3122
|
+
const propType = checker.getTypeOfSymbol(prop);
|
|
3123
|
+
const propTypeSymbol = propType.aliasSymbol ?? propType.symbol;
|
|
3124
|
+
if (propTypeSymbol != null && isAllowedType(getFqn(propTypeSymbol))) continue;
|
|
3125
|
+
onImplicitProp(node);
|
|
3126
|
+
}
|
|
3127
|
+
} };
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
//#endregion
|
|
3131
|
+
//#region src/rules/no-implicit-children/no-implicit-children.ts
|
|
3132
|
+
const RULE_NAME$30 = "no-implicit-children";
|
|
3133
|
+
const RE_REACT_CHILDREN_TYPE = /react\.(reactnode|reactelement|reactportal)$/i;
|
|
3134
|
+
var no_implicit_children_default = createRule({
|
|
3135
|
+
meta: {
|
|
3136
|
+
type: "problem",
|
|
3137
|
+
docs: { description: "Prevents implicitly passing the 'children' prop to components." },
|
|
3138
|
+
messages: { default: "This spread attribute implicitly passes the 'children' prop to a component, this could lead to unexpected behavior. If you intend to pass the 'children' prop, use 'children={value}'." },
|
|
3139
|
+
schema: []
|
|
3140
|
+
},
|
|
3141
|
+
name: RULE_NAME$30,
|
|
3142
|
+
create: create$30,
|
|
3143
|
+
defaultOptions: []
|
|
3144
|
+
});
|
|
3145
|
+
function create$30(context) {
|
|
3146
|
+
return createImplicitPropListener(context, {
|
|
3147
|
+
name: "children",
|
|
3148
|
+
isAllowedProp: (fqn) => fqn.endsWith("attributes.children") || fqn.endsWith("propswithchildren.children"),
|
|
3149
|
+
isAllowedType: (fqn) => RE_REACT_CHILDREN_TYPE.test(fqn) || fqn === "jsx.element",
|
|
3150
|
+
onImplicitProp(node) {
|
|
3151
|
+
context.report({
|
|
3152
|
+
messageId: "default",
|
|
3153
|
+
node
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
//#endregion
|
|
3160
|
+
//#region src/rules/no-implicit-key/no-implicit-key.ts
|
|
3161
|
+
const RULE_NAME$29 = "no-implicit-key";
|
|
3162
|
+
var no_implicit_key_default = createRule({
|
|
3163
|
+
meta: {
|
|
3164
|
+
type: "problem",
|
|
3165
|
+
docs: { description: "Prevents implicitly passing the 'key' prop to components." },
|
|
3166
|
+
messages: { default: "This spread attribute implicitly passes the 'key' prop to a component, this could lead to unexpected behavior. If you intend to pass the 'key' prop, use 'key={value}'." },
|
|
3167
|
+
schema: []
|
|
3168
|
+
},
|
|
3169
|
+
name: RULE_NAME$29,
|
|
3170
|
+
create: create$29,
|
|
3171
|
+
defaultOptions: []
|
|
3172
|
+
});
|
|
3173
|
+
function create$29(context) {
|
|
3174
|
+
return createImplicitPropListener(context, {
|
|
3175
|
+
name: "key",
|
|
3176
|
+
isAllowedProp: (fqn) => fqn.endsWith("react.attributes.key"),
|
|
3177
|
+
isAllowedType: (fqn) => fqn.endsWith("react.key"),
|
|
3178
|
+
onImplicitProp(node) {
|
|
3179
|
+
context.report({
|
|
3180
|
+
messageId: "default",
|
|
3181
|
+
node
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
//#endregion
|
|
3188
|
+
//#region src/rules/no-implicit-ref/no-implicit-ref.ts
|
|
3189
|
+
const RULE_NAME$28 = "no-implicit-ref";
|
|
3190
|
+
const RE_REACT_REF_TYPE = /react\.(ref|legacyref|refcallback|refobject)$/i;
|
|
3191
|
+
var no_implicit_ref_default = createRule({
|
|
3192
|
+
meta: {
|
|
3193
|
+
type: "problem",
|
|
3194
|
+
docs: { description: "Prevents implicitly passing the 'ref' prop to components." },
|
|
3195
|
+
messages: { default: "This spread attribute implicitly passes the 'ref' prop to a component, this could lead to unexpected behavior. If you intend to pass the 'ref' prop, use 'ref={value}'." },
|
|
3196
|
+
schema: []
|
|
3197
|
+
},
|
|
3198
|
+
name: RULE_NAME$28,
|
|
3199
|
+
create: create$28,
|
|
3200
|
+
defaultOptions: []
|
|
3201
3201
|
});
|
|
3202
|
+
function create$28(context) {
|
|
3203
|
+
return createImplicitPropListener(context, {
|
|
3204
|
+
name: "ref",
|
|
3205
|
+
isAllowedProp: (fqn) => fqn.endsWith("attributes.ref"),
|
|
3206
|
+
isAllowedType: (fqn) => RE_REACT_REF_TYPE.test(fqn),
|
|
3207
|
+
onImplicitProp(node) {
|
|
3208
|
+
context.report({
|
|
3209
|
+
messageId: "default",
|
|
3210
|
+
node
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
});
|
|
3214
|
+
}
|
|
3202
3215
|
|
|
3203
3216
|
//#endregion
|
|
3204
3217
|
//#region src/rules/no-leaked-conditional-rendering/no-leaked-conditional-rendering.ts
|
|
@@ -4358,7 +4371,7 @@ function collectUsedPropKeysOfReference(context, usedPropKeys, ref) {
|
|
|
4358
4371
|
function getKeyOfExpression(expr) {
|
|
4359
4372
|
switch (expr.type) {
|
|
4360
4373
|
case AST_NODE_TYPES.Identifier: return expr.name;
|
|
4361
|
-
case AST_NODE_TYPES.Literal: if (
|
|
4374
|
+
case AST_NODE_TYPES.Literal: if (isString(expr.value)) return expr.value;
|
|
4362
4375
|
}
|
|
4363
4376
|
return null;
|
|
4364
4377
|
}
|
|
@@ -7872,7 +7885,7 @@ const IDENTIFIER_VISITOR_KEYS = {
|
|
|
7872
7885
|
[AST_NODE_TYPES.AssignmentPattern]: ["left", "right"],
|
|
7873
7886
|
[AST_NODE_TYPES.AwaitExpression]: ["argument"],
|
|
7874
7887
|
[AST_NODE_TYPES.BinaryExpression]: ["left", "right"],
|
|
7875
|
-
[AST_NODE_TYPES.CallExpression]: ["
|
|
7888
|
+
[AST_NODE_TYPES.CallExpression]: ["callee", "arguments"],
|
|
7876
7889
|
[AST_NODE_TYPES.ChainExpression]: ["expression"],
|
|
7877
7890
|
[AST_NODE_TYPES.ConditionalExpression]: [
|
|
7878
7891
|
"test",
|
|
@@ -7884,7 +7897,7 @@ const IDENTIFIER_VISITOR_KEYS = {
|
|
|
7884
7897
|
[AST_NODE_TYPES.ImportExpression]: ["source"],
|
|
7885
7898
|
[AST_NODE_TYPES.LogicalExpression]: ["left", "right"],
|
|
7886
7899
|
[AST_NODE_TYPES.MemberExpression]: ["object", "property"],
|
|
7887
|
-
[AST_NODE_TYPES.NewExpression]: ["
|
|
7900
|
+
[AST_NODE_TYPES.NewExpression]: ["callee", "arguments"],
|
|
7888
7901
|
[AST_NODE_TYPES.ObjectExpression]: ["properties"],
|
|
7889
7902
|
[AST_NODE_TYPES.ObjectPattern]: ["properties"],
|
|
7890
7903
|
[AST_NODE_TYPES.Property]: ["value"],
|
|
@@ -8140,6 +8153,12 @@ function isArgumentUsingRefValue(context, node) {
|
|
|
8140
8153
|
case AST_NODE_TYPES.UpdateExpression: return isUsingRefValue(n.argument);
|
|
8141
8154
|
case AST_NODE_TYPES.ConditionalExpression: return isUsingRefValue(n.consequent) || isUsingRefValue(n.alternate);
|
|
8142
8155
|
case AST_NODE_TYPES.SequenceExpression: return n.expressions.some(isUsingRefValue);
|
|
8156
|
+
case AST_NODE_TYPES.TemplateLiteral: return n.expressions.some(isUsingRefValue);
|
|
8157
|
+
case AST_NODE_TYPES.TaggedTemplateExpression: return isUsingRefValue(n.tag) || isUsingRefValue(n.quasi);
|
|
8158
|
+
case AST_NODE_TYPES.ObjectExpression: return n.properties.some(isUsingRefValue);
|
|
8159
|
+
case AST_NODE_TYPES.Property: return isUsingRefValue(n.value);
|
|
8160
|
+
case AST_NODE_TYPES.ArrayExpression: return n.elements.some((element) => element != null && isUsingRefValue(element));
|
|
8161
|
+
case AST_NODE_TYPES.SpreadElement: return isUsingRefValue(n.argument);
|
|
8143
8162
|
case AST_NODE_TYPES.AssignmentExpression: return isUsingRefValue(n.right);
|
|
8144
8163
|
default: return false;
|
|
8145
8164
|
}
|
|
@@ -8991,7 +9010,7 @@ const EXPRESSION_VISITOR_KEYS = {
|
|
|
8991
9010
|
[AST_NODE_TYPES.AssignmentPattern]: ["left", "right"],
|
|
8992
9011
|
[AST_NODE_TYPES.AwaitExpression]: ["argument"],
|
|
8993
9012
|
[AST_NODE_TYPES.BinaryExpression]: ["left", "right"],
|
|
8994
|
-
[AST_NODE_TYPES.CallExpression]: ["
|
|
9013
|
+
[AST_NODE_TYPES.CallExpression]: ["callee", "arguments"],
|
|
8995
9014
|
[AST_NODE_TYPES.ChainExpression]: ["expression"],
|
|
8996
9015
|
[AST_NODE_TYPES.ConditionalExpression]: [
|
|
8997
9016
|
"test",
|
|
@@ -9014,7 +9033,7 @@ const EXPRESSION_VISITOR_KEYS = {
|
|
|
9014
9033
|
[AST_NODE_TYPES.JSXSpreadChild]: ["expression"],
|
|
9015
9034
|
[AST_NODE_TYPES.LogicalExpression]: ["left", "right"],
|
|
9016
9035
|
[AST_NODE_TYPES.MemberExpression]: ["object", "property"],
|
|
9017
|
-
[AST_NODE_TYPES.NewExpression]: ["
|
|
9036
|
+
[AST_NODE_TYPES.NewExpression]: ["callee", "arguments"],
|
|
9018
9037
|
[AST_NODE_TYPES.ObjectExpression]: ["properties"],
|
|
9019
9038
|
[AST_NODE_TYPES.ObjectPattern]: ["properties"],
|
|
9020
9039
|
[AST_NODE_TYPES.Property]: ["value"],
|