eslint-plugin-react-x 5.24.0-beta.1 → 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 +780 -767
- 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
|
|
@@ -2093,748 +2093,165 @@ 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
|
-
}
|
|
2196
|
+
const Class = (function() {
|
|
2197
|
+
function PipeableBase() {}
|
|
2198
|
+
PipeableBase.prototype = Prototype;
|
|
2199
|
+
return PipeableBase;
|
|
2200
|
+
})();
|
|
2518
2201
|
/**
|
|
2519
|
-
*
|
|
2520
|
-
*
|
|
2521
|
-
*
|
|
2522
|
-
*
|
|
2523
|
-
*
|
|
2524
|
-
*
|
|
2202
|
+
* Provides small helpers for defining and reusing TypeScript functions.
|
|
2203
|
+
*
|
|
2204
|
+
* The main helpers are `pipe` and `flow` for left-to-right composition and
|
|
2205
|
+
* `dual` for APIs that support both direct and pipe-friendly call styles. The
|
|
2206
|
+
* module also contains small identity, constant, tuple, type-level, and
|
|
2207
|
+
* memoization helpers used across the library.
|
|
2208
|
+
*
|
|
2209
|
+
* @since 2.0.0
|
|
2525
2210
|
*/
|
|
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
2211
|
/**
|
|
2697
|
-
*
|
|
2698
|
-
*
|
|
2212
|
+
* Creates a function that can be called in data-first style or data-last
|
|
2213
|
+
* (`pipe`-friendly) style.
|
|
2699
2214
|
*
|
|
2700
2215
|
* **When to use**
|
|
2701
2216
|
*
|
|
2702
|
-
* Use to
|
|
2703
|
-
*
|
|
2217
|
+
* Use to expose one implementation through both direct and `pipe`-friendly
|
|
2218
|
+
* call styles.
|
|
2704
2219
|
*
|
|
2705
2220
|
* **Details**
|
|
2706
2221
|
*
|
|
2707
|
-
*
|
|
2708
|
-
*
|
|
2709
|
-
*
|
|
2222
|
+
* Pass either the arity of the uncurried function or a predicate that decides
|
|
2223
|
+
* whether the current call is data-first. Arity is the common case. Use a
|
|
2224
|
+
* predicate when optional arguments make arity ambiguous.
|
|
2710
2225
|
*
|
|
2711
|
-
* **Example** (
|
|
2226
|
+
* **Example** (Selecting data-first or data-last style by arity)
|
|
2712
2227
|
*
|
|
2713
2228
|
* ```ts
|
|
2714
|
-
* import {
|
|
2715
|
-
*
|
|
2716
|
-
* class NumberBox {
|
|
2717
|
-
* constructor(readonly value: number) {}
|
|
2229
|
+
* import { Function, pipe } from "effect"
|
|
2718
2230
|
*
|
|
2719
|
-
*
|
|
2720
|
-
*
|
|
2721
|
-
*
|
|
2722
|
-
*
|
|
2231
|
+
* const sum = Function.dual<
|
|
2232
|
+
* (that: number) => (self: number) => number,
|
|
2233
|
+
* (self: number, that: number) => number
|
|
2234
|
+
* >(2, (self, that) => self + that)
|
|
2723
2235
|
*
|
|
2724
|
-
*
|
|
2725
|
-
*
|
|
2726
|
-
* (n) => n * 3
|
|
2727
|
-
* )
|
|
2728
|
-
* console.log(result) // 21
|
|
2236
|
+
* console.log(sum(2, 3)) // 5
|
|
2237
|
+
* console.log(pipe(2, sum(3))) // 5
|
|
2729
2238
|
* ```
|
|
2730
2239
|
*
|
|
2731
|
-
*
|
|
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`.
|
|
2240
|
+
* **Example** (Defining overloads with call signatures)
|
|
2755
2241
|
*
|
|
2756
|
-
*
|
|
2242
|
+
* ```ts
|
|
2243
|
+
* import { Function, pipe } from "effect"
|
|
2757
2244
|
*
|
|
2758
|
-
*
|
|
2759
|
-
*
|
|
2245
|
+
* const sum: {
|
|
2246
|
+
* (that: number): (self: number) => number
|
|
2247
|
+
* (self: number, that: number): number
|
|
2248
|
+
* } = Function.dual(2, (self: number, that: number): number => self + that)
|
|
2760
2249
|
*
|
|
2761
|
-
*
|
|
2762
|
-
*
|
|
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.
|
|
2250
|
+
* console.log(sum(2, 3)) // 5
|
|
2251
|
+
* console.log(pipe(2, sum(3))) // 5
|
|
2252
|
+
* ```
|
|
2770
2253
|
*
|
|
2771
|
-
* **
|
|
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
|
-
})();
|
|
2784
|
-
/**
|
|
2785
|
-
* Provides small helpers for defining and reusing TypeScript functions.
|
|
2786
|
-
*
|
|
2787
|
-
* The main helpers are `pipe` and `flow` for left-to-right composition and
|
|
2788
|
-
* `dual` for APIs that support both direct and pipe-friendly call styles. The
|
|
2789
|
-
* module also contains small identity, constant, tuple, type-level, and
|
|
2790
|
-
* memoization helpers used across the library.
|
|
2791
|
-
*
|
|
2792
|
-
* @since 2.0.0
|
|
2793
|
-
*/
|
|
2794
|
-
/**
|
|
2795
|
-
* Creates a function that can be called in data-first style or data-last
|
|
2796
|
-
* (`pipe`-friendly) style.
|
|
2797
|
-
*
|
|
2798
|
-
* **When to use**
|
|
2799
|
-
*
|
|
2800
|
-
* Use to expose one implementation through both direct and `pipe`-friendly
|
|
2801
|
-
* call styles.
|
|
2802
|
-
*
|
|
2803
|
-
* **Details**
|
|
2804
|
-
*
|
|
2805
|
-
* Pass either the arity of the uncurried function or a predicate that decides
|
|
2806
|
-
* whether the current call is data-first. Arity is the common case. Use a
|
|
2807
|
-
* predicate when optional arguments make arity ambiguous.
|
|
2808
|
-
*
|
|
2809
|
-
* **Example** (Selecting data-first or data-last style by arity)
|
|
2810
|
-
*
|
|
2811
|
-
* ```ts
|
|
2812
|
-
* import { Function, pipe } from "effect"
|
|
2813
|
-
*
|
|
2814
|
-
* const sum = Function.dual<
|
|
2815
|
-
* (that: number) => (self: number) => number,
|
|
2816
|
-
* (self: number, that: number) => number
|
|
2817
|
-
* >(2, (self, that) => self + that)
|
|
2818
|
-
*
|
|
2819
|
-
* console.log(sum(2, 3)) // 5
|
|
2820
|
-
* console.log(pipe(2, sum(3))) // 5
|
|
2821
|
-
* ```
|
|
2822
|
-
*
|
|
2823
|
-
* **Example** (Defining overloads with call signatures)
|
|
2824
|
-
*
|
|
2825
|
-
* ```ts
|
|
2826
|
-
* import { Function, pipe } from "effect"
|
|
2827
|
-
*
|
|
2828
|
-
* const sum: {
|
|
2829
|
-
* (that: number): (self: number) => number
|
|
2830
|
-
* (self: number, that: number): number
|
|
2831
|
-
* } = Function.dual(2, (self: number, that: number): number => self + that)
|
|
2832
|
-
*
|
|
2833
|
-
* console.log(sum(2, 3)) // 5
|
|
2834
|
-
* console.log(pipe(2, sum(3))) // 5
|
|
2835
|
-
* ```
|
|
2836
|
-
*
|
|
2837
|
-
* **Example** (Selecting data-first or data-last style with a predicate)
|
|
2254
|
+
* **Example** (Selecting data-first or data-last style with a predicate)
|
|
2838
2255
|
*
|
|
2839
2256
|
* ```ts
|
|
2840
2257
|
* import { Function, pipe } from "effect"
|
|
@@ -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
|
}
|