oxlint-plugin-react-doctor 0.7.2-dev.11e9c87 → 0.7.2-dev.43d766f
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 +171 -286
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -595,19 +595,6 @@ const TRIVIAL_INITIALIZER_NAMES = new Set([
|
|
|
595
595
|
"parseInt",
|
|
596
596
|
"parseFloat"
|
|
597
597
|
]);
|
|
598
|
-
const TRIVIAL_CONSTRUCTOR_NAMES = new Set([
|
|
599
|
-
"Date",
|
|
600
|
-
"Map",
|
|
601
|
-
"Set",
|
|
602
|
-
"WeakMap",
|
|
603
|
-
"WeakSet",
|
|
604
|
-
"WeakRef",
|
|
605
|
-
"RegExp",
|
|
606
|
-
"Error",
|
|
607
|
-
"URL",
|
|
608
|
-
"URLSearchParams",
|
|
609
|
-
"AbortController"
|
|
610
|
-
]);
|
|
611
598
|
const TRIVIAL_DERIVATION_CALLEE_NAMES = new Set([
|
|
612
599
|
"Boolean",
|
|
613
600
|
"String",
|
|
@@ -2248,38 +2235,6 @@ const anchorHasContent = defineRule({
|
|
|
2248
2235
|
}
|
|
2249
2236
|
});
|
|
2250
2237
|
//#endregion
|
|
2251
|
-
//#region src/plugin/utils/get-jsx-prop-static-string-values.ts
|
|
2252
|
-
const MAX_CONST_RESOLUTION_HOPS = 4;
|
|
2253
|
-
const resolveStaticStringValues = (rawExpression, scopes, remainingHops) => {
|
|
2254
|
-
const expression = stripParenExpression(rawExpression);
|
|
2255
|
-
if (isNodeOfType(expression, "Literal")) return typeof expression.value === "string" ? [expression.value] : null;
|
|
2256
|
-
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
2257
|
-
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
2258
|
-
return staticValue === null ? null : [staticValue];
|
|
2259
|
-
}
|
|
2260
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
2261
|
-
const consequentValues = resolveStaticStringValues(expression.consequent, scopes, remainingHops);
|
|
2262
|
-
if (consequentValues === null) return null;
|
|
2263
|
-
const alternateValues = resolveStaticStringValues(expression.alternate, scopes, remainingHops);
|
|
2264
|
-
if (alternateValues === null) return null;
|
|
2265
|
-
return [...consequentValues, ...alternateValues];
|
|
2266
|
-
}
|
|
2267
|
-
if (isNodeOfType(expression, "Identifier")) {
|
|
2268
|
-
if (remainingHops === 0) return null;
|
|
2269
|
-
const symbol = scopes.referenceFor(expression)?.resolvedSymbol;
|
|
2270
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
|
|
2271
|
-
return resolveStaticStringValues(symbol.initializer, scopes, remainingHops - 1);
|
|
2272
|
-
}
|
|
2273
|
-
return null;
|
|
2274
|
-
};
|
|
2275
|
-
const getJsxPropStaticStringValues = (attribute, scopes) => {
|
|
2276
|
-
const value = attribute.value;
|
|
2277
|
-
if (!value) return null;
|
|
2278
|
-
if (isNodeOfType(value, "Literal")) return typeof value.value === "string" ? [value.value] : null;
|
|
2279
|
-
if (isNodeOfType(value, "JSXExpressionContainer")) return resolveStaticStringValues(value.expression, scopes, MAX_CONST_RESOLUTION_HOPS);
|
|
2280
|
-
return null;
|
|
2281
|
-
};
|
|
2282
|
-
//#endregion
|
|
2283
2238
|
//#region src/plugin/rules/a11y/anchor-is-valid.ts
|
|
2284
2239
|
const MESSAGE_MISSING_HREF = "Keyboard users can't reach this link because it has no `href`, so add a real `href` (or use `<button>` for actions).";
|
|
2285
2240
|
const MESSAGE_INCORRECT_HREF = "Keyboard users can't reach this link because its `href` goes nowhere (`#`, `javascript:`, or empty), so point it at a real destination.";
|
|
@@ -2309,11 +2264,28 @@ const isInvalidHref = (value, validHrefs) => {
|
|
|
2309
2264
|
if (validHrefs.has(value)) return false;
|
|
2310
2265
|
return value === "" || value === "#" || value === "javascript:void(0)";
|
|
2311
2266
|
};
|
|
2312
|
-
const
|
|
2267
|
+
const getStaticHrefValue = (value) => {
|
|
2268
|
+
if (isNodeOfType(value, "Literal")) return typeof value.value === "string" ? value.value : null;
|
|
2269
|
+
if (isNodeOfType(value, "JSXExpressionContainer")) {
|
|
2270
|
+
const expression = value.expression;
|
|
2271
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
|
|
2272
|
+
if (isNodeOfType(expression, "TemplateLiteral")) return getStaticTemplateLiteralValue(expression);
|
|
2273
|
+
}
|
|
2274
|
+
return null;
|
|
2275
|
+
};
|
|
2276
|
+
const checkValueIsEmptyOrInvalid = (value, validHrefs) => {
|
|
2277
|
+
if (isNodeOfType(value, "Literal")) return typeof value.value === "string" ? isInvalidHref(value.value, validHrefs) : false;
|
|
2313
2278
|
if (isNodeOfType(value, "JSXExpressionContainer")) {
|
|
2314
2279
|
const expression = value.expression;
|
|
2315
2280
|
if (isNodeOfType(expression, "Identifier") && expression.name === "undefined") return true;
|
|
2316
|
-
if (isNodeOfType(expression, "Literal")
|
|
2281
|
+
if (isNodeOfType(expression, "Literal")) {
|
|
2282
|
+
if (expression.value === null) return true;
|
|
2283
|
+
if (typeof expression.value === "string") return isInvalidHref(expression.value, validHrefs);
|
|
2284
|
+
}
|
|
2285
|
+
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
2286
|
+
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
2287
|
+
return staticValue === null ? false : isInvalidHref(staticValue, validHrefs);
|
|
2288
|
+
}
|
|
2317
2289
|
}
|
|
2318
2290
|
if (isNodeOfType(value, "JSXFragment")) return true;
|
|
2319
2291
|
return false;
|
|
@@ -2346,10 +2318,9 @@ const anchorIsValid = defineRule({
|
|
|
2346
2318
|
});
|
|
2347
2319
|
return;
|
|
2348
2320
|
}
|
|
2349
|
-
|
|
2350
|
-
if (hrefCandidates !== null ? hrefCandidates.length > 0 && hrefCandidates.every((candidate) => isInvalidHref(candidate, settings.validHrefs)) : isNullishOrFragmentHref(hrefAttribute.value)) {
|
|
2321
|
+
if (checkValueIsEmptyOrInvalid(hrefAttribute.value, settings.validHrefs)) {
|
|
2351
2322
|
const hasOnClick = Boolean(hasJsxPropIgnoreCase(node.attributes, "onClick"));
|
|
2352
|
-
if (!hasOnClick &&
|
|
2323
|
+
if (!hasOnClick && getStaticHrefValue(hrefAttribute.value) === "#") return;
|
|
2353
2324
|
context.report({
|
|
2354
2325
|
node: node.name,
|
|
2355
2326
|
message: hasOnClick ? MESSAGE_CANT_BE_ANCHOR : MESSAGE_INCORRECT_HREF
|
|
@@ -3405,25 +3376,6 @@ const ariaRole = defineRule({
|
|
|
3405
3376
|
if (!roleAttribute) return;
|
|
3406
3377
|
const elementType = getElementType(node, context.settings);
|
|
3407
3378
|
if (settings.ignoreNonDOM && !HTML_TAGS.has(elementType)) return;
|
|
3408
|
-
const reportFirstInvalidCandidate = (candidates) => {
|
|
3409
|
-
for (const candidate of candidates) {
|
|
3410
|
-
if (candidate.trim().length === 0) {
|
|
3411
|
-
context.report({
|
|
3412
|
-
node: roleAttribute,
|
|
3413
|
-
message: buildBaseMessage("")
|
|
3414
|
-
});
|
|
3415
|
-
return;
|
|
3416
|
-
}
|
|
3417
|
-
const tokens = candidate.split(/\s+/).filter((token) => token.length > 0);
|
|
3418
|
-
for (const token of tokens) if (!VALID_ARIA_ROLES.has(token) && !settings.allowedInvalidRoles.includes(token)) {
|
|
3419
|
-
context.report({
|
|
3420
|
-
node: roleAttribute,
|
|
3421
|
-
message: buildBaseMessage(` \`${token}\` is not one.`)
|
|
3422
|
-
});
|
|
3423
|
-
return;
|
|
3424
|
-
}
|
|
3425
|
-
}
|
|
3426
|
-
};
|
|
3427
3379
|
const value = roleAttribute.value;
|
|
3428
3380
|
if (!value) {
|
|
3429
3381
|
context.report({
|
|
@@ -3440,7 +3392,22 @@ const ariaRole = defineRule({
|
|
|
3440
3392
|
});
|
|
3441
3393
|
return;
|
|
3442
3394
|
}
|
|
3443
|
-
|
|
3395
|
+
const stringValue = value.value;
|
|
3396
|
+
if (stringValue.trim().length === 0) {
|
|
3397
|
+
context.report({
|
|
3398
|
+
node: roleAttribute,
|
|
3399
|
+
message: buildBaseMessage("")
|
|
3400
|
+
});
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
const tokens = stringValue.split(/\s+/).filter((token) => token.length > 0);
|
|
3404
|
+
for (const token of tokens) if (!VALID_ARIA_ROLES.has(token) && !settings.allowedInvalidRoles.includes(token)) {
|
|
3405
|
+
context.report({
|
|
3406
|
+
node: roleAttribute,
|
|
3407
|
+
message: buildBaseMessage(` \`${token}\` is not one.`)
|
|
3408
|
+
});
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3444
3411
|
return;
|
|
3445
3412
|
}
|
|
3446
3413
|
if (isNodeOfType(value, "JSXExpressionContainer")) {
|
|
@@ -3461,11 +3428,6 @@ const ariaRole = defineRule({
|
|
|
3461
3428
|
});
|
|
3462
3429
|
return;
|
|
3463
3430
|
}
|
|
3464
|
-
const resolvedCandidates = getJsxPropStaticStringValues(roleAttribute, context.scopes);
|
|
3465
|
-
if (resolvedCandidates !== null) {
|
|
3466
|
-
reportFirstInvalidCandidate(resolvedCandidates);
|
|
3467
|
-
return;
|
|
3468
|
-
}
|
|
3469
3431
|
return;
|
|
3470
3432
|
}
|
|
3471
3433
|
context.report({
|
|
@@ -9654,7 +9616,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
9654
9616
|
});
|
|
9655
9617
|
//#endregion
|
|
9656
9618
|
//#region src/plugin/rules/react-native/expo-no-non-inlined-env.ts
|
|
9657
|
-
const EMPTY_VISITORS$
|
|
9619
|
+
const EMPTY_VISITORS$5 = {};
|
|
9658
9620
|
const NODE_OR_BUILD_FILE = /(\.config\.[cm]?[jt]sx?$)|((^|\/)(scripts|tools|tooling|cli|bin)\/)|(\+(api|html)\.[cm]?[jt]sx?$)|(\.server\.[cm]?[jt]sx?$)|(\.(test|spec)\.[cm]?[jt]sx?$)|((^|\/)__tests__\/)|(\.e2e\.[cm]?[jt]sx?$)/;
|
|
9659
9621
|
const isNonExpoPublicLiteralKey = (key) => isNodeOfType(key, "Literal") && typeof key.value === "string" && !key.value.startsWith("EXPO_PUBLIC_");
|
|
9660
9622
|
const isProcessEnv = (node) => isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && node.object.name === "process" && isNodeOfType(node.property, "Identifier") && node.property.name === "env";
|
|
@@ -9666,7 +9628,7 @@ const expoNoNonInlinedEnv = defineRule({
|
|
|
9666
9628
|
recommendation: "Read env vars with static dotted access (`process.env.EXPO_PUBLIC_NAME`). Computed access and destructuring aren't inlined by babel-preset-expo and resolve to `undefined` at runtime.",
|
|
9667
9629
|
create: (context) => {
|
|
9668
9630
|
const filename = normalizeFilename(context.filename ?? "");
|
|
9669
|
-
if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$
|
|
9631
|
+
if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$5;
|
|
9670
9632
|
return {
|
|
9671
9633
|
MemberExpression(node) {
|
|
9672
9634
|
if (!node.computed) return;
|
|
@@ -11084,8 +11046,8 @@ const interactiveSupportsFocus = defineRule({
|
|
|
11084
11046
|
if (node.attributes.length === 0) return;
|
|
11085
11047
|
if (hasJsxSpreadAttribute$1(node.attributes)) return;
|
|
11086
11048
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
11087
|
-
const
|
|
11088
|
-
if (
|
|
11049
|
+
const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
|
|
11050
|
+
if (!role) return;
|
|
11089
11051
|
let hasInteractiveHandler = false;
|
|
11090
11052
|
for (const attribute of node.attributes) {
|
|
11091
11053
|
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
@@ -11099,16 +11061,11 @@ const interactiveSupportsFocus = defineRule({
|
|
|
11099
11061
|
const elementType = getElementType(node, context.settings);
|
|
11100
11062
|
if (!HTML_TAGS.has(elementType)) return;
|
|
11101
11063
|
if (isDisabledElement(node) || isHiddenFromScreenReader(node, context.settings) || isPresentationRole(node)) return;
|
|
11064
|
+
if (COMPOSITE_CONTAINER_ROLES.has(role)) return;
|
|
11065
|
+
if (COMPOSITE_ITEM_ROLES.has(role) && Boolean(hasJsxPropIgnoreCase(node.attributes, "id"))) return;
|
|
11102
11066
|
const hasTabIndex = Boolean(hasJsxPropIgnoreCase(node.attributes, "tabIndex"));
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
if (COMPOSITE_CONTAINER_ROLES.has(role)) return;
|
|
11106
|
-
if (COMPOSITE_ITEM_ROLES.has(role) && hasId) return;
|
|
11107
|
-
if (!isInteractiveRole(role) || isInteractiveElement(elementType, node) || isNonInteractiveRole(role) || isNonInteractiveElement(elementType, node) || hasTabIndex) return;
|
|
11108
|
-
}
|
|
11109
|
-
const isEveryCandidateTabbable = roleCandidates.every((role) => tabbableSet.has(role));
|
|
11110
|
-
const roleDisplay = roleCandidates.join("' / '");
|
|
11111
|
-
const message = isEveryCandidateTabbable ? buildTabbableMessage(roleDisplay) : buildFocusableMessage(roleDisplay);
|
|
11067
|
+
if (!isInteractiveRole(role) || isInteractiveElement(elementType, node) || isNonInteractiveRole(role) || isNonInteractiveElement(elementType, node) || hasTabIndex) return;
|
|
11068
|
+
const message = tabbableSet.has(role) ? buildTabbableMessage(role) : buildFocusableMessage(role);
|
|
11112
11069
|
context.report({
|
|
11113
11070
|
node,
|
|
11114
11071
|
message
|
|
@@ -22520,10 +22477,6 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
|
|
|
22520
22477
|
const section = manifest[sectionName];
|
|
22521
22478
|
return typeof section === "object" && section !== null && Object.keys(section).length > 0;
|
|
22522
22479
|
});
|
|
22523
|
-
const declaresDependency = (manifest, dependencyName) => {
|
|
22524
|
-
for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
|
|
22525
|
-
return false;
|
|
22526
|
-
};
|
|
22527
22480
|
const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
|
|
22528
22481
|
const classifyPackagePlatform = (filename) => {
|
|
22529
22482
|
const manifest = readNearestPackageManifest(filename);
|
|
@@ -22540,7 +22493,9 @@ const classifyPackagePlatform = (filename) => {
|
|
|
22540
22493
|
return result;
|
|
22541
22494
|
};
|
|
22542
22495
|
//#endregion
|
|
22543
|
-
//#region src/plugin/utils/is-
|
|
22496
|
+
//#region src/plugin/utils/is-react-native-file.ts
|
|
22497
|
+
const WEB_FILE_EXTENSION_PATTERN = /\.web\.[cm]?[jt]sx?$/;
|
|
22498
|
+
const NATIVE_FILE_EXTENSION_PATTERN = /\.(?:ios|android|native)\.[cm]?[jt]sx?$/;
|
|
22544
22499
|
const cachedRealDirectoryByDirectory = /* @__PURE__ */ new Map();
|
|
22545
22500
|
const resolveRealDirectory = (directory) => {
|
|
22546
22501
|
const cached = cachedRealDirectoryByDirectory.get(directory);
|
|
@@ -22561,10 +22516,6 @@ const isPackageNestedBelowProjectRoot = (packageDirectory, rootDirectory) => {
|
|
|
22561
22516
|
const rootPrefix = normalizedRootDirectory.endsWith("/") ? normalizedRootDirectory : `${normalizedRootDirectory}/`;
|
|
22562
22517
|
return realPackageDirectory.startsWith(rootPrefix);
|
|
22563
22518
|
};
|
|
22564
|
-
//#endregion
|
|
22565
|
-
//#region src/plugin/utils/is-react-native-file.ts
|
|
22566
|
-
const WEB_FILE_EXTENSION_PATTERN = /\.web\.[cm]?[jt]sx?$/;
|
|
22567
|
-
const NATIVE_FILE_EXTENSION_PATTERN = /\.(?:ios|android|native)\.[cm]?[jt]sx?$/;
|
|
22568
22519
|
const classifyReactNativeFileTarget = (context) => {
|
|
22569
22520
|
const rawFilename = context.filename;
|
|
22570
22521
|
if (!rawFilename) return "unknown";
|
|
@@ -22981,6 +22932,7 @@ const countStatementSequenceSetStateCalls = (statements, context) => {
|
|
|
22981
22932
|
let fallThroughCount = 0;
|
|
22982
22933
|
let maxTerminatingPathCount = 0;
|
|
22983
22934
|
for (const statement of statements) {
|
|
22935
|
+
if (isFunctionLike$1(statement)) continue;
|
|
22984
22936
|
const guardBranch = isGuardWithTerminatingBranch(statement);
|
|
22985
22937
|
if (guardBranch) {
|
|
22986
22938
|
maxTerminatingPathCount = Math.max(maxTerminatingPathCount, fallThroughCount + countMaxPathSetStateCalls(guardBranch, context));
|
|
@@ -23016,23 +22968,14 @@ const isWholesaleDelegationCall = (callNode, effectCallback) => {
|
|
|
23016
22968
|
if (!isNodeOfType(parent, "ExpressionStatement")) return false;
|
|
23017
22969
|
return parent.parent === effectCallback.body;
|
|
23018
22970
|
};
|
|
23019
|
-
const countFunctionBodySetStateCalls = (functionNode, context) => {
|
|
23020
|
-
if (isAsyncFunctionLike(functionNode)) return 0;
|
|
23021
|
-
const body = functionNode.body;
|
|
23022
|
-
if (!body) return 0;
|
|
23023
|
-
return countMaxPathSetStateCalls(body, context);
|
|
23024
|
-
};
|
|
23025
22971
|
const countMaxPathSetStateCalls = (node, context) => {
|
|
23026
22972
|
if (!node || typeof node !== "object") return 0;
|
|
23027
|
-
if (
|
|
22973
|
+
if (isAsyncFunctionLike(node)) return 0;
|
|
23028
22974
|
if (isNodeOfType(node, "BlockStatement") || isNodeOfType(node, "Program")) return countStatementSequenceSetStateCalls(node.body ?? [], context);
|
|
23029
22975
|
if (isNodeOfType(node, "IfStatement") || isNodeOfType(node, "ConditionalExpression")) {
|
|
23030
22976
|
const consequent = node.consequent;
|
|
23031
22977
|
const alternate = node.alternate;
|
|
23032
|
-
|
|
23033
|
-
const thenCount = countMaxPathSetStateCalls(consequent, context);
|
|
23034
|
-
const elseCount = alternate ? countMaxPathSetStateCalls(alternate, context) : 0;
|
|
23035
|
-
return testCount + Math.max(thenCount, elseCount);
|
|
22978
|
+
return countMaxPathSetStateCalls(consequent, context) + (alternate ? countMaxPathSetStateCalls(alternate, context) : 0);
|
|
23036
22979
|
}
|
|
23037
22980
|
if (isNodeOfType(node, "SwitchStatement")) {
|
|
23038
22981
|
let maxRunSetters = 0;
|
|
@@ -23062,31 +23005,28 @@ const countMaxPathSetStateCalls = (node, context) => {
|
|
|
23062
23005
|
}
|
|
23063
23006
|
if (isNodeOfType(node, "CallExpression") && isSetterCall(node) && isNodeOfType(node.callee, "Identifier") && isUseStateSetterInScope(node, node.callee.name)) {
|
|
23064
23007
|
let nestedSettersInArgs = 0;
|
|
23065
|
-
for (const argument of node.arguments ?? []) nestedSettersInArgs +=
|
|
23008
|
+
for (const argument of node.arguments ?? []) nestedSettersInArgs += countMaxPathSetStateCalls(argument, context);
|
|
23066
23009
|
return 1 + nestedSettersInArgs;
|
|
23067
23010
|
}
|
|
23068
23011
|
if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier")) {
|
|
23069
23012
|
const helperFunction = context.helpersByName.get(node.callee.name);
|
|
23070
23013
|
if (helperFunction && !context.activeHelpers.has(helperFunction) && isWholesaleDelegationCall(node, context.effectCallback)) {
|
|
23071
23014
|
context.activeHelpers.add(helperFunction);
|
|
23072
|
-
let helperCount =
|
|
23015
|
+
let helperCount = countMaxPathSetStateCalls(helperFunction, context);
|
|
23073
23016
|
context.activeHelpers.delete(helperFunction);
|
|
23074
23017
|
for (const argument of node.arguments ?? []) helperCount += countMaxPathSetStateCalls(argument, context);
|
|
23075
23018
|
return helperCount;
|
|
23076
23019
|
}
|
|
23077
23020
|
}
|
|
23078
|
-
const
|
|
23079
|
-
if (isFunctionLike$1(child)) return runsOnEffectDispatch(child) ? countFunctionBodySetStateCalls(child, context) : 0;
|
|
23080
|
-
return countMaxPathSetStateCalls(child, context);
|
|
23081
|
-
};
|
|
23021
|
+
const shouldWalkChild = (child) => !isFunctionLike$1(child) || runsOnEffectDispatch(child);
|
|
23082
23022
|
let total = 0;
|
|
23083
23023
|
const nodeRecord = node;
|
|
23084
23024
|
for (const key of Object.keys(nodeRecord)) {
|
|
23085
23025
|
if (key === "parent") continue;
|
|
23086
23026
|
const child = nodeRecord[key];
|
|
23087
23027
|
if (Array.isArray(child)) {
|
|
23088
|
-
for (const item of child) if (item && typeof item === "object" && "type" in item) total +=
|
|
23089
|
-
} else if (child && typeof child === "object" && "type" in child) total +=
|
|
23028
|
+
for (const item of child) if (item && typeof item === "object" && "type" in item && shouldWalkChild(item)) total += countMaxPathSetStateCalls(item, context);
|
|
23029
|
+
} else if (child && typeof child === "object" && "type" in child && shouldWalkChild(child)) total += countMaxPathSetStateCalls(child, context);
|
|
23090
23030
|
}
|
|
23091
23031
|
return total;
|
|
23092
23032
|
};
|
|
@@ -23135,7 +23075,7 @@ const noCascadingSetState = defineRule({
|
|
|
23135
23075
|
const callback = getEffectCallback(node);
|
|
23136
23076
|
if (!callback) return;
|
|
23137
23077
|
if (isDevOnlyGuardedEffect(callback)) return;
|
|
23138
|
-
const setStateCallCount =
|
|
23078
|
+
const setStateCallCount = countMaxPathSetStateCalls(callback, {
|
|
23139
23079
|
helpersByName: collectLocalHelperFunctions(findProgramRoot(node) ?? callback),
|
|
23140
23080
|
activeHelpers: /* @__PURE__ */ new Set(),
|
|
23141
23081
|
effectCallback: callback
|
|
@@ -26353,11 +26293,7 @@ const noEffectEventInDeps = defineRule({
|
|
|
26353
26293
|
const initializer = declaratorNode.init;
|
|
26354
26294
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
|
|
26355
26295
|
if (!isHookCall$2(initializer, "useEffectEvent")) return;
|
|
26356
|
-
if (isNodeOfType(initializer.callee, "Identifier"))
|
|
26357
|
-
if (isImportedFromNonReactModule(declaratorNode, initializer.callee.name)) return;
|
|
26358
|
-
const calleeSymbol = context.scopes.referenceFor(initializer.callee)?.resolvedSymbol;
|
|
26359
|
-
if (calleeSymbol && calleeSymbol.kind !== "import") return;
|
|
26360
|
-
}
|
|
26296
|
+
if (isNodeOfType(initializer.callee, "Identifier") && isImportedFromNonReactModule(declaratorNode, initializer.callee.name)) return;
|
|
26361
26297
|
componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
|
|
26362
26298
|
} });
|
|
26363
26299
|
return {
|
|
@@ -30079,40 +30015,6 @@ const noMutableInDeps = defineRule({
|
|
|
30079
30015
|
}
|
|
30080
30016
|
});
|
|
30081
30017
|
//#endregion
|
|
30082
|
-
//#region src/plugin/utils/is-result-discarded-call.ts
|
|
30083
|
-
const isResultDiscardedCall = (callExpression) => {
|
|
30084
|
-
let node = callExpression;
|
|
30085
|
-
let parent = node.parent;
|
|
30086
|
-
while (parent) {
|
|
30087
|
-
if (isNodeOfType(parent, "ExpressionStatement")) return true;
|
|
30088
|
-
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
|
|
30089
|
-
if (isNodeOfType(parent, "ChainExpression")) {
|
|
30090
|
-
node = parent;
|
|
30091
|
-
parent = node.parent;
|
|
30092
|
-
continue;
|
|
30093
|
-
}
|
|
30094
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
|
|
30095
|
-
node = parent;
|
|
30096
|
-
parent = node.parent;
|
|
30097
|
-
continue;
|
|
30098
|
-
}
|
|
30099
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
|
|
30100
|
-
node = parent;
|
|
30101
|
-
parent = node.parent;
|
|
30102
|
-
continue;
|
|
30103
|
-
}
|
|
30104
|
-
if (isNodeOfType(parent, "SequenceExpression")) {
|
|
30105
|
-
const expressions = parent.expressions ?? [];
|
|
30106
|
-
if (expressions[expressions.length - 1] !== node) return true;
|
|
30107
|
-
node = parent;
|
|
30108
|
-
parent = node.parent;
|
|
30109
|
-
continue;
|
|
30110
|
-
}
|
|
30111
|
-
return false;
|
|
30112
|
-
}
|
|
30113
|
-
return false;
|
|
30114
|
-
};
|
|
30115
|
-
//#endregion
|
|
30116
30018
|
//#region src/plugin/rules/state-and-effects/utils/lodash-mutator-call.ts
|
|
30117
30019
|
const LODASH_MUTATOR_NAMES = new Set([
|
|
30118
30020
|
"set",
|
|
@@ -30210,6 +30112,7 @@ const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
|
30210
30112
|
"reverse",
|
|
30211
30113
|
"sort"
|
|
30212
30114
|
]);
|
|
30115
|
+
const SAME_REFERENCE_COLLECTION_RETURN_METHODS = new Set(["add", "set"]);
|
|
30213
30116
|
const OBJECT_MUTATION_METHODS = new Set([
|
|
30214
30117
|
"assign",
|
|
30215
30118
|
"defineProperties",
|
|
@@ -30283,6 +30186,7 @@ const canExpressionReturnOriginalReducerStateReference = (node, state) => {
|
|
|
30283
30186
|
const methodName = getStaticMemberPropertyName(unwrappedNode.callee);
|
|
30284
30187
|
if (methodName === "assign" && isNodeOfType(unwrappedNode.callee.object, "Identifier") && unwrappedNode.callee.object.name === "Object") return isExpressionOriginalReducerStateReference(unwrappedNode.arguments?.[0], state);
|
|
30285
30188
|
if (methodName && SAME_REFERENCE_ARRAY_RETURN_METHODS.has(methodName) && isExpressionOriginalReducerStateReference(unwrappedNode.callee.object, state)) return true;
|
|
30189
|
+
if (methodName && SAME_REFERENCE_COLLECTION_RETURN_METHODS.has(methodName) && isExpressionOriginalReducerStateReference(unwrappedNode.callee.object, state)) return true;
|
|
30286
30190
|
}
|
|
30287
30191
|
}
|
|
30288
30192
|
if (isNodeOfType(unwrappedNode, "ConditionalExpression")) return canExpressionReturnOriginalReducerStateReference(unwrappedNode.consequent, state) || canExpressionReturnOriginalReducerStateReference(unwrappedNode.alternate, state);
|
|
@@ -30321,7 +30225,6 @@ const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
|
|
|
30321
30225
|
if (!isNodeOfType(unwrappedChild.callee, "MemberExpression")) return;
|
|
30322
30226
|
const methodName = getStaticMemberPropertyName(unwrappedChild.callee);
|
|
30323
30227
|
if (!methodName || !MUTATING_ARRAY_METHODS.has(methodName) && !MUTATING_COLLECTION_METHODS.has(methodName)) return;
|
|
30324
|
-
if (MUTATING_COLLECTION_METHODS.has(methodName) && !MUTATING_ARRAY_METHODS.has(methodName) && !isResultDiscardedCall(unwrappedChild)) return;
|
|
30325
30228
|
if (isExpressionRootedInMutableReducerStateSource(unwrappedChild.callee.object, state)) mutations.push({ node: unwrappedChild });
|
|
30326
30229
|
});
|
|
30327
30230
|
return mutations;
|
|
@@ -32033,6 +31936,40 @@ const noPreventDefault = defineRule({
|
|
|
32033
31936
|
}
|
|
32034
31937
|
});
|
|
32035
31938
|
//#endregion
|
|
31939
|
+
//#region src/plugin/utils/is-result-discarded-call.ts
|
|
31940
|
+
const isResultDiscardedCall = (callExpression) => {
|
|
31941
|
+
let node = callExpression;
|
|
31942
|
+
let parent = node.parent;
|
|
31943
|
+
while (parent) {
|
|
31944
|
+
if (isNodeOfType(parent, "ExpressionStatement")) return true;
|
|
31945
|
+
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
|
|
31946
|
+
if (isNodeOfType(parent, "ChainExpression")) {
|
|
31947
|
+
node = parent;
|
|
31948
|
+
parent = node.parent;
|
|
31949
|
+
continue;
|
|
31950
|
+
}
|
|
31951
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
|
|
31952
|
+
node = parent;
|
|
31953
|
+
parent = node.parent;
|
|
31954
|
+
continue;
|
|
31955
|
+
}
|
|
31956
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
|
|
31957
|
+
node = parent;
|
|
31958
|
+
parent = node.parent;
|
|
31959
|
+
continue;
|
|
31960
|
+
}
|
|
31961
|
+
if (isNodeOfType(parent, "SequenceExpression")) {
|
|
31962
|
+
const expressions = parent.expressions ?? [];
|
|
31963
|
+
if (expressions[expressions.length - 1] !== node) return true;
|
|
31964
|
+
node = parent;
|
|
31965
|
+
parent = node.parent;
|
|
31966
|
+
continue;
|
|
31967
|
+
}
|
|
31968
|
+
return false;
|
|
31969
|
+
}
|
|
31970
|
+
return false;
|
|
31971
|
+
};
|
|
31972
|
+
//#endregion
|
|
32036
31973
|
//#region src/plugin/rules/state-and-effects/no-prop-callback-in-effect.ts
|
|
32037
31974
|
const isRefLatchGuardedEffect = (callbackBody) => {
|
|
32038
31975
|
const refNamesReadInGuards = /* @__PURE__ */ new Set();
|
|
@@ -36638,7 +36575,8 @@ const NAMED_EXPORT_MESSAGE = "This file exports non-components, so Fast Refresh
|
|
|
36638
36575
|
const ANONYMOUS_MESSAGE = "This component is unnamed, so Fast Refresh can't track it and falls back to a full reload.";
|
|
36639
36576
|
const EXPORT_ALL_MESSAGE = "`export *` hides what's exported, so Fast Refresh can't safely preserve component state.";
|
|
36640
36577
|
const REACT_CONTEXT_MESSAGE = "This file exports a context with components, so Fast Refresh can't safely preserve component state.";
|
|
36641
|
-
const
|
|
36578
|
+
const LOCAL_COMPONENT_MESSAGE = "This component is not exported, so Fast Refresh skips it and local edits can full-reload.";
|
|
36579
|
+
const NO_EXPORT_MESSAGE = "This file exports nothing, so Fast Refresh can't track the component and local edits can full-reload.";
|
|
36642
36580
|
const DEFAULT_REACT_HOCS = [
|
|
36643
36581
|
"memo",
|
|
36644
36582
|
"forwardRef",
|
|
@@ -36709,20 +36647,6 @@ const isReactComponentInitializer = (expression, state) => {
|
|
|
36709
36647
|
if (isNodeOfType(stripped, "CallExpression") && isHocCallee(stripped.callee, state) && stripped.arguments.length > 0) return true;
|
|
36710
36648
|
return false;
|
|
36711
36649
|
};
|
|
36712
|
-
const objectExpressionBundlesComponents = (objectExpression, state) => {
|
|
36713
|
-
for (const property of objectExpression.properties ?? []) {
|
|
36714
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
36715
|
-
const value = skipTsExpression(property.value);
|
|
36716
|
-
if (isNodeOfType(value, "Identifier")) {
|
|
36717
|
-
if (state.localComponentNames.has(value.name)) return true;
|
|
36718
|
-
continue;
|
|
36719
|
-
}
|
|
36720
|
-
if (!(!property.computed && isNodeOfType(property.key, "Identifier") && isReactComponentName(property.key.name))) continue;
|
|
36721
|
-
if (isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) return true;
|
|
36722
|
-
if (isNodeOfType(value, "CallExpression") && isHocCallee(value.callee, state) && value.arguments.length > 0) return true;
|
|
36723
|
-
}
|
|
36724
|
-
return false;
|
|
36725
|
-
};
|
|
36726
36650
|
const classifyExport = (name, reportNode, isFunction, initializer, state) => {
|
|
36727
36651
|
if (initializer) {
|
|
36728
36652
|
const expression = skipTsExpression(initializer);
|
|
@@ -36753,10 +36677,6 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
|
|
|
36753
36677
|
reportNode
|
|
36754
36678
|
};
|
|
36755
36679
|
}
|
|
36756
|
-
if (isNodeOfType(stripped, "ObjectExpression") && objectExpressionBundlesComponents(stripped, state)) return {
|
|
36757
|
-
kind: "namespace-object",
|
|
36758
|
-
reportNode
|
|
36759
|
-
};
|
|
36760
36680
|
if (NOT_REACT_COMPONENT_EXPRESSION_TYPES.has(stripped.type)) return {
|
|
36761
36681
|
kind: "non-component",
|
|
36762
36682
|
reportNode
|
|
@@ -36773,7 +36693,7 @@ const collectRelevantNodes = (programRoot) => {
|
|
|
36773
36693
|
walkAst(programRoot, (child) => {
|
|
36774
36694
|
const childType = child.type;
|
|
36775
36695
|
if (childType === "ExportAllDeclaration" || childType === "ExportDefaultDeclaration" || childType === "ExportNamedDeclaration") exportNodes.push(child);
|
|
36776
|
-
else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator"
|
|
36696
|
+
else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator") componentCandidates.push(child);
|
|
36777
36697
|
});
|
|
36778
36698
|
return {
|
|
36779
36699
|
exportNodes,
|
|
@@ -36818,31 +36738,18 @@ const onlyExportComponents = defineRule({
|
|
|
36818
36738
|
category: "Architecture",
|
|
36819
36739
|
create: (context) => {
|
|
36820
36740
|
const settings = resolveSettings$6(context.settings);
|
|
36741
|
+
const state = {
|
|
36742
|
+
customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
|
|
36743
|
+
allowExportNames: new Set(settings.allowExportNames),
|
|
36744
|
+
allowConstantExport: settings.allowConstantExport
|
|
36745
|
+
};
|
|
36821
36746
|
return { Program(node) {
|
|
36822
36747
|
if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
|
|
36823
36748
|
const { exportNodes, componentCandidates } = collectRelevantNodes(node);
|
|
36824
|
-
const localComponentNames = /* @__PURE__ */ new Set();
|
|
36825
|
-
const state = {
|
|
36826
|
-
customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
|
|
36827
|
-
allowExportNames: new Set(settings.allowExportNames),
|
|
36828
|
-
allowConstantExport: settings.allowConstantExport,
|
|
36829
|
-
localComponentNames
|
|
36830
|
-
};
|
|
36831
|
-
for (const child of componentCandidates) {
|
|
36832
|
-
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
36833
|
-
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
36834
|
-
}
|
|
36835
|
-
if (isNodeOfType(child, "ClassDeclaration") && child.id) {
|
|
36836
|
-
if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
36837
|
-
}
|
|
36838
|
-
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
36839
|
-
const initializer = child.init;
|
|
36840
|
-
if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
36841
|
-
}
|
|
36842
|
-
}
|
|
36843
36749
|
const exports = [];
|
|
36844
36750
|
let hasReactExport = false;
|
|
36845
36751
|
let hasAnyExports = false;
|
|
36752
|
+
const localComponents = [];
|
|
36846
36753
|
const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
|
|
36847
36754
|
for (const child of exportNodes) {
|
|
36848
36755
|
if (isNodeOfType(child, "ExportAllDeclaration")) {
|
|
@@ -36913,14 +36820,7 @@ const onlyExportComponents = defineRule({
|
|
|
36913
36820
|
});
|
|
36914
36821
|
continue;
|
|
36915
36822
|
}
|
|
36916
|
-
if (isNodeOfType(stripped, "ObjectExpression")) {
|
|
36917
|
-
context.report({
|
|
36918
|
-
node: stripped,
|
|
36919
|
-
message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
|
|
36920
|
-
});
|
|
36921
|
-
continue;
|
|
36922
|
-
}
|
|
36923
|
-
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
|
|
36823
|
+
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "Literal")) {
|
|
36924
36824
|
context.report({
|
|
36925
36825
|
node: stripped,
|
|
36926
36826
|
message: ANONYMOUS_MESSAGE
|
|
@@ -36973,23 +36873,32 @@ const onlyExportComponents = defineRule({
|
|
|
36973
36873
|
let entry;
|
|
36974
36874
|
if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
|
|
36975
36875
|
else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
|
|
36976
|
-
else {
|
|
36977
|
-
|
|
36978
|
-
|
|
36979
|
-
|
|
36980
|
-
};
|
|
36981
|
-
if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
|
|
36982
|
-
}
|
|
36876
|
+
else entry = {
|
|
36877
|
+
kind: "non-component",
|
|
36878
|
+
reportNode
|
|
36879
|
+
};
|
|
36983
36880
|
if (isReExportFromSource && entry.kind !== "react-component") continue;
|
|
36984
36881
|
exports.push(entry);
|
|
36985
36882
|
}
|
|
36986
36883
|
}
|
|
36987
36884
|
}
|
|
36988
36885
|
for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
|
|
36989
|
-
|
|
36990
|
-
node
|
|
36991
|
-
|
|
36992
|
-
|
|
36886
|
+
const isInsideExport = (node) => {
|
|
36887
|
+
let walker = node.parent;
|
|
36888
|
+
while (walker) {
|
|
36889
|
+
if (isNodeOfType(walker, "ExportNamedDeclaration") || isNodeOfType(walker, "ExportDefaultDeclaration") || isNodeOfType(walker, "ExportAllDeclaration")) return true;
|
|
36890
|
+
walker = walker.parent ?? null;
|
|
36891
|
+
}
|
|
36892
|
+
return false;
|
|
36893
|
+
};
|
|
36894
|
+
for (const child of componentCandidates) {
|
|
36895
|
+
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
36896
|
+
if (isReactComponentName(child.id.name) && !isInsideExport(child) && !isInsideFunctionScope(child)) localComponents.push(child.id);
|
|
36897
|
+
}
|
|
36898
|
+
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
36899
|
+
if (isReactComponentName(child.id.name) && canBeReactFunctionComponent(child.init, state) && !isInsideExport(child) && !isInsideFunctionScope(child)) localComponents.push(child.id);
|
|
36900
|
+
}
|
|
36901
|
+
}
|
|
36993
36902
|
if (hasAnyExports && hasReactExport) for (const entry of exports) {
|
|
36994
36903
|
if (entry.kind === "non-component") context.report({
|
|
36995
36904
|
node: entry.reportNode,
|
|
@@ -37000,6 +36909,14 @@ const onlyExportComponents = defineRule({
|
|
|
37000
36909
|
message: REACT_CONTEXT_MESSAGE
|
|
37001
36910
|
});
|
|
37002
36911
|
}
|
|
36912
|
+
else if (hasAnyExports && !hasReactExport && localComponents.length > 0) for (const local of localComponents) context.report({
|
|
36913
|
+
node: local,
|
|
36914
|
+
message: LOCAL_COMPONENT_MESSAGE
|
|
36915
|
+
});
|
|
36916
|
+
else if (!hasAnyExports && localComponents.length > 0) for (const local of localComponents) context.report({
|
|
36917
|
+
node: local,
|
|
36918
|
+
message: NO_EXPORT_MESSAGE
|
|
36919
|
+
});
|
|
37003
36920
|
} };
|
|
37004
36921
|
}
|
|
37005
36922
|
});
|
|
@@ -37797,8 +37714,8 @@ const preferHtmlDialog = defineRule({
|
|
|
37797
37714
|
if (!HTML_TAGS.has(tagName)) return;
|
|
37798
37715
|
const roleAttribute = findJsxAttribute(node.attributes, "role");
|
|
37799
37716
|
if (roleAttribute) {
|
|
37800
|
-
const
|
|
37801
|
-
if (
|
|
37717
|
+
const roleValue = getJsxPropStringValue(roleAttribute);
|
|
37718
|
+
if (roleValue !== null && ROLE_DIALOG_VALUES.has(roleValue)) {
|
|
37802
37719
|
if (focusTrapSignals && isElementFocusTrapped(node, focusTrapSignals)) return;
|
|
37803
37720
|
const ariaModalAttribute = findJsxAttribute(node.attributes, "aria-modal");
|
|
37804
37721
|
const isModal = ariaModalAttribute ? isAriaModalTrue(ariaModalAttribute) : false;
|
|
@@ -40787,7 +40704,6 @@ const rerenderLazyRefInit = defineRule({
|
|
|
40787
40704
|
const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
|
|
40788
40705
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
|
|
40789
40706
|
if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
|
|
40790
|
-
if (isNewCall && TRIVIAL_CONSTRUCTOR_NAMES.has(calleeName)) return;
|
|
40791
40707
|
if (isPlainCall && isReactHookName(calleeName)) return;
|
|
40792
40708
|
const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
|
|
40793
40709
|
context.report({
|
|
@@ -40820,6 +40736,18 @@ const TRIVIAL_DATE_GETTER_NAMES = new Set([
|
|
|
40820
40736
|
"getUTCMilliseconds",
|
|
40821
40737
|
"valueOf"
|
|
40822
40738
|
]);
|
|
40739
|
+
const TRIVIAL_CONSTRUCTOR_NAMES = new Set([
|
|
40740
|
+
"Date",
|
|
40741
|
+
"Map",
|
|
40742
|
+
"Set",
|
|
40743
|
+
"WeakMap",
|
|
40744
|
+
"WeakSet",
|
|
40745
|
+
"RegExp",
|
|
40746
|
+
"Error",
|
|
40747
|
+
"URL",
|
|
40748
|
+
"URLSearchParams",
|
|
40749
|
+
"AbortController"
|
|
40750
|
+
]);
|
|
40823
40751
|
const EAGER_CALL_RESOLUTION_DEPTH_LIMIT = 4;
|
|
40824
40752
|
const findEagerInitializerCall = (expression, depth = 0) => {
|
|
40825
40753
|
if (depth > EAGER_CALL_RESOLUTION_DEPTH_LIMIT) return null;
|
|
@@ -41563,7 +41491,7 @@ const rnBottomSheetPreferNative = defineRule({
|
|
|
41563
41491
|
});
|
|
41564
41492
|
//#endregion
|
|
41565
41493
|
//#region src/plugin/rules/react-native/rn-detox-missing-await.ts
|
|
41566
|
-
const EMPTY_VISITORS$
|
|
41494
|
+
const EMPTY_VISITORS$4 = {};
|
|
41567
41495
|
const DETOX_TEST_FILE = /(\.e2e\.[cm]?[jt]sx?$)|((^|\/)e2e\/)/;
|
|
41568
41496
|
const DETOX_ELEMENT_ACTIONS = new Set([
|
|
41569
41497
|
"tap",
|
|
@@ -41623,7 +41551,7 @@ const rnDetoxMissingAwait = defineRule({
|
|
|
41623
41551
|
recommendation: "Prepend `await` to Detox actions, `waitFor(...)` chains, and `expect(element(...))` assertions. They return promises tied to Detox's synchronization, so a missing await runs steps out of order.",
|
|
41624
41552
|
create: (context) => {
|
|
41625
41553
|
const filename = normalizeFilename(context.filename ?? "");
|
|
41626
|
-
if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$
|
|
41554
|
+
if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$4;
|
|
41627
41555
|
return { ExpressionStatement(node) {
|
|
41628
41556
|
const expression = node.expression;
|
|
41629
41557
|
if (!isNodeOfType(expression, "CallExpression")) return;
|
|
@@ -42598,7 +42526,7 @@ const isLegacyArchReactNativeFile = (filename) => {
|
|
|
42598
42526
|
};
|
|
42599
42527
|
//#endregion
|
|
42600
42528
|
//#region src/plugin/rules/react-native/rn-no-legacy-shadow-styles.ts
|
|
42601
|
-
const EMPTY_VISITORS$
|
|
42529
|
+
const EMPTY_VISITORS$3 = {};
|
|
42602
42530
|
const reportLegacyShadowProperties = (objectExpression, context) => {
|
|
42603
42531
|
const legacyShadowPropertyNames = [];
|
|
42604
42532
|
for (const property of objectExpression.properties ?? []) {
|
|
@@ -42621,7 +42549,7 @@ const rnNoLegacyShadowStyles = defineRule({
|
|
|
42621
42549
|
severity: "warn",
|
|
42622
42550
|
recommendation: "Use `boxShadow` for shadows that work on both platforms on the new architecture, instead of platform-specific shadow properties.",
|
|
42623
42551
|
create: (context) => {
|
|
42624
|
-
if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$
|
|
42552
|
+
if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$3;
|
|
42625
42553
|
return {
|
|
42626
42554
|
JSXAttribute(node) {
|
|
42627
42555
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -43542,7 +43470,7 @@ const isExpoManagedFileActive = (context) => {
|
|
|
43542
43470
|
};
|
|
43543
43471
|
//#endregion
|
|
43544
43472
|
//#region src/plugin/rules/react-native/rn-prefer-expo-image.ts
|
|
43545
|
-
const EMPTY_VISITORS$
|
|
43473
|
+
const EMPTY_VISITORS$2 = {};
|
|
43546
43474
|
const BUNDLED_ASSET_SOURCE_PATTERN = /\.(?:png|jpe?g|gif|webp|bmp)$/i;
|
|
43547
43475
|
const MEMBER_PATH_FAN_OUT_LIMIT = 32;
|
|
43548
43476
|
const isRequireOfBundledAsset = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "require" && node.arguments?.length === 1 && isNodeOfType(node.arguments[0], "Literal") && typeof node.arguments[0].value === "string" && BUNDLED_ASSET_SOURCE_PATTERN.test(node.arguments[0].value);
|
|
@@ -43576,7 +43504,7 @@ const rnPreferExpoImage = defineRule({
|
|
|
43576
43504
|
severity: "warn",
|
|
43577
43505
|
recommendation: "Use `<Image>` from `expo-image` instead of `react-native`. Same props, plus caching, placeholders, and crossfades for faster image loading.",
|
|
43578
43506
|
create: (context) => {
|
|
43579
|
-
if (!isExpoManagedFileActive(context)) return EMPTY_VISITORS$
|
|
43507
|
+
if (!isExpoManagedFileActive(context)) return EMPTY_VISITORS$2;
|
|
43580
43508
|
const flaggedImports = [];
|
|
43581
43509
|
const assetBindingNames = /* @__PURE__ */ new Set();
|
|
43582
43510
|
const moduleObjectLiterals = /* @__PURE__ */ new Map();
|
|
@@ -44063,7 +43991,7 @@ const rnScrollviewFlexInContentContainer = defineRule({
|
|
|
44063
43991
|
});
|
|
44064
43992
|
//#endregion
|
|
44065
43993
|
//#region src/plugin/rules/react-native/rn-style-prefer-box-shadow.ts
|
|
44066
|
-
const EMPTY_VISITORS$
|
|
43994
|
+
const EMPTY_VISITORS$1 = {};
|
|
44067
43995
|
const IOS_SHADOW_KEYS = new Set([
|
|
44068
43996
|
"shadowColor",
|
|
44069
43997
|
"shadowOffset",
|
|
@@ -44149,7 +44077,7 @@ const rnStylePreferBoxShadow = defineRule({
|
|
|
44149
44077
|
severity: "warn",
|
|
44150
44078
|
recommendation: "These shadow keys only work on one platform. On RN v7+, use the CSS `boxShadow` string instead, like `boxShadow: \"0 2px 8px rgba(0,0,0,0.1)\"`, which works on both.",
|
|
44151
44079
|
create: (context) => {
|
|
44152
|
-
if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$
|
|
44080
|
+
if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$1;
|
|
44153
44081
|
return {
|
|
44154
44082
|
JSXAttribute(node) {
|
|
44155
44083
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -44246,9 +44174,9 @@ const roleHasRequiredAriaProps = defineRule({
|
|
|
44246
44174
|
if (!HTML_TAGS.has(elementType)) return;
|
|
44247
44175
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
44248
44176
|
if (!roleAttribute) return;
|
|
44249
|
-
const
|
|
44250
|
-
if (
|
|
44251
|
-
const roles =
|
|
44177
|
+
const roleValue = getJsxPropStringValue(roleAttribute);
|
|
44178
|
+
if (roleValue === null) return;
|
|
44179
|
+
const roles = roleValue.split(/\s+/).filter((token) => token.length > 0);
|
|
44252
44180
|
for (const role of roles) {
|
|
44253
44181
|
const required = ROLE_REQUIRED_PROPS.get(role);
|
|
44254
44182
|
if (!required) continue;
|
|
@@ -47365,9 +47293,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
47365
47293
|
};
|
|
47366
47294
|
//#endregion
|
|
47367
47295
|
//#region src/plugin/rules/a11y/role-supports-aria-props.ts
|
|
47368
|
-
const buildMessageDefault = (
|
|
47369
|
-
return `Screen reader users get no help from \`${propName}\` because role ${roles.map((role) => `\`${role}\``).join(" / ")} ignores it, so remove it or change the role.`;
|
|
47370
|
-
};
|
|
47296
|
+
const buildMessageDefault = (role, propName) => `Screen reader users get no help from \`${propName}\` because role \`${role}\` ignores it, so remove it or change the role.`;
|
|
47371
47297
|
const buildMessageImplicit = (role, propName, elementType) => `Screen reader users get no help from \`${propName}\` because \`${elementType}\` has role \`${role}\`, which ignores it, so remove \`${propName}\` or change the element.`;
|
|
47372
47298
|
const roleSupportsAriaProps = defineRule({
|
|
47373
47299
|
id: "role-supports-aria-props",
|
|
@@ -47395,21 +47321,17 @@ const roleSupportsAriaProps = defineRule({
|
|
|
47395
47321
|
if (!ariaAttributes) return;
|
|
47396
47322
|
const elementType = getElementType(node, context.settings);
|
|
47397
47323
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
47398
|
-
const
|
|
47399
|
-
if (
|
|
47400
|
-
|
|
47401
|
-
for (const role of roleCandidates) {
|
|
47402
|
-
if (!VALID_ARIA_ROLES.has(role)) return;
|
|
47403
|
-
const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
|
|
47404
|
-
if (!supported) return;
|
|
47405
|
-
supportedSets.push(supported);
|
|
47406
|
-
}
|
|
47324
|
+
const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : getImplicitRole(node, elementType);
|
|
47325
|
+
if (!role) return;
|
|
47326
|
+
if (!VALID_ARIA_ROLES.has(role)) return;
|
|
47407
47327
|
const isImplicit = !roleAttribute;
|
|
47328
|
+
const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
|
|
47329
|
+
if (!supported) return;
|
|
47408
47330
|
for (const { attribute, propName } of ariaAttributes) {
|
|
47409
|
-
if (
|
|
47331
|
+
if (supported.has(propName)) continue;
|
|
47410
47332
|
context.report({
|
|
47411
47333
|
node: attribute,
|
|
47412
|
-
message: isImplicit ? buildMessageImplicit(
|
|
47334
|
+
message: isImplicit ? buildMessageImplicit(role, propName, elementType) : buildMessageDefault(role, propName)
|
|
47413
47335
|
});
|
|
47414
47336
|
}
|
|
47415
47337
|
} })
|
|
@@ -47761,15 +47683,11 @@ const isUseEffectEventSymbol = (symbol) => {
|
|
|
47761
47683
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
47762
47684
|
return getHookNameFromCallee(initializer.callee) === "useEffectEvent";
|
|
47763
47685
|
};
|
|
47764
|
-
const
|
|
47765
|
-
|
|
47766
|
-
return Boolean(symbol && symbol.kind !== "import");
|
|
47767
|
-
};
|
|
47768
|
-
const isNonReactEffectEventCallee = (callee, contextNode, scopes) => isNodeOfType(callee, "Identifier") && (isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes));
|
|
47769
|
-
const isNonReactEffectEventSymbol = (symbol, contextNode, scopes) => {
|
|
47686
|
+
const isNonReactEffectEventCallee = (callee, contextNode) => isNodeOfType(callee, "Identifier") && isImportedFromNonReactModule(contextNode, callee.name);
|
|
47687
|
+
const isNonReactEffectEventSymbol = (symbol, contextNode) => {
|
|
47770
47688
|
const initializer = symbol.initializer;
|
|
47771
47689
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
47772
|
-
return isNonReactEffectEventCallee(initializer.callee, contextNode
|
|
47690
|
+
return isNonReactEffectEventCallee(initializer.callee, contextNode);
|
|
47773
47691
|
};
|
|
47774
47692
|
const findEnclosingComponentOrHookFunction = (node) => {
|
|
47775
47693
|
let current = node.parent;
|
|
@@ -47848,7 +47766,7 @@ const rulesOfHooks = defineRule({
|
|
|
47848
47766
|
const hookContext = isHookCall$1(node, context.scopes, settings);
|
|
47849
47767
|
if (!hookContext) return;
|
|
47850
47768
|
const { hookName } = hookContext;
|
|
47851
|
-
if (hookName === "useEffectEvent" && !isUseEffectEventInitializer(node) && !isNonReactEffectEventCallee(node.callee, node
|
|
47769
|
+
if (hookName === "useEffectEvent" && !isUseEffectEventInitializer(node) && !isNonReactEffectEventCallee(node.callee, node)) {
|
|
47852
47770
|
context.report({
|
|
47853
47771
|
node: node.callee,
|
|
47854
47772
|
message: buildEffectEventPassedDownMessage()
|
|
@@ -47961,7 +47879,7 @@ const rulesOfHooks = defineRule({
|
|
|
47961
47879
|
Identifier(node) {
|
|
47962
47880
|
const symbol = context.scopes.referenceFor(node)?.resolvedSymbol;
|
|
47963
47881
|
if (!symbol || !isUseEffectEventSymbol(symbol)) return;
|
|
47964
|
-
if (isNonReactEffectEventSymbol(symbol, node
|
|
47882
|
+
if (isNonReactEffectEventSymbol(symbol, node)) return;
|
|
47965
47883
|
if (!isSameComponentOrHookScope(symbol, node)) return;
|
|
47966
47884
|
if (isInsideAllowedEffectEventCallback(node, additionalEffectHooksRegex)) return;
|
|
47967
47885
|
context.report({
|
|
@@ -55533,34 +55451,6 @@ const reactDoctorRules = [
|
|
|
55533
55451
|
];
|
|
55534
55452
|
const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id, rule.rule]));
|
|
55535
55453
|
//#endregion
|
|
55536
|
-
//#region src/plugin/utils/is-next-file.ts
|
|
55537
|
-
const isNextFileActive = (context) => {
|
|
55538
|
-
const rawFilename = context.filename;
|
|
55539
|
-
if (!rawFilename) return true;
|
|
55540
|
-
const filename = normalizeFilename(rawFilename);
|
|
55541
|
-
const manifest = readNearestPackageManifest(filename);
|
|
55542
|
-
if (!manifest) return true;
|
|
55543
|
-
if (declaresDependency(manifest, "next")) return true;
|
|
55544
|
-
if (!declaresAnyDependency(manifest)) return true;
|
|
55545
|
-
const packageDirectory = findNearestPackageDirectory(filename);
|
|
55546
|
-
const rootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
|
|
55547
|
-
if (packageDirectory !== null && isPackageNestedBelowProjectRoot(packageDirectory, rootDirectory)) return false;
|
|
55548
|
-
return true;
|
|
55549
|
-
};
|
|
55550
|
-
//#endregion
|
|
55551
|
-
//#region src/plugin/utils/wrap-nextjs-rule.ts
|
|
55552
|
-
const EMPTY_VISITORS$1 = {};
|
|
55553
|
-
const wrapNextjsRule = (rule) => {
|
|
55554
|
-
const innerCreate = rule.create.bind(rule);
|
|
55555
|
-
return {
|
|
55556
|
-
...rule,
|
|
55557
|
-
create: (context) => {
|
|
55558
|
-
if (!isNextFileActive(context)) return EMPTY_VISITORS$1;
|
|
55559
|
-
return innerCreate(context);
|
|
55560
|
-
}
|
|
55561
|
-
};
|
|
55562
|
-
};
|
|
55563
|
-
//#endregion
|
|
55564
55454
|
//#region src/plugin/utils/wrap-react-native-rule.ts
|
|
55565
55455
|
const EMPTY_VISITORS = {};
|
|
55566
55456
|
const wrapReactNativeRule = (rule) => {
|
|
@@ -56030,14 +55920,9 @@ const wrapWithSemanticContext = (rule) => ({
|
|
|
56030
55920
|
});
|
|
56031
55921
|
//#endregion
|
|
56032
55922
|
//#region src/plugin/react-doctor-plugin.ts
|
|
56033
|
-
const applyFrameworkGate = (rule) => {
|
|
56034
|
-
if (rule.framework === "react-native") return wrapReactNativeRule(rule);
|
|
56035
|
-
if (rule.framework === "nextjs") return wrapNextjsRule(rule);
|
|
56036
|
-
return rule;
|
|
56037
|
-
};
|
|
56038
55923
|
const applyFrameworkRuleWrappers = (registry) => {
|
|
56039
55924
|
const wrapped = {};
|
|
56040
|
-
for (const [ruleId, rule] of Object.entries(registry)) wrapped[ruleId] = wrapWithSemanticContext(
|
|
55925
|
+
for (const [ruleId, rule] of Object.entries(registry)) wrapped[ruleId] = wrapWithSemanticContext(rule.framework === "react-native" ? wrapReactNativeRule(rule) : rule);
|
|
56041
55926
|
return wrapped;
|
|
56042
55927
|
};
|
|
56043
55928
|
const plugin = {
|