oxlint-plugin-react-doctor 0.7.3-dev.f91ede7 → 0.7.3-dev.fd2db30
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.d.ts +0 -138
- package/dist/index.js +162 -1097
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -791,7 +791,7 @@ const findProgramRoot = (node) => {
|
|
|
791
791
|
};
|
|
792
792
|
//#endregion
|
|
793
793
|
//#region src/plugin/utils/get-imported-name.ts
|
|
794
|
-
const getImportedName = (importSpecifier) => {
|
|
794
|
+
const getImportedName$1 = (importSpecifier) => {
|
|
795
795
|
if (!isNodeOfType(importSpecifier, "ImportSpecifier")) return void 0;
|
|
796
796
|
const imported = importSpecifier.imported;
|
|
797
797
|
if (isNodeOfType(imported, "Identifier")) return imported.name;
|
|
@@ -913,7 +913,7 @@ const activityWrapsEffectHeavySubtree = defineRule({
|
|
|
913
913
|
continue;
|
|
914
914
|
}
|
|
915
915
|
if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
|
|
916
|
-
const importedName = getImportedName(specifier);
|
|
916
|
+
const importedName = getImportedName$1(specifier);
|
|
917
917
|
if (!importedName || !ACTIVITY_IMPORTED_NAMES.has(importedName)) continue;
|
|
918
918
|
if (isNodeOfType(specifier.local, "Identifier")) localActivityNames.add(specifier.local.name);
|
|
919
919
|
}
|
|
@@ -2215,7 +2215,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2215
2215
|
});
|
|
2216
2216
|
//#endregion
|
|
2217
2217
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
2218
|
-
const MESSAGE$
|
|
2218
|
+
const MESSAGE$60 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
2219
2219
|
const isTransComponentsTemplate = (node) => {
|
|
2220
2220
|
let current = node.parent;
|
|
2221
2221
|
while (current) {
|
|
@@ -2251,7 +2251,7 @@ const anchorHasContent = defineRule({
|
|
|
2251
2251
|
if (isTransComponentsTemplate(node)) return;
|
|
2252
2252
|
context.report({
|
|
2253
2253
|
node: opening.name,
|
|
2254
|
-
message: MESSAGE$
|
|
2254
|
+
message: MESSAGE$60
|
|
2255
2255
|
});
|
|
2256
2256
|
} };
|
|
2257
2257
|
}
|
|
@@ -2682,7 +2682,7 @@ const parseJsxValue = (value) => {
|
|
|
2682
2682
|
};
|
|
2683
2683
|
//#endregion
|
|
2684
2684
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2685
|
-
const MESSAGE$
|
|
2685
|
+
const MESSAGE$59 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2686
2686
|
const mayBeContentEditable = (node) => {
|
|
2687
2687
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
|
|
2688
2688
|
if (!attribute) return false;
|
|
@@ -2713,7 +2713,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2713
2713
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2714
2714
|
context.report({
|
|
2715
2715
|
node: node.name,
|
|
2716
|
-
message: MESSAGE$
|
|
2716
|
+
message: MESSAGE$59
|
|
2717
2717
|
});
|
|
2718
2718
|
return;
|
|
2719
2719
|
}
|
|
@@ -2721,7 +2721,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2721
2721
|
if (mayBeContentEditable(node)) return;
|
|
2722
2722
|
context.report({
|
|
2723
2723
|
node: node.name,
|
|
2724
|
-
message: MESSAGE$
|
|
2724
|
+
message: MESSAGE$59
|
|
2725
2725
|
});
|
|
2726
2726
|
} })
|
|
2727
2727
|
});
|
|
@@ -5139,7 +5139,7 @@ const asyncParallel = defineRule({
|
|
|
5139
5139
|
});
|
|
5140
5140
|
//#endregion
|
|
5141
5141
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
5142
|
-
const MESSAGE$
|
|
5142
|
+
const MESSAGE$58 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
|
|
5143
5143
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
5144
5144
|
const STORAGE_GLOBALS = new Set([
|
|
5145
5145
|
"window",
|
|
@@ -5200,7 +5200,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5200
5200
|
if (keyString === null || !isAuthCredentialKey(keyString)) return;
|
|
5201
5201
|
context.report({
|
|
5202
5202
|
node,
|
|
5203
|
-
message: MESSAGE$
|
|
5203
|
+
message: MESSAGE$58
|
|
5204
5204
|
});
|
|
5205
5205
|
},
|
|
5206
5206
|
AssignmentExpression(node) {
|
|
@@ -5211,7 +5211,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5211
5211
|
if (!propertyName || !isAuthCredentialKey(propertyName)) return;
|
|
5212
5212
|
context.report({
|
|
5213
5213
|
node: target,
|
|
5214
|
-
message: MESSAGE$
|
|
5214
|
+
message: MESSAGE$58
|
|
5215
5215
|
});
|
|
5216
5216
|
}
|
|
5217
5217
|
}))
|
|
@@ -5350,7 +5350,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
|
|
|
5350
5350
|
const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
|
|
5351
5351
|
const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
|
|
5352
5352
|
const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
|
|
5353
|
-
const MESSAGE$
|
|
5353
|
+
const MESSAGE$57 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
|
|
5354
5354
|
const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
|
|
5355
5355
|
const scanWorkflowContent = (content) => {
|
|
5356
5356
|
const lines = content.split("\n");
|
|
@@ -5395,7 +5395,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5395
5395
|
const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
|
|
5396
5396
|
const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
|
|
5397
5397
|
return [{
|
|
5398
|
-
message: MESSAGE$
|
|
5398
|
+
message: MESSAGE$57,
|
|
5399
5399
|
line: step.startLineIndex + installLineOffset + 1,
|
|
5400
5400
|
column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
|
|
5401
5401
|
}];
|
|
@@ -5405,7 +5405,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5405
5405
|
const scanNonWorkflowConfig = scanByPattern({
|
|
5406
5406
|
shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
|
|
5407
5407
|
pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
|
|
5408
|
-
message: MESSAGE$
|
|
5408
|
+
message: MESSAGE$57
|
|
5409
5409
|
});
|
|
5410
5410
|
const scan = (file) => {
|
|
5411
5411
|
if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
|
|
@@ -5945,7 +5945,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
5945
5945
|
};
|
|
5946
5946
|
//#endregion
|
|
5947
5947
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
5948
|
-
const MESSAGE$
|
|
5948
|
+
const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
5949
5949
|
const KEY_HANDLERS = [
|
|
5950
5950
|
"onKeyUp",
|
|
5951
5951
|
"onKeyDown",
|
|
@@ -6114,7 +6114,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6114
6114
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
6115
6115
|
context.report({
|
|
6116
6116
|
node: node.name,
|
|
6117
|
-
message: MESSAGE$
|
|
6117
|
+
message: MESSAGE$56
|
|
6118
6118
|
});
|
|
6119
6119
|
} };
|
|
6120
6120
|
}
|
|
@@ -6343,7 +6343,7 @@ const getClassNameLiteral = (classAttribute) => {
|
|
|
6343
6343
|
};
|
|
6344
6344
|
//#endregion
|
|
6345
6345
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
6346
|
-
const MESSAGE$
|
|
6346
|
+
const MESSAGE$55 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
6347
6347
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
6348
6348
|
"td",
|
|
6349
6349
|
"th",
|
|
@@ -6781,7 +6781,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
6781
6781
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
6782
6782
|
context.report({
|
|
6783
6783
|
node: candidate.opening,
|
|
6784
|
-
message: MESSAGE$
|
|
6784
|
+
message: MESSAGE$55
|
|
6785
6785
|
});
|
|
6786
6786
|
}
|
|
6787
6787
|
}
|
|
@@ -7288,7 +7288,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
7288
7288
|
});
|
|
7289
7289
|
//#endregion
|
|
7290
7290
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
7291
|
-
const MESSAGE$
|
|
7291
|
+
const MESSAGE$54 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
7292
7292
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
7293
7293
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
7294
7294
|
"aria-label",
|
|
@@ -7313,7 +7313,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
7313
7313
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
7314
7314
|
context.report({
|
|
7315
7315
|
node: node.name,
|
|
7316
|
-
message: MESSAGE$
|
|
7316
|
+
message: MESSAGE$54
|
|
7317
7317
|
});
|
|
7318
7318
|
} };
|
|
7319
7319
|
}
|
|
@@ -7353,7 +7353,7 @@ const isEs6Component = (node) => {
|
|
|
7353
7353
|
};
|
|
7354
7354
|
//#endregion
|
|
7355
7355
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
7356
|
-
const MESSAGE$
|
|
7356
|
+
const MESSAGE$53 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
7357
7357
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
7358
7358
|
"observer",
|
|
7359
7359
|
"lazy",
|
|
@@ -7546,7 +7546,7 @@ const displayName = defineRule({
|
|
|
7546
7546
|
const reportAt = (node) => {
|
|
7547
7547
|
context.report({
|
|
7548
7548
|
node,
|
|
7549
|
-
message: MESSAGE$
|
|
7549
|
+
message: MESSAGE$53
|
|
7550
7550
|
});
|
|
7551
7551
|
};
|
|
7552
7552
|
return {
|
|
@@ -7662,744 +7662,6 @@ const displayName = defineRule({
|
|
|
7662
7662
|
}
|
|
7663
7663
|
});
|
|
7664
7664
|
//#endregion
|
|
7665
|
-
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
7666
|
-
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
7667
|
-
if (!isNodeOfType(node, "Property")) return null;
|
|
7668
|
-
if (node.computed) {
|
|
7669
|
-
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
7670
|
-
return null;
|
|
7671
|
-
}
|
|
7672
|
-
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
7673
|
-
if (isNodeOfType(node.key, "Literal")) {
|
|
7674
|
-
if (typeof node.key.value === "string") return node.key.value;
|
|
7675
|
-
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
7676
|
-
}
|
|
7677
|
-
return null;
|
|
7678
|
-
};
|
|
7679
|
-
//#endregion
|
|
7680
|
-
//#region src/plugin/utils/read-static-boolean.ts
|
|
7681
|
-
const readStaticBoolean = (node) => {
|
|
7682
|
-
if (!node) return null;
|
|
7683
|
-
const unwrappedNode = stripParenExpression(node);
|
|
7684
|
-
if (!isNodeOfType(unwrappedNode, "Literal") || typeof unwrappedNode.value !== "boolean") return null;
|
|
7685
|
-
return unwrappedNode.value;
|
|
7686
|
-
};
|
|
7687
|
-
//#endregion
|
|
7688
|
-
//#region src/plugin/utils/is-react-api-call.ts
|
|
7689
|
-
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
7690
|
-
const isImportedFromReact = (symbol) => {
|
|
7691
|
-
if (symbol.kind !== "import") return false;
|
|
7692
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
7693
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react");
|
|
7694
|
-
};
|
|
7695
|
-
const isNamedReactApiImport = (identifier, apiNames, scopes) => {
|
|
7696
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
7697
|
-
const symbol = scopes.symbolFor(identifier);
|
|
7698
|
-
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
7699
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
7700
|
-
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
7701
|
-
};
|
|
7702
|
-
const isReactNamespaceImport = (identifier, scopes) => {
|
|
7703
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
7704
|
-
const symbol = scopes.symbolFor(identifier);
|
|
7705
|
-
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
7706
|
-
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier");
|
|
7707
|
-
};
|
|
7708
|
-
const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
7709
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7710
|
-
const callee = stripParenExpression(node.callee);
|
|
7711
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
7712
|
-
if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
|
|
7713
|
-
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
7714
|
-
}
|
|
7715
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
|
|
7716
|
-
if (isReactNamespaceImport(callee.object, scopes)) return true;
|
|
7717
|
-
return Boolean(options.allowGlobalReactNamespace && callee.object.name === "React" && scopes.isGlobalReference(callee.object));
|
|
7718
|
-
};
|
|
7719
|
-
//#endregion
|
|
7720
|
-
//#region src/plugin/rules/state-and-effects/utils/build-listener-cleanup-mismatch-message.ts
|
|
7721
|
-
const buildListenerCleanupMismatchMessage = (eventName, registrationCapture, removalCapture, callbackComparison) => {
|
|
7722
|
-
const hasCallbackMismatch = callbackComparison === "different";
|
|
7723
|
-
if (hasCallbackMismatch && registrationCapture !== removalCapture) return `The cleanup removes \`${eventName}\` with a different callback binding and capture ${String(removalCapture)}, but it was registered with capture ${String(registrationCapture)}. Pass the same callback binding and capture flag to both EventTarget calls.`;
|
|
7724
|
-
if (hasCallbackMismatch) return `The cleanup removes \`${eventName}\` with a different callback binding than the one registered, so \`removeEventListener\` cannot detach that listener. Pass the same callback binding to both calls.`;
|
|
7725
|
-
return `The cleanup removes \`${eventName}\` with capture ${String(removalCapture)}, but it was registered with capture ${String(registrationCapture)}. \`removeEventListener\` must use the same capture flag as \`addEventListener\`.`;
|
|
7726
|
-
};
|
|
7727
|
-
//#endregion
|
|
7728
|
-
//#region src/plugin/rules/state-and-effects/utils/callback-may-register-event-listener.ts
|
|
7729
|
-
const callbackMayRegisterEventListener = (callbackNode) => {
|
|
7730
|
-
let foundCall = false;
|
|
7731
|
-
walkAst(callbackNode, (child) => {
|
|
7732
|
-
if (isNodeOfType(child, "CallExpression")) {
|
|
7733
|
-
foundCall = true;
|
|
7734
|
-
return false;
|
|
7735
|
-
}
|
|
7736
|
-
});
|
|
7737
|
-
return foundCall;
|
|
7738
|
-
};
|
|
7739
|
-
//#endregion
|
|
7740
|
-
//#region src/plugin/rules/state-and-effects/utils/compare-listener-callback-identities.ts
|
|
7741
|
-
const compareListenerCallbackIdentities = (registrationIdentity, removalIdentity) => {
|
|
7742
|
-
if (registrationIdentity.node === removalIdentity.node) return "same";
|
|
7743
|
-
if (registrationIdentity.isConcreteFunction && removalIdentity.isConcreteFunction) return "different";
|
|
7744
|
-
return "unknown";
|
|
7745
|
-
};
|
|
7746
|
-
//#endregion
|
|
7747
|
-
//#region src/plugin/rules/state-and-effects/utils/does-listener-analysis-abort-controller.ts
|
|
7748
|
-
const doesListenerAnalysisAbortController = (analysis, controllerSymbolId) => analysis.abortedControllerSymbolIds.has(controllerSymbolId) || analysis.exhaustiveBranches.some(({ alternate, consequent }) => doesListenerAnalysisAbortController(alternate, controllerSymbolId) && doesListenerAnalysisAbortController(consequent, controllerSymbolId));
|
|
7749
|
-
//#endregion
|
|
7750
|
-
//#region src/plugin/rules/state-and-effects/utils/compare-listener-target-keys.ts
|
|
7751
|
-
const compareListenerTargetKeys = (firstTargetKey, secondTargetKey) => {
|
|
7752
|
-
if (firstTargetKey === secondTargetKey) return "same";
|
|
7753
|
-
if (firstTargetKey.startsWith("global:") && secondTargetKey.startsWith("global:")) return "different";
|
|
7754
|
-
if (firstTargetKey.startsWith("fresh:") || secondTargetKey.startsWith("fresh:")) return "different";
|
|
7755
|
-
return "unknown";
|
|
7756
|
-
};
|
|
7757
|
-
//#endregion
|
|
7758
|
-
//#region src/plugin/rules/state-and-effects/utils/does-listener-analysis-cancel-registration.ts
|
|
7759
|
-
const cancellationResult = (analysis, registration) => {
|
|
7760
|
-
if (registration.abortControllerSymbolId !== null && doesListenerAnalysisAbortController(analysis, registration.abortControllerSymbolId)) return "cancelled";
|
|
7761
|
-
let result = analysis.hasUnknownRemovalCall || registration.abortControllerSymbolId !== null && analysis.hasUnknownAbortCall ? "unknown" : "not-cancelled";
|
|
7762
|
-
for (const removal of analysis.removals) {
|
|
7763
|
-
if (removal.eventName !== registration.eventName || removal.capture !== registration.capture || removal.callbackIdentity === null || compareListenerCallbackIdentities(registration.callbackIdentity, removal.callbackIdentity) !== "same") continue;
|
|
7764
|
-
const targetComparison = compareListenerTargetKeys(registration.targetKey, removal.targetKey);
|
|
7765
|
-
if (targetComparison === "same") return "cancelled";
|
|
7766
|
-
if (targetComparison === "unknown") result = "unknown";
|
|
7767
|
-
}
|
|
7768
|
-
for (const { alternate, consequent } of analysis.exhaustiveBranches) {
|
|
7769
|
-
const alternateResult = cancellationResult(alternate, registration);
|
|
7770
|
-
const consequentResult = cancellationResult(consequent, registration);
|
|
7771
|
-
if (alternateResult === "cancelled" && consequentResult === "cancelled") return "cancelled";
|
|
7772
|
-
if (alternateResult !== "not-cancelled" && consequentResult !== "not-cancelled") result = "unknown";
|
|
7773
|
-
}
|
|
7774
|
-
return result;
|
|
7775
|
-
};
|
|
7776
|
-
const doesListenerAnalysisCancelRegistration = (analysis, registration) => cancellationResult(analysis, registration) !== "not-cancelled";
|
|
7777
|
-
//#endregion
|
|
7778
|
-
//#region src/plugin/rules/state-and-effects/utils/static-member-property-name.ts
|
|
7779
|
-
const getStaticMemberPropertyName = (node) => {
|
|
7780
|
-
if (!node) return null;
|
|
7781
|
-
const unwrappedNode = stripParenExpression(node);
|
|
7782
|
-
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
7783
|
-
if (!unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier")) return unwrappedNode.property.name;
|
|
7784
|
-
if (unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Literal") && typeof unwrappedNode.property.value === "string") return unwrappedNode.property.value;
|
|
7785
|
-
return null;
|
|
7786
|
-
};
|
|
7787
|
-
//#endregion
|
|
7788
|
-
//#region src/plugin/rules/state-and-effects/utils/is-listener-path-ambiguous.ts
|
|
7789
|
-
const PATH_AMBIGUOUS_ANCESTOR_TYPES = new Set([
|
|
7790
|
-
"CatchClause",
|
|
7791
|
-
"ConditionalExpression",
|
|
7792
|
-
"DoWhileStatement",
|
|
7793
|
-
"ForInStatement",
|
|
7794
|
-
"ForOfStatement",
|
|
7795
|
-
"ForStatement",
|
|
7796
|
-
"IfStatement",
|
|
7797
|
-
"LogicalExpression",
|
|
7798
|
-
"SwitchCase",
|
|
7799
|
-
"SwitchStatement",
|
|
7800
|
-
"TryStatement",
|
|
7801
|
-
"WhileStatement"
|
|
7802
|
-
]);
|
|
7803
|
-
const isGuaranteedChild = (parentNode, childNode) => {
|
|
7804
|
-
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
7805
|
-
if (parentNode.test === childNode) return true;
|
|
7806
|
-
const staticTestValue = readStaticBoolean(parentNode.test);
|
|
7807
|
-
return parentNode.consequent === childNode && staticTestValue === true || parentNode.alternate === childNode && staticTestValue === false;
|
|
7808
|
-
}
|
|
7809
|
-
if (isNodeOfType(parentNode, "ConditionalExpression")) {
|
|
7810
|
-
if (parentNode.test === childNode) return true;
|
|
7811
|
-
const staticTestValue = readStaticBoolean(parentNode.test);
|
|
7812
|
-
return parentNode.consequent === childNode && staticTestValue === true || parentNode.alternate === childNode && staticTestValue === false;
|
|
7813
|
-
}
|
|
7814
|
-
if (isNodeOfType(parentNode, "LogicalExpression")) {
|
|
7815
|
-
if (parentNode.left === childNode) return true;
|
|
7816
|
-
if (parentNode.right !== childNode) return false;
|
|
7817
|
-
const staticLeftValue = readStaticBoolean(parentNode.left);
|
|
7818
|
-
return parentNode.operator === "&&" && staticLeftValue === true || parentNode.operator === "||" && staticLeftValue === false;
|
|
7819
|
-
}
|
|
7820
|
-
if (isNodeOfType(parentNode, "TryStatement")) return parentNode.finalizer === childNode;
|
|
7821
|
-
if (isNodeOfType(parentNode, "SwitchStatement")) return parentNode.discriminant === childNode;
|
|
7822
|
-
if (isNodeOfType(parentNode, "ForStatement")) return parentNode.init === childNode || parentNode.test === childNode;
|
|
7823
|
-
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.right === childNode;
|
|
7824
|
-
if (isNodeOfType(parentNode, "WhileStatement")) return parentNode.test === childNode;
|
|
7825
|
-
return false;
|
|
7826
|
-
};
|
|
7827
|
-
const isListenerPathAmbiguous = (node, bodyNode, allowAmbiguousChild) => {
|
|
7828
|
-
let currentNode = node;
|
|
7829
|
-
while (currentNode && currentNode !== bodyNode) {
|
|
7830
|
-
const parentNode = currentNode.parent;
|
|
7831
|
-
if (!parentNode) return true;
|
|
7832
|
-
if (PATH_AMBIGUOUS_ANCESTOR_TYPES.has(parentNode.type) && !isGuaranteedChild(parentNode, currentNode) && !allowAmbiguousChild?.(parentNode, currentNode)) return true;
|
|
7833
|
-
currentNode = parentNode;
|
|
7834
|
-
}
|
|
7835
|
-
return false;
|
|
7836
|
-
};
|
|
7837
|
-
//#endregion
|
|
7838
|
-
//#region src/plugin/rules/state-and-effects/utils/resolve-event-listener-capture.ts
|
|
7839
|
-
const resolveEventListenerCapture = (optionsNode) => {
|
|
7840
|
-
if (!optionsNode) return false;
|
|
7841
|
-
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
7842
|
-
if (isNodeOfType(unwrappedOptions, "Literal")) return typeof unwrappedOptions.value === "boolean" ? unwrappedOptions.value : null;
|
|
7843
|
-
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) return null;
|
|
7844
|
-
let capture = false;
|
|
7845
|
-
for (const property of unwrappedOptions.properties) {
|
|
7846
|
-
if (!isNodeOfType(property, "Property")) return null;
|
|
7847
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
7848
|
-
if (propertyName === null) return null;
|
|
7849
|
-
if (!property.computed && propertyName === "__proto__") return null;
|
|
7850
|
-
if (propertyName !== "capture") continue;
|
|
7851
|
-
const propertyValue = stripParenExpression(property.value);
|
|
7852
|
-
if (!isNodeOfType(propertyValue, "Literal") || typeof propertyValue.value !== "boolean") return null;
|
|
7853
|
-
capture = propertyValue.value;
|
|
7854
|
-
}
|
|
7855
|
-
return capture;
|
|
7856
|
-
};
|
|
7857
|
-
//#endregion
|
|
7858
|
-
//#region src/plugin/rules/state-and-effects/utils/resolve-static-once-option.ts
|
|
7859
|
-
const resolveStaticOnceOption = (optionsNode) => {
|
|
7860
|
-
if (!optionsNode) return false;
|
|
7861
|
-
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
7862
|
-
if (isNodeOfType(unwrappedOptions, "Literal")) return typeof unwrappedOptions.value === "boolean" ? false : null;
|
|
7863
|
-
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) return null;
|
|
7864
|
-
let once = false;
|
|
7865
|
-
for (const property of unwrappedOptions.properties) {
|
|
7866
|
-
if (!isNodeOfType(property, "Property")) return null;
|
|
7867
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
7868
|
-
if (propertyName === null || !property.computed && propertyName === "__proto__") return null;
|
|
7869
|
-
if (propertyName !== "once") continue;
|
|
7870
|
-
const propertyValue = stripParenExpression(property.value);
|
|
7871
|
-
if (!isNodeOfType(propertyValue, "Literal") || typeof propertyValue.value !== "boolean") return null;
|
|
7872
|
-
once = propertyValue.value;
|
|
7873
|
-
}
|
|
7874
|
-
return once;
|
|
7875
|
-
};
|
|
7876
|
-
//#endregion
|
|
7877
|
-
//#region src/plugin/rules/state-and-effects/effect-listener-cleanup-mismatch.ts
|
|
7878
|
-
const LISTENER_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
7879
|
-
const hasOnlyReadReferences = (symbol) => symbol.references.every((reference) => reference.flag === "read");
|
|
7880
|
-
const isStableSymbol = (symbol) => symbol.kind === "const" || symbol.kind === "import" || (symbol.kind === "function" || symbol.kind === "parameter") && hasOnlyReadReferences(symbol);
|
|
7881
|
-
const isPlainConstSymbol = (symbol) => symbol.kind === "const" && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && isNodeOfType(symbol.declarationNode.id, "Identifier") && symbol.declarationNode.id === symbol.bindingIdentifier;
|
|
7882
|
-
const resolveAliasedSymbol = (identifier, context, visitedSymbolIds) => {
|
|
7883
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
7884
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
7885
|
-
if (!symbol || !isStableSymbol(symbol) || visitedSymbolIds.has(symbol.id)) return null;
|
|
7886
|
-
if (symbol.kind === "const" && !isPlainConstSymbol(symbol)) return null;
|
|
7887
|
-
visitedSymbolIds.add(symbol.id);
|
|
7888
|
-
const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
7889
|
-
if (symbol.kind === "const" && isNodeOfType(initializer, "Identifier")) {
|
|
7890
|
-
const resolvedAlias = resolveAliasedSymbol(initializer, context, visitedSymbolIds);
|
|
7891
|
-
if (resolvedAlias) return resolvedAlias;
|
|
7892
|
-
}
|
|
7893
|
-
return symbol;
|
|
7894
|
-
};
|
|
7895
|
-
const resolveCallbackIdentity = (callbackNode, context) => {
|
|
7896
|
-
if (!callbackNode) return null;
|
|
7897
|
-
const unwrappedCallback = stripParenExpression(callbackNode);
|
|
7898
|
-
if (isFunctionLike$1(unwrappedCallback)) return {
|
|
7899
|
-
node: unwrappedCallback,
|
|
7900
|
-
isConcreteFunction: true
|
|
7901
|
-
};
|
|
7902
|
-
if (!isNodeOfType(unwrappedCallback, "Identifier")) return null;
|
|
7903
|
-
const symbol = resolveAliasedSymbol(unwrappedCallback, context, /* @__PURE__ */ new Set());
|
|
7904
|
-
if (!symbol) return null;
|
|
7905
|
-
const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
7906
|
-
if (initializer && isFunctionLike$1(initializer)) return {
|
|
7907
|
-
node: initializer,
|
|
7908
|
-
isConcreteFunction: true
|
|
7909
|
-
};
|
|
7910
|
-
return {
|
|
7911
|
-
node: symbol.bindingIdentifier,
|
|
7912
|
-
isConcreteFunction: false
|
|
7913
|
-
};
|
|
7914
|
-
};
|
|
7915
|
-
const resolveTargetKey = (targetNode, context) => {
|
|
7916
|
-
const unwrappedTarget = stripParenExpression(targetNode);
|
|
7917
|
-
if (isNodeOfType(unwrappedTarget, "Identifier")) {
|
|
7918
|
-
const symbol = resolveAliasedSymbol(unwrappedTarget, context, /* @__PURE__ */ new Set());
|
|
7919
|
-
if (symbol) {
|
|
7920
|
-
if (symbol.kind === "import" || symbol.kind === "parameter" || symbol.kind === "function") return null;
|
|
7921
|
-
const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
7922
|
-
if (isNodeOfType(initializer, "NewExpression") && isNodeOfType(initializer.callee, "Identifier") && !context.scopes.isGlobalReference(initializer.callee)) return null;
|
|
7923
|
-
if (isNodeOfType(initializer, "NewExpression")) return `fresh:${symbol.id}`;
|
|
7924
|
-
return `symbol:${symbol.id}`;
|
|
7925
|
-
}
|
|
7926
|
-
if (context.scopes.isGlobalReference(unwrappedTarget)) return `global:${unwrappedTarget.name}`;
|
|
7927
|
-
return null;
|
|
7928
|
-
}
|
|
7929
|
-
if (isNodeOfType(unwrappedTarget, "MemberExpression") && !unwrappedTarget.computed && isNodeOfType(unwrappedTarget.property, "Identifier")) {
|
|
7930
|
-
const objectKey = resolveTargetKey(unwrappedTarget.object, context);
|
|
7931
|
-
if (unwrappedTarget.property.name === "document" && (objectKey === "global:window" || objectKey === "global:globalThis")) return "global:document";
|
|
7932
|
-
if (unwrappedTarget.property.name === "window" && (objectKey === "global:window" || objectKey === "global:globalThis")) return "global:window";
|
|
7933
|
-
return objectKey === null ? null : `${objectKey}.${unwrappedTarget.property.name}`;
|
|
7934
|
-
}
|
|
7935
|
-
return null;
|
|
7936
|
-
};
|
|
7937
|
-
const resolveStaticEventName = (eventNode, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
7938
|
-
if (!eventNode) return null;
|
|
7939
|
-
const unwrappedEvent = stripParenExpression(eventNode);
|
|
7940
|
-
if (isNodeOfType(unwrappedEvent, "Literal") && typeof unwrappedEvent.value === "string") return unwrappedEvent.value;
|
|
7941
|
-
if (isNodeOfType(unwrappedEvent, "TemplateLiteral") && unwrappedEvent.expressions.length === 0) return unwrappedEvent.quasis[0]?.value.cooked ?? "";
|
|
7942
|
-
if (!isNodeOfType(unwrappedEvent, "Identifier")) return null;
|
|
7943
|
-
const symbol = context.scopes.symbolFor(unwrappedEvent);
|
|
7944
|
-
if (!symbol || !isPlainConstSymbol(symbol) || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return null;
|
|
7945
|
-
visitedSymbolIds.add(symbol.id);
|
|
7946
|
-
return resolveStaticEventName(symbol.initializer, context, visitedSymbolIds);
|
|
7947
|
-
};
|
|
7948
|
-
const readStaticEventDispatch = (node, context) => {
|
|
7949
|
-
const targetNode = readDirectMemberReceiver(node.callee, "dispatchEvent", context);
|
|
7950
|
-
const eventArgument = node.arguments?.[0];
|
|
7951
|
-
if (!eventArgument) return null;
|
|
7952
|
-
const eventNode = stripParenExpression(eventArgument);
|
|
7953
|
-
if (!targetNode || !isNodeOfType(eventNode, "NewExpression") || !isNodeOfType(eventNode.callee, "Identifier") || eventNode.callee.name !== "Event" || !context.scopes.isGlobalReference(eventNode.callee)) return null;
|
|
7954
|
-
const target = stripParenExpression(targetNode);
|
|
7955
|
-
if (!isNodeOfType(target, "Identifier")) return null;
|
|
7956
|
-
const targetSymbol = resolveAliasedSymbol(target, context, /* @__PURE__ */ new Set());
|
|
7957
|
-
const targetInitializer = targetSymbol?.initializer ? stripParenExpression(targetSymbol.initializer) : null;
|
|
7958
|
-
if (!isNodeOfType(targetInitializer, "NewExpression") || !isNodeOfType(targetInitializer.callee, "Identifier") || targetInitializer.callee.name !== "EventTarget" || !context.scopes.isGlobalReference(targetInitializer.callee)) return null;
|
|
7959
|
-
const targetKey = resolveTargetKey(targetNode, context);
|
|
7960
|
-
const eventName = resolveStaticEventName(eventNode.arguments?.[0], context);
|
|
7961
|
-
return targetKey !== null && eventName !== null ? {
|
|
7962
|
-
eventName,
|
|
7963
|
-
targetKey
|
|
7964
|
-
} : null;
|
|
7965
|
-
};
|
|
7966
|
-
const resolveLocalAbortControllerSymbolId = (controllerNode, context) => {
|
|
7967
|
-
const unwrappedController = stripParenExpression(controllerNode);
|
|
7968
|
-
if (!isNodeOfType(unwrappedController, "Identifier")) return null;
|
|
7969
|
-
const controllerSymbol = resolveAliasedSymbol(unwrappedController, context, /* @__PURE__ */ new Set());
|
|
7970
|
-
if (!controllerSymbol || controllerSymbol.kind !== "const" || !controllerSymbol.initializer) return null;
|
|
7971
|
-
const initializer = stripParenExpression(controllerSymbol.initializer);
|
|
7972
|
-
if (!isNodeOfType(initializer, "NewExpression") || !isNodeOfType(initializer.callee, "Identifier") || initializer.callee.name !== "AbortController" || !context.scopes.isGlobalReference(initializer.callee)) return null;
|
|
7973
|
-
return controllerSymbol.id;
|
|
7974
|
-
};
|
|
7975
|
-
const readDirectMemberReceiver = (memberNode, memberName, context) => {
|
|
7976
|
-
if (!memberNode) return null;
|
|
7977
|
-
const unwrappedMember = stripParenExpression(memberNode);
|
|
7978
|
-
if (!isNodeOfType(unwrappedMember, "MemberExpression")) return null;
|
|
7979
|
-
if ((getStaticMemberPropertyName(unwrappedMember) ?? (context && unwrappedMember.computed ? resolveStaticEventName(unwrappedMember.property, context) : null)) !== memberName) return null;
|
|
7980
|
-
return unwrappedMember.object;
|
|
7981
|
-
};
|
|
7982
|
-
const isSignalObjectPatternBinding = (patternNode, bindingIdentifier) => {
|
|
7983
|
-
if (!isNodeOfType(patternNode, "ObjectPattern")) return false;
|
|
7984
|
-
return patternNode.properties.some((property) => {
|
|
7985
|
-
if (!isNodeOfType(property, "Property")) return false;
|
|
7986
|
-
if (getStaticPropertyKeyName(property, { allowComputedString: true }) !== "signal") return false;
|
|
7987
|
-
return (isNodeOfType(property.value, "AssignmentPattern") ? property.value.left : property.value) === bindingIdentifier;
|
|
7988
|
-
});
|
|
7989
|
-
};
|
|
7990
|
-
const resolveSignalAbortControllerSymbolId = (signalNode, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
7991
|
-
const unwrappedSignal = stripParenExpression(signalNode);
|
|
7992
|
-
const directControllerNode = readDirectMemberReceiver(unwrappedSignal, "signal");
|
|
7993
|
-
if (directControllerNode) return resolveLocalAbortControllerSymbolId(directControllerNode, context);
|
|
7994
|
-
if (!isNodeOfType(unwrappedSignal, "Identifier")) return null;
|
|
7995
|
-
const signalSymbol = context.scopes.symbolFor(unwrappedSignal);
|
|
7996
|
-
if (!signalSymbol || signalSymbol.kind !== "const" || !signalSymbol.initializer || visitedSymbolIds.has(signalSymbol.id) || !hasOnlyReadReferences(signalSymbol)) return null;
|
|
7997
|
-
visitedSymbolIds.add(signalSymbol.id);
|
|
7998
|
-
const declarationNode = signalSymbol.declarationNode;
|
|
7999
|
-
if (!isNodeOfType(declarationNode, "VariableDeclarator")) return null;
|
|
8000
|
-
if (isNodeOfType(declarationNode.id, "Identifier") && declarationNode.id === signalSymbol.bindingIdentifier) {
|
|
8001
|
-
const initializer = stripParenExpression(signalSymbol.initializer);
|
|
8002
|
-
if (isNodeOfType(initializer, "Identifier")) return resolveSignalAbortControllerSymbolId(initializer, context, visitedSymbolIds);
|
|
8003
|
-
const controllerNode = readDirectMemberReceiver(initializer, "signal");
|
|
8004
|
-
return controllerNode ? resolveLocalAbortControllerSymbolId(controllerNode, context) : null;
|
|
8005
|
-
}
|
|
8006
|
-
if (!isSignalObjectPatternBinding(declarationNode.id, signalSymbol.bindingIdentifier)) return null;
|
|
8007
|
-
return resolveLocalAbortControllerSymbolId(signalSymbol.initializer, context);
|
|
8008
|
-
};
|
|
8009
|
-
const readControllerAbortedCondition = (conditionNode, controllerSymbolId, context) => {
|
|
8010
|
-
const unwrappedCondition = stripParenExpression(conditionNode);
|
|
8011
|
-
if (isNodeOfType(unwrappedCondition, "UnaryExpression") && unwrappedCondition.operator === "!") {
|
|
8012
|
-
const operandCondition = readControllerAbortedCondition(unwrappedCondition.argument, controllerSymbolId, context);
|
|
8013
|
-
if (operandCondition === "aborted") return "not-aborted";
|
|
8014
|
-
if (operandCondition === "not-aborted") return "aborted";
|
|
8015
|
-
return null;
|
|
8016
|
-
}
|
|
8017
|
-
if (isNodeOfType(unwrappedCondition, "BinaryExpression")) {
|
|
8018
|
-
const leftBoolean = readStaticBoolean(unwrappedCondition.left);
|
|
8019
|
-
const rightBoolean = readStaticBoolean(unwrappedCondition.right);
|
|
8020
|
-
const memberNode = leftBoolean === null ? unwrappedCondition.left : rightBoolean === null ? unwrappedCondition.right : null;
|
|
8021
|
-
const comparedBoolean = leftBoolean ?? rightBoolean;
|
|
8022
|
-
if (memberNode && comparedBoolean !== null) {
|
|
8023
|
-
const memberCondition = readControllerAbortedCondition(memberNode, controllerSymbolId, context);
|
|
8024
|
-
if (memberCondition) {
|
|
8025
|
-
const isEquality = unwrappedCondition.operator === "==" || unwrappedCondition.operator === "===";
|
|
8026
|
-
const isInequality = unwrappedCondition.operator === "!=" || unwrappedCondition.operator === "!==";
|
|
8027
|
-
const shouldInvert = isEquality && !comparedBoolean || isInequality && comparedBoolean;
|
|
8028
|
-
if (isEquality || isInequality) {
|
|
8029
|
-
if (!shouldInvert) return memberCondition;
|
|
8030
|
-
return memberCondition === "aborted" ? "not-aborted" : "aborted";
|
|
8031
|
-
}
|
|
8032
|
-
}
|
|
8033
|
-
}
|
|
8034
|
-
return null;
|
|
8035
|
-
}
|
|
8036
|
-
const signalNode = readDirectMemberReceiver(unwrappedCondition, "aborted");
|
|
8037
|
-
if (!signalNode) return null;
|
|
8038
|
-
return resolveSignalAbortControllerSymbolId(signalNode, context) === controllerSymbolId ? "aborted" : null;
|
|
8039
|
-
};
|
|
8040
|
-
const isAbortGuardForChild = (parentNode, childNode, controllerSymbolId, context) => {
|
|
8041
|
-
if (isNodeOfType(parentNode, "IfStatement") || isNodeOfType(parentNode, "ConditionalExpression")) {
|
|
8042
|
-
const condition = readControllerAbortedCondition(parentNode.test, controllerSymbolId, context);
|
|
8043
|
-
return parentNode.consequent === childNode && condition === "not-aborted" || parentNode.alternate === childNode && condition === "aborted";
|
|
8044
|
-
}
|
|
8045
|
-
if (!isNodeOfType(parentNode, "LogicalExpression") || parentNode.right !== childNode) return false;
|
|
8046
|
-
const condition = readControllerAbortedCondition(parentNode.left, controllerSymbolId, context);
|
|
8047
|
-
return parentNode.operator === "&&" && condition === "not-aborted" || parentNode.operator === "||" && condition === "aborted";
|
|
8048
|
-
};
|
|
8049
|
-
const isAbortGuaranteedByPath = (node, bodyNode, controllerSymbolId, context) => !isListenerPathAmbiguous(node, bodyNode, (parentNode, childNode) => isAbortGuardForChild(parentNode, childNode, controllerSymbolId, context));
|
|
8050
|
-
const resolveBoundAbortControllerSymbolId = (callNode, context) => {
|
|
8051
|
-
const callee = stripParenExpression(callNode.callee);
|
|
8052
|
-
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
8053
|
-
const boundAbortSymbol = resolveAliasedSymbol(callee, context, /* @__PURE__ */ new Set());
|
|
8054
|
-
if (!boundAbortSymbol?.initializer) return null;
|
|
8055
|
-
const initializer = stripParenExpression(boundAbortSymbol.initializer);
|
|
8056
|
-
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
8057
|
-
const abortMethodNode = readDirectMemberReceiver(initializer.callee, "bind");
|
|
8058
|
-
if (!abortMethodNode) return null;
|
|
8059
|
-
const controllerNode = readDirectMemberReceiver(abortMethodNode, "abort");
|
|
8060
|
-
const boundThisNode = initializer.arguments?.[0];
|
|
8061
|
-
if (!controllerNode || !boundThisNode) return null;
|
|
8062
|
-
const controllerSymbolId = resolveLocalAbortControllerSymbolId(controllerNode, context);
|
|
8063
|
-
const boundThisSymbolId = resolveLocalAbortControllerSymbolId(boundThisNode, context);
|
|
8064
|
-
return controllerSymbolId !== null && controllerSymbolId === boundThisSymbolId ? controllerSymbolId : null;
|
|
8065
|
-
};
|
|
8066
|
-
const resolveRegistrationCancellation = (optionsNode, context) => {
|
|
8067
|
-
const noCancellation = {
|
|
8068
|
-
abortControllerSymbolId: null,
|
|
8069
|
-
hasUnknownCancellation: false
|
|
8070
|
-
};
|
|
8071
|
-
if (!optionsNode) return noCancellation;
|
|
8072
|
-
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
8073
|
-
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) return noCancellation;
|
|
8074
|
-
let resolvedCancellation = null;
|
|
8075
|
-
for (const property of unwrappedOptions.properties) {
|
|
8076
|
-
if (!isNodeOfType(property, "Property")) return noCancellation;
|
|
8077
|
-
if (getStaticPropertyKeyName(property, { allowComputedString: true }) !== "signal") continue;
|
|
8078
|
-
if (resolvedCancellation) return {
|
|
8079
|
-
abortControllerSymbolId: null,
|
|
8080
|
-
hasUnknownCancellation: true
|
|
8081
|
-
};
|
|
8082
|
-
const abortControllerSymbolId = resolveSignalAbortControllerSymbolId(property.value, context);
|
|
8083
|
-
resolvedCancellation = {
|
|
8084
|
-
abortControllerSymbolId,
|
|
8085
|
-
hasUnknownCancellation: abortControllerSymbolId === null
|
|
8086
|
-
};
|
|
8087
|
-
}
|
|
8088
|
-
return resolvedCancellation ?? noCancellation;
|
|
8089
|
-
};
|
|
8090
|
-
const readListenerCandidate = (node, methodName, context) => {
|
|
8091
|
-
const targetNode = readDirectMemberReceiver(node.callee, methodName, context);
|
|
8092
|
-
if (!targetNode) return null;
|
|
8093
|
-
const targetKey = resolveTargetKey(targetNode, context);
|
|
8094
|
-
const eventName = resolveStaticEventName(node.arguments?.[0], context);
|
|
8095
|
-
const callbackIdentity = resolveCallbackIdentity(node.arguments?.[1], context);
|
|
8096
|
-
const capture = resolveEventListenerCapture(node.arguments?.[2]);
|
|
8097
|
-
if (targetKey === null || eventName === null) return null;
|
|
8098
|
-
return {
|
|
8099
|
-
node,
|
|
8100
|
-
targetKey,
|
|
8101
|
-
eventName,
|
|
8102
|
-
callbackIdentity,
|
|
8103
|
-
capture
|
|
8104
|
-
};
|
|
8105
|
-
};
|
|
8106
|
-
const readDestructuredRemovalCandidate = (node, context) => {
|
|
8107
|
-
const methodNode = readDirectMemberReceiver(node.callee, "call");
|
|
8108
|
-
if (!methodNode || !isNodeOfType(methodNode, "Identifier")) return null;
|
|
8109
|
-
const methodSymbol = context.scopes.symbolFor(methodNode);
|
|
8110
|
-
if (!methodSymbol || methodSymbol.kind !== "const" || !hasOnlyReadReferences(methodSymbol) || !isNodeOfType(methodSymbol.declarationNode, "VariableDeclarator")) return null;
|
|
8111
|
-
const declaration = methodSymbol.declarationNode;
|
|
8112
|
-
if (!isNodeOfType(declaration.id, "ObjectPattern") || !declaration.init) return null;
|
|
8113
|
-
if (!declaration.id.properties.some((property) => {
|
|
8114
|
-
if (!isNodeOfType(property, "Property")) return false;
|
|
8115
|
-
const bindingNode = isNodeOfType(property.value, "AssignmentPattern") ? property.value.left : property.value;
|
|
8116
|
-
return getStaticPropertyKeyName(property, { allowComputedString: true }) === "removeEventListener" && bindingNode === methodSymbol.bindingIdentifier;
|
|
8117
|
-
})) return null;
|
|
8118
|
-
const targetNode = node.arguments?.[0];
|
|
8119
|
-
if (!targetNode) return null;
|
|
8120
|
-
const declarationTargetKey = resolveTargetKey(declaration.init, context);
|
|
8121
|
-
const targetKey = resolveTargetKey(targetNode, context);
|
|
8122
|
-
if (declarationTargetKey === null || targetKey === null || declarationTargetKey !== targetKey) return null;
|
|
8123
|
-
const eventName = resolveStaticEventName(node.arguments?.[1], context);
|
|
8124
|
-
if (eventName === null) return null;
|
|
8125
|
-
return {
|
|
8126
|
-
node,
|
|
8127
|
-
targetKey,
|
|
8128
|
-
eventName,
|
|
8129
|
-
callbackIdentity: resolveCallbackIdentity(node.arguments?.[2], context),
|
|
8130
|
-
capture: resolveEventListenerCapture(node.arguments?.[3])
|
|
8131
|
-
};
|
|
8132
|
-
};
|
|
8133
|
-
const resolveReturnedCleanupBody = (returnedValue, context) => {
|
|
8134
|
-
if (!returnedValue) return null;
|
|
8135
|
-
const unwrappedValue = stripParenExpression(returnedValue);
|
|
8136
|
-
if (isFunctionLike$1(unwrappedValue)) return unwrappedValue.body;
|
|
8137
|
-
if (!isNodeOfType(unwrappedValue, "Identifier")) return null;
|
|
8138
|
-
const symbol = resolveAliasedSymbol(unwrappedValue, context, /* @__PURE__ */ new Set());
|
|
8139
|
-
if (!symbol || !symbol.initializer) return null;
|
|
8140
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
8141
|
-
return isFunctionLike$1(initializer) ? initializer.body : null;
|
|
8142
|
-
};
|
|
8143
|
-
const collectEffectListenerInputs = (effectBody, context) => {
|
|
8144
|
-
const registrations = [];
|
|
8145
|
-
const cleanupBodies = [];
|
|
8146
|
-
const listenerRegistrationCountsByTarget = /* @__PURE__ */ new Map();
|
|
8147
|
-
let returnStatementCount = 0;
|
|
8148
|
-
if (!isNodeOfType(effectBody, "BlockStatement")) return {
|
|
8149
|
-
registrations,
|
|
8150
|
-
cleanupBodies,
|
|
8151
|
-
hasCanonicalCleanupReturn: false,
|
|
8152
|
-
returnStatementCount
|
|
8153
|
-
};
|
|
8154
|
-
const finalEffectStatement = effectBody.body[effectBody.body.length - 1];
|
|
8155
|
-
const hasCanonicalCleanupReturn = isNodeOfType(finalEffectStatement, "ReturnStatement");
|
|
8156
|
-
walkAst(effectBody, (child) => {
|
|
8157
|
-
if (child !== effectBody && isFunctionLike$1(child)) return false;
|
|
8158
|
-
if (isNodeOfType(child, "ClassDeclaration") || isNodeOfType(child, "ClassExpression")) return false;
|
|
8159
|
-
if (isNodeOfType(child, "CallExpression")) {
|
|
8160
|
-
if (isListenerPathAmbiguous(child, effectBody)) return;
|
|
8161
|
-
const registrationTarget = readDirectMemberReceiver(child.callee, "addEventListener", context);
|
|
8162
|
-
const registrationTargetKey = registrationTarget ? resolveTargetKey(registrationTarget, context) : null;
|
|
8163
|
-
if (registrationTargetKey !== null) listenerRegistrationCountsByTarget.set(registrationTargetKey, (listenerRegistrationCountsByTarget.get(registrationTargetKey) ?? 0) + 1);
|
|
8164
|
-
const candidate = readListenerCandidate(child, "addEventListener", context);
|
|
8165
|
-
if (candidate?.callbackIdentity && candidate.capture !== null) {
|
|
8166
|
-
const registrationCancellation = resolveRegistrationCancellation(candidate.node.arguments?.[2], context);
|
|
8167
|
-
registrations.push({
|
|
8168
|
-
node: candidate.node,
|
|
8169
|
-
targetKey: candidate.targetKey,
|
|
8170
|
-
eventName: candidate.eventName,
|
|
8171
|
-
callbackIdentity: candidate.callbackIdentity,
|
|
8172
|
-
capture: candidate.capture,
|
|
8173
|
-
once: resolveStaticOnceOption(candidate.node.arguments?.[2]) === true,
|
|
8174
|
-
abortControllerSymbolId: registrationCancellation.abortControllerSymbolId,
|
|
8175
|
-
hasUnknownCancellation: registrationCancellation.hasUnknownCancellation
|
|
8176
|
-
});
|
|
8177
|
-
}
|
|
8178
|
-
const eventDispatch = readStaticEventDispatch(child, context);
|
|
8179
|
-
if (eventDispatch) {
|
|
8180
|
-
const dispatchedRegistrations = registrations.filter((registration) => registration.targetKey === eventDispatch.targetKey && registration.eventName === eventDispatch.eventName);
|
|
8181
|
-
const dispatchedRegistration = dispatchedRegistrations[0];
|
|
8182
|
-
if (dispatchedRegistrations.length === 1 && dispatchedRegistration?.once && listenerRegistrationCountsByTarget.get(eventDispatch.targetKey) === 1 && dispatchedRegistration.callbackIdentity.isConcreteFunction && !callbackMayRegisterEventListener(dispatchedRegistration.callbackIdentity.node)) registrations.splice(registrations.indexOf(dispatchedRegistration), 1);
|
|
8183
|
-
}
|
|
8184
|
-
return;
|
|
8185
|
-
}
|
|
8186
|
-
if (isNodeOfType(child, "ReturnStatement")) {
|
|
8187
|
-
returnStatementCount += 1;
|
|
8188
|
-
const cleanupBody = resolveReturnedCleanupBody(child.argument, context);
|
|
8189
|
-
if (cleanupBody) cleanupBodies.push(cleanupBody);
|
|
8190
|
-
}
|
|
8191
|
-
});
|
|
8192
|
-
return {
|
|
8193
|
-
registrations,
|
|
8194
|
-
cleanupBodies,
|
|
8195
|
-
hasCanonicalCleanupReturn,
|
|
8196
|
-
returnStatementCount
|
|
8197
|
-
};
|
|
8198
|
-
};
|
|
8199
|
-
const resolveCalledCleanup = (callNode, context) => {
|
|
8200
|
-
const callee = stripParenExpression(callNode.callee);
|
|
8201
|
-
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
8202
|
-
const cleanupSymbol = resolveAliasedSymbol(callee, context, /* @__PURE__ */ new Set());
|
|
8203
|
-
if (!cleanupSymbol?.initializer || cleanupSymbol.kind !== "const" && cleanupSymbol.kind !== "function") return null;
|
|
8204
|
-
const initializer = stripParenExpression(cleanupSymbol.initializer);
|
|
8205
|
-
if (!isFunctionLike$1(initializer) || initializer.async || initializer.generator) return null;
|
|
8206
|
-
return {
|
|
8207
|
-
body: initializer.body,
|
|
8208
|
-
symbolId: cleanupSymbol.id
|
|
8209
|
-
};
|
|
8210
|
-
};
|
|
8211
|
-
const analyzeCleanupBody = (cleanupBody, context, visitedCleanupSymbolIds = /* @__PURE__ */ new Set(), isLoopBody = false) => {
|
|
8212
|
-
const removals = [];
|
|
8213
|
-
const abortedControllerSymbolIds = /* @__PURE__ */ new Set();
|
|
8214
|
-
const exhaustiveBranches = [];
|
|
8215
|
-
let hasAmbiguousReachability = false;
|
|
8216
|
-
let hasUnknownAbortCall = false;
|
|
8217
|
-
let hasUnknownRemovalCall = false;
|
|
8218
|
-
const finalCleanupStatement = isNodeOfType(cleanupBody, "BlockStatement") ? cleanupBody.body[cleanupBody.body.length - 1] : null;
|
|
8219
|
-
const addCleanupAnalysis = (analysis) => {
|
|
8220
|
-
removals.push(...analysis.removals);
|
|
8221
|
-
hasUnknownAbortCall ||= analysis.hasUnknownAbortCall;
|
|
8222
|
-
hasUnknownRemovalCall ||= analysis.hasUnknownRemovalCall;
|
|
8223
|
-
for (const controllerSymbolId of analysis.abortedControllerSymbolIds) abortedControllerSymbolIds.add(controllerSymbolId);
|
|
8224
|
-
exhaustiveBranches.push(...analysis.exhaustiveBranches);
|
|
8225
|
-
};
|
|
8226
|
-
const addGuaranteedLoopPrefix = (loopBody) => {
|
|
8227
|
-
const loopStatements = isNodeOfType(loopBody, "BlockStatement") ? loopBody.body : [loopBody];
|
|
8228
|
-
for (const loopStatement of loopStatements) {
|
|
8229
|
-
if (isNodeOfType(loopStatement, "BreakStatement") || isNodeOfType(loopStatement, "ContinueStatement")) return true;
|
|
8230
|
-
if (isNodeOfType(loopStatement, "BlockStatement")) {
|
|
8231
|
-
if (addGuaranteedLoopPrefix(loopStatement)) return true;
|
|
8232
|
-
continue;
|
|
8233
|
-
}
|
|
8234
|
-
if (isNodeOfType(loopStatement, "IfStatement")) {
|
|
8235
|
-
const staticTestValue = readStaticBoolean(loopStatement.test);
|
|
8236
|
-
if (staticTestValue !== null) {
|
|
8237
|
-
const testAnalysis = analyzeCleanupBody(loopStatement.test, context, new Set(visitedCleanupSymbolIds), true);
|
|
8238
|
-
if (!testAnalysis) return true;
|
|
8239
|
-
addCleanupAnalysis(testAnalysis);
|
|
8240
|
-
const guaranteedBranch = staticTestValue ? loopStatement.consequent : loopStatement.alternate;
|
|
8241
|
-
if (guaranteedBranch && addGuaranteedLoopPrefix(guaranteedBranch)) return true;
|
|
8242
|
-
continue;
|
|
8243
|
-
}
|
|
8244
|
-
}
|
|
8245
|
-
const loopStatementAnalysis = analyzeCleanupBody(loopStatement, context, new Set(visitedCleanupSymbolIds), true);
|
|
8246
|
-
if (!loopStatementAnalysis) return true;
|
|
8247
|
-
addCleanupAnalysis(loopStatementAnalysis);
|
|
8248
|
-
}
|
|
8249
|
-
return false;
|
|
8250
|
-
};
|
|
8251
|
-
walkAst(cleanupBody, (child) => {
|
|
8252
|
-
if (child !== cleanupBody && isFunctionLike$1(child)) return false;
|
|
8253
|
-
if (isNodeOfType(child, "ClassDeclaration") || isNodeOfType(child, "ClassExpression")) return false;
|
|
8254
|
-
if (isNodeOfType(child, "ReturnStatement") && child === finalCleanupStatement) return;
|
|
8255
|
-
if (isNodeOfType(child, "ReturnStatement") || isNodeOfType(child, "ThrowStatement") || isLoopBody && (isNodeOfType(child, "BreakStatement") || isNodeOfType(child, "ContinueStatement"))) {
|
|
8256
|
-
hasAmbiguousReachability = true;
|
|
8257
|
-
return false;
|
|
8258
|
-
}
|
|
8259
|
-
if (isNodeOfType(child, "ForOfStatement")) {
|
|
8260
|
-
const iterable = stripParenExpression(child.right);
|
|
8261
|
-
if (isNodeOfType(iterable, "ArrayExpression") && iterable.elements.some((element) => element && !isNodeOfType(element, "SpreadElement")) && !isListenerPathAmbiguous(child, cleanupBody)) addGuaranteedLoopPrefix(child.body);
|
|
8262
|
-
return false;
|
|
8263
|
-
}
|
|
8264
|
-
if ((isNodeOfType(child, "IfStatement") || isNodeOfType(child, "ConditionalExpression")) && child.alternate && readStaticBoolean(child.test) === null && !isListenerPathAmbiguous(child, cleanupBody)) {
|
|
8265
|
-
const consequentAnalysis = analyzeCleanupBody(child.consequent, context, new Set(visitedCleanupSymbolIds));
|
|
8266
|
-
const alternateAnalysis = analyzeCleanupBody(child.alternate, context, new Set(visitedCleanupSymbolIds));
|
|
8267
|
-
if (consequentAnalysis && alternateAnalysis) {
|
|
8268
|
-
exhaustiveBranches.push({
|
|
8269
|
-
alternate: alternateAnalysis,
|
|
8270
|
-
consequent: consequentAnalysis
|
|
8271
|
-
});
|
|
8272
|
-
hasUnknownAbortCall ||= consequentAnalysis.hasUnknownAbortCall && alternateAnalysis.hasUnknownAbortCall;
|
|
8273
|
-
hasUnknownRemovalCall ||= consequentAnalysis.hasUnknownRemovalCall && alternateAnalysis.hasUnknownRemovalCall;
|
|
8274
|
-
}
|
|
8275
|
-
const testAnalysis = analyzeCleanupBody(child.test, context, new Set(visitedCleanupSymbolIds));
|
|
8276
|
-
if (testAnalysis) addCleanupAnalysis(testAnalysis);
|
|
8277
|
-
return false;
|
|
8278
|
-
}
|
|
8279
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
8280
|
-
const hasAmbiguousPath = isListenerPathAmbiguous(child, cleanupBody);
|
|
8281
|
-
const removalTarget = readDirectMemberReceiver(child.callee, "removeEventListener", context);
|
|
8282
|
-
if (removalTarget && !hasAmbiguousPath) {
|
|
8283
|
-
const removal = readListenerCandidate(child, "removeEventListener", context);
|
|
8284
|
-
if (removal) {
|
|
8285
|
-
removals.push(removal);
|
|
8286
|
-
hasUnknownRemovalCall ||= removal.callbackIdentity === null || removal.capture === null;
|
|
8287
|
-
} else hasUnknownRemovalCall = true;
|
|
8288
|
-
}
|
|
8289
|
-
if (!removalTarget && !hasAmbiguousPath) {
|
|
8290
|
-
const destructuredRemoval = readDestructuredRemovalCandidate(child, context);
|
|
8291
|
-
if (destructuredRemoval) removals.push(destructuredRemoval);
|
|
8292
|
-
}
|
|
8293
|
-
const controllerNode = readDirectMemberReceiver(child.callee, "abort");
|
|
8294
|
-
if (controllerNode) {
|
|
8295
|
-
const controllerSymbolId = resolveLocalAbortControllerSymbolId(controllerNode, context);
|
|
8296
|
-
if (controllerSymbolId === null) {
|
|
8297
|
-
if (!hasAmbiguousPath) hasUnknownAbortCall = true;
|
|
8298
|
-
} else if (!hasAmbiguousPath || isAbortGuaranteedByPath(child, cleanupBody, controllerSymbolId, context)) abortedControllerSymbolIds.add(controllerSymbolId);
|
|
8299
|
-
return;
|
|
8300
|
-
}
|
|
8301
|
-
if (hasAmbiguousPath) return;
|
|
8302
|
-
const boundAbortControllerSymbolId = resolveBoundAbortControllerSymbolId(child, context);
|
|
8303
|
-
if (boundAbortControllerSymbolId !== null) {
|
|
8304
|
-
abortedControllerSymbolIds.add(boundAbortControllerSymbolId);
|
|
8305
|
-
return;
|
|
8306
|
-
}
|
|
8307
|
-
const calledCleanup = resolveCalledCleanup(child, context);
|
|
8308
|
-
if (!calledCleanup || visitedCleanupSymbolIds.has(calledCleanup.symbolId)) return;
|
|
8309
|
-
visitedCleanupSymbolIds.add(calledCleanup.symbolId);
|
|
8310
|
-
const calledCleanupAnalysis = analyzeCleanupBody(calledCleanup.body, context, visitedCleanupSymbolIds);
|
|
8311
|
-
if (!calledCleanupAnalysis) return;
|
|
8312
|
-
addCleanupAnalysis(calledCleanupAnalysis);
|
|
8313
|
-
});
|
|
8314
|
-
if (hasAmbiguousReachability) return null;
|
|
8315
|
-
return {
|
|
8316
|
-
removals,
|
|
8317
|
-
abortedControllerSymbolIds,
|
|
8318
|
-
exhaustiveBranches,
|
|
8319
|
-
hasUnknownAbortCall,
|
|
8320
|
-
hasUnknownRemovalCall
|
|
8321
|
-
};
|
|
8322
|
-
};
|
|
8323
|
-
const analyzeEffectListeners = (effectBody, context) => {
|
|
8324
|
-
const effectInputs = collectEffectListenerInputs(effectBody, context);
|
|
8325
|
-
if (!effectInputs.hasCanonicalCleanupReturn || effectInputs.returnStatementCount !== 1 || effectInputs.cleanupBodies.length !== 1) return null;
|
|
8326
|
-
const removals = [];
|
|
8327
|
-
const abortedControllerSymbolIds = /* @__PURE__ */ new Set();
|
|
8328
|
-
const exhaustiveBranches = [];
|
|
8329
|
-
let hasUnknownAbortCall = false;
|
|
8330
|
-
let hasUnknownRemovalCall = false;
|
|
8331
|
-
const addAnalysis = (analysis) => {
|
|
8332
|
-
removals.push(...analysis.removals);
|
|
8333
|
-
exhaustiveBranches.push(...analysis.exhaustiveBranches);
|
|
8334
|
-
hasUnknownAbortCall ||= analysis.hasUnknownAbortCall;
|
|
8335
|
-
hasUnknownRemovalCall ||= analysis.hasUnknownRemovalCall;
|
|
8336
|
-
for (const controllerSymbolId of analysis.abortedControllerSymbolIds) abortedControllerSymbolIds.add(controllerSymbolId);
|
|
8337
|
-
};
|
|
8338
|
-
const setupAbortAnalysis = analyzeCleanupBody(effectBody, context);
|
|
8339
|
-
for (const cleanupBody of effectInputs.cleanupBodies) {
|
|
8340
|
-
const cleanupAnalysis = analyzeCleanupBody(cleanupBody, context);
|
|
8341
|
-
if (!cleanupAnalysis) return null;
|
|
8342
|
-
addAnalysis(cleanupAnalysis);
|
|
8343
|
-
}
|
|
8344
|
-
return {
|
|
8345
|
-
registrations: effectInputs.registrations,
|
|
8346
|
-
removals,
|
|
8347
|
-
abortedControllerSymbolIds,
|
|
8348
|
-
exhaustiveBranches,
|
|
8349
|
-
hasUnknownAbortCall,
|
|
8350
|
-
hasUnknownRemovalCall,
|
|
8351
|
-
setupAbortAnalysis
|
|
8352
|
-
};
|
|
8353
|
-
};
|
|
8354
|
-
const effectListenerCleanupMismatch = defineRule({
|
|
8355
|
-
id: "effect-listener-cleanup-mismatch",
|
|
8356
|
-
title: "Effect cleanup does not match its event listener",
|
|
8357
|
-
severity: "error",
|
|
8358
|
-
recommendation: "Pass the same callback binding and capture flag to `addEventListener` and `removeEventListener`, or abort the registration's local AbortController during cleanup.",
|
|
8359
|
-
create: (context) => ({ CallExpression(node) {
|
|
8360
|
-
if (!isReactApiCall(node, LISTENER_EFFECT_HOOK_NAMES, context.scopes)) return;
|
|
8361
|
-
const effectCallback = getEffectCallback(node);
|
|
8362
|
-
if (!isFunctionLike$1(effectCallback)) return;
|
|
8363
|
-
const listenerAnalysis = analyzeEffectListeners(effectCallback.body, context);
|
|
8364
|
-
if (!listenerAnalysis) return;
|
|
8365
|
-
for (const registration of listenerAnalysis.registrations) {
|
|
8366
|
-
if (registration.hasUnknownCancellation || listenerAnalysis.hasUnknownRemovalCall) continue;
|
|
8367
|
-
if (listenerAnalysis.registrations.filter((candidateRegistration) => candidateRegistration.targetKey === registration.targetKey && candidateRegistration.eventName === registration.eventName).length > 1) continue;
|
|
8368
|
-
if (registration.abortControllerSymbolId !== null && listenerAnalysis.hasUnknownAbortCall) continue;
|
|
8369
|
-
if (registration.abortControllerSymbolId !== null && listenerAnalysis.setupAbortAnalysis && doesListenerAnalysisAbortController(listenerAnalysis.setupAbortAnalysis, registration.abortControllerSymbolId)) continue;
|
|
8370
|
-
if (doesListenerAnalysisCancelRegistration(listenerAnalysis, registration)) continue;
|
|
8371
|
-
const candidateRemovals = listenerAnalysis.removals.filter((removal) => removal.targetKey === registration.targetKey && removal.eventName === registration.eventName);
|
|
8372
|
-
let firstProvableMismatch;
|
|
8373
|
-
let didFindNonMismatchCandidate = false;
|
|
8374
|
-
for (const removal of candidateRemovals) {
|
|
8375
|
-
if (!removal.callbackIdentity || removal.capture === null) {
|
|
8376
|
-
didFindNonMismatchCandidate = true;
|
|
8377
|
-
break;
|
|
8378
|
-
}
|
|
8379
|
-
const callbackComparison = compareListenerCallbackIdentities(registration.callbackIdentity, removal.callbackIdentity);
|
|
8380
|
-
if (callbackComparison === "unknown") {
|
|
8381
|
-
didFindNonMismatchCandidate = true;
|
|
8382
|
-
break;
|
|
8383
|
-
}
|
|
8384
|
-
if (callbackComparison === "same" && registration.capture === removal.capture) {
|
|
8385
|
-
didFindNonMismatchCandidate = true;
|
|
8386
|
-
break;
|
|
8387
|
-
}
|
|
8388
|
-
firstProvableMismatch ??= {
|
|
8389
|
-
removalNode: removal.node,
|
|
8390
|
-
removalCapture: removal.capture,
|
|
8391
|
-
callbackComparison
|
|
8392
|
-
};
|
|
8393
|
-
}
|
|
8394
|
-
if (candidateRemovals.length === 0 || didFindNonMismatchCandidate || !firstProvableMismatch) continue;
|
|
8395
|
-
context.report({
|
|
8396
|
-
node: firstProvableMismatch.removalNode,
|
|
8397
|
-
message: buildListenerCleanupMismatchMessage(registration.eventName, registration.capture, firstProvableMismatch.removalCapture, firstProvableMismatch.callbackComparison)
|
|
8398
|
-
});
|
|
8399
|
-
}
|
|
8400
|
-
} })
|
|
8401
|
-
});
|
|
8402
|
-
//#endregion
|
|
8403
7665
|
//#region src/plugin/utils/is-react-hook-name.ts
|
|
8404
7666
|
const isReactHookName = (name) => {
|
|
8405
7667
|
if (!name.startsWith("use")) return false;
|
|
@@ -11037,7 +10299,7 @@ const forbidElements = defineRule({
|
|
|
11037
10299
|
});
|
|
11038
10300
|
//#endregion
|
|
11039
10301
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
11040
|
-
const MESSAGE$
|
|
10302
|
+
const MESSAGE$52 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
11041
10303
|
const forwardRefUsesRef = defineRule({
|
|
11042
10304
|
id: "forward-ref-uses-ref",
|
|
11043
10305
|
title: "forwardRef without ref parameter",
|
|
@@ -11057,7 +10319,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
11057
10319
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
11058
10320
|
context.report({
|
|
11059
10321
|
node: inner,
|
|
11060
|
-
message: MESSAGE$
|
|
10322
|
+
message: MESSAGE$52
|
|
11061
10323
|
});
|
|
11062
10324
|
} })
|
|
11063
10325
|
});
|
|
@@ -11098,7 +10360,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
11098
10360
|
});
|
|
11099
10361
|
//#endregion
|
|
11100
10362
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
11101
|
-
const MESSAGE$
|
|
10363
|
+
const MESSAGE$51 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
11102
10364
|
const DEFAULT_HEADING_TAGS = [
|
|
11103
10365
|
"h1",
|
|
11104
10366
|
"h2",
|
|
@@ -11132,7 +10394,7 @@ const headingHasContent = defineRule({
|
|
|
11132
10394
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
11133
10395
|
context.report({
|
|
11134
10396
|
node,
|
|
11135
|
-
message: MESSAGE$
|
|
10397
|
+
message: MESSAGE$51
|
|
11136
10398
|
});
|
|
11137
10399
|
} };
|
|
11138
10400
|
}
|
|
@@ -11270,7 +10532,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
11270
10532
|
});
|
|
11271
10533
|
//#endregion
|
|
11272
10534
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
11273
|
-
const MESSAGE$
|
|
10535
|
+
const MESSAGE$50 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
11274
10536
|
const resolveSettings$39 = (settings) => {
|
|
11275
10537
|
const reactDoctor = settings?.["react-doctor"];
|
|
11276
10538
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -11317,13 +10579,13 @@ const htmlHasLang = defineRule({
|
|
|
11317
10579
|
if (!lang) {
|
|
11318
10580
|
context.report({
|
|
11319
10581
|
node: node.name,
|
|
11320
|
-
message: MESSAGE$
|
|
10582
|
+
message: MESSAGE$50
|
|
11321
10583
|
});
|
|
11322
10584
|
return;
|
|
11323
10585
|
}
|
|
11324
10586
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
11325
10587
|
node: lang,
|
|
11326
|
-
message: MESSAGE$
|
|
10588
|
+
message: MESSAGE$50
|
|
11327
10589
|
});
|
|
11328
10590
|
} };
|
|
11329
10591
|
}
|
|
@@ -11567,7 +10829,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
11567
10829
|
});
|
|
11568
10830
|
//#endregion
|
|
11569
10831
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
11570
|
-
const MESSAGE$
|
|
10832
|
+
const MESSAGE$49 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
11571
10833
|
const evaluateTitleValue = (value) => {
|
|
11572
10834
|
if (!value) return "missing";
|
|
11573
10835
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -11607,14 +10869,14 @@ const iframeHasTitle = defineRule({
|
|
|
11607
10869
|
if (!titleAttr) {
|
|
11608
10870
|
if (hasSpread || tag === "iframe") context.report({
|
|
11609
10871
|
node: node.name,
|
|
11610
|
-
message: MESSAGE$
|
|
10872
|
+
message: MESSAGE$49
|
|
11611
10873
|
});
|
|
11612
10874
|
return;
|
|
11613
10875
|
}
|
|
11614
10876
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
11615
10877
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
11616
10878
|
node: titleAttr,
|
|
11617
|
-
message: MESSAGE$
|
|
10879
|
+
message: MESSAGE$49
|
|
11618
10880
|
});
|
|
11619
10881
|
} })
|
|
11620
10882
|
});
|
|
@@ -11739,7 +11001,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
11739
11001
|
});
|
|
11740
11002
|
//#endregion
|
|
11741
11003
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
11742
|
-
const MESSAGE$
|
|
11004
|
+
const MESSAGE$48 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
11743
11005
|
const DEFAULT_COMPONENTS = ["img"];
|
|
11744
11006
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
11745
11007
|
"image",
|
|
@@ -11804,7 +11066,7 @@ const imgRedundantAlt = defineRule({
|
|
|
11804
11066
|
if (!altAttribute) return;
|
|
11805
11067
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
11806
11068
|
node: altAttribute,
|
|
11807
|
-
message: MESSAGE$
|
|
11069
|
+
message: MESSAGE$48
|
|
11808
11070
|
});
|
|
11809
11071
|
} };
|
|
11810
11072
|
}
|
|
@@ -12560,7 +11822,7 @@ const jotaiTqUseRawQueryAtom = defineRule({
|
|
|
12560
11822
|
if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
|
|
12561
11823
|
if (!isNodeOfType(specifier.local, "Identifier")) continue;
|
|
12562
11824
|
if (source === "jotai-tanstack-query") {
|
|
12563
|
-
const importedName = getImportedName(specifier);
|
|
11825
|
+
const importedName = getImportedName$1(specifier);
|
|
12564
11826
|
if (importedName && QUERY_ATOM_FACTORY_IMPORTED_NAMES.has(importedName)) queryAtomFactoryLocalNames.add(specifier.local.name);
|
|
12565
11827
|
continue;
|
|
12566
11828
|
}
|
|
@@ -13574,7 +12836,7 @@ const isStaticRegExpConstruction = (node) => {
|
|
|
13574
12836
|
const flagsArgument = node.arguments?.[1];
|
|
13575
12837
|
return isNodeOfType(node.callee, "Identifier") && node.callee.name === "RegExp" && isStaticPattern(patternArgument) && (flagsArgument === void 0 || isStaticPattern(flagsArgument));
|
|
13576
12838
|
};
|
|
13577
|
-
const MESSAGE$
|
|
12839
|
+
const MESSAGE$47 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
13578
12840
|
const jsHoistRegexp = defineRule({
|
|
13579
12841
|
id: "js-hoist-regexp",
|
|
13580
12842
|
title: "RegExp built inside a loop",
|
|
@@ -13585,13 +12847,13 @@ const jsHoistRegexp = defineRule({
|
|
|
13585
12847
|
NewExpression(node) {
|
|
13586
12848
|
if (isStaticRegExpConstruction(node)) context.report({
|
|
13587
12849
|
node,
|
|
13588
|
-
message: MESSAGE$
|
|
12850
|
+
message: MESSAGE$47
|
|
13589
12851
|
});
|
|
13590
12852
|
},
|
|
13591
12853
|
CallExpression(node) {
|
|
13592
12854
|
if (isStaticRegExpConstruction(node)) context.report({
|
|
13593
12855
|
node,
|
|
13594
|
-
message: MESSAGE$
|
|
12856
|
+
message: MESSAGE$47
|
|
13595
12857
|
});
|
|
13596
12858
|
}
|
|
13597
12859
|
}, { treatIteratorCallbacksAsLoops: true })
|
|
@@ -15673,7 +14935,7 @@ const jsxMaxDepth = defineRule({
|
|
|
15673
14935
|
});
|
|
15674
14936
|
//#endregion
|
|
15675
14937
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
15676
|
-
const MESSAGE$
|
|
14938
|
+
const MESSAGE$46 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
15677
14939
|
const LITERAL_TEXT_TAGS = new Set([
|
|
15678
14940
|
"code",
|
|
15679
14941
|
"pre",
|
|
@@ -15743,7 +15005,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
15743
15005
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
15744
15006
|
context.report({
|
|
15745
15007
|
node,
|
|
15746
|
-
message: MESSAGE$
|
|
15008
|
+
message: MESSAGE$46
|
|
15747
15009
|
});
|
|
15748
15010
|
} })
|
|
15749
15011
|
});
|
|
@@ -15774,7 +15036,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
15774
15036
|
};
|
|
15775
15037
|
//#endregion
|
|
15776
15038
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
15777
|
-
const MESSAGE$
|
|
15039
|
+
const MESSAGE$45 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
15778
15040
|
const CONTEXT_MODULES$1 = [
|
|
15779
15041
|
"react",
|
|
15780
15042
|
"use-context-selector",
|
|
@@ -15872,7 +15134,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
15872
15134
|
if (!isConstructedValue(innerExpression)) continue;
|
|
15873
15135
|
context.report({
|
|
15874
15136
|
node: attribute,
|
|
15875
|
-
message: MESSAGE$
|
|
15137
|
+
message: MESSAGE$45
|
|
15876
15138
|
});
|
|
15877
15139
|
}
|
|
15878
15140
|
}
|
|
@@ -16539,7 +15801,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
16539
15801
|
];
|
|
16540
15802
|
//#endregion
|
|
16541
15803
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
16542
|
-
const MESSAGE$
|
|
15804
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
16543
15805
|
const isDataArrayPropName = (propName) => {
|
|
16544
15806
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
16545
15807
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -16626,7 +15888,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
16626
15888
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
16627
15889
|
context.report({
|
|
16628
15890
|
node,
|
|
16629
|
-
message: MESSAGE$
|
|
15891
|
+
message: MESSAGE$44
|
|
16630
15892
|
});
|
|
16631
15893
|
}
|
|
16632
15894
|
};
|
|
@@ -16884,7 +16146,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
16884
16146
|
]);
|
|
16885
16147
|
//#endregion
|
|
16886
16148
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
16887
|
-
const MESSAGE$
|
|
16149
|
+
const MESSAGE$43 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
16888
16150
|
const isAccessorPredicateName = (propName) => {
|
|
16889
16151
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
16890
16152
|
if (propName.length <= prefix.length) continue;
|
|
@@ -17090,7 +16352,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
17090
16352
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
17091
16353
|
context.report({
|
|
17092
16354
|
node,
|
|
17093
|
-
message: MESSAGE$
|
|
16355
|
+
message: MESSAGE$43
|
|
17094
16356
|
});
|
|
17095
16357
|
}
|
|
17096
16358
|
};
|
|
@@ -17310,7 +16572,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
17310
16572
|
];
|
|
17311
16573
|
//#endregion
|
|
17312
16574
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
17313
|
-
const MESSAGE$
|
|
16575
|
+
const MESSAGE$42 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
17314
16576
|
const isConfigObjectPropName = (propName) => {
|
|
17315
16577
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
17316
16578
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -17399,7 +16661,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
17399
16661
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
17400
16662
|
context.report({
|
|
17401
16663
|
node,
|
|
17402
|
-
message: MESSAGE$
|
|
16664
|
+
message: MESSAGE$42
|
|
17403
16665
|
});
|
|
17404
16666
|
}
|
|
17405
16667
|
};
|
|
@@ -17407,7 +16669,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
17407
16669
|
});
|
|
17408
16670
|
//#endregion
|
|
17409
16671
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
17410
|
-
const MESSAGE$
|
|
16672
|
+
const MESSAGE$41 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
17411
16673
|
const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
|
|
17412
16674
|
const resolveSettings$29 = (settings) => {
|
|
17413
16675
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -17448,7 +16710,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
17448
16710
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
17449
16711
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
17450
16712
|
node: attribute,
|
|
17451
|
-
message: MESSAGE$
|
|
16713
|
+
message: MESSAGE$41
|
|
17452
16714
|
});
|
|
17453
16715
|
}
|
|
17454
16716
|
} };
|
|
@@ -18072,7 +17334,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
18072
17334
|
});
|
|
18073
17335
|
//#endregion
|
|
18074
17336
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
18075
|
-
const MESSAGE$
|
|
17337
|
+
const MESSAGE$40 = "You can't tell what props reach this element when you spread them.";
|
|
18076
17338
|
const resolveSettings$25 = (settings) => {
|
|
18077
17339
|
const reactDoctor = settings?.["react-doctor"];
|
|
18078
17340
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -18115,7 +17377,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
18115
17377
|
}
|
|
18116
17378
|
context.report({
|
|
18117
17379
|
node: attribute,
|
|
18118
|
-
message: MESSAGE$
|
|
17380
|
+
message: MESSAGE$40
|
|
18119
17381
|
});
|
|
18120
17382
|
didReportInFile = true;
|
|
18121
17383
|
return;
|
|
@@ -18390,7 +17652,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
18390
17652
|
});
|
|
18391
17653
|
//#endregion
|
|
18392
17654
|
//#region src/plugin/rules/a11y/lang.ts
|
|
18393
|
-
const MESSAGE$
|
|
17655
|
+
const MESSAGE$39 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
|
|
18394
17656
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
18395
17657
|
"aa",
|
|
18396
17658
|
"ab",
|
|
@@ -18870,7 +18132,7 @@ const lang = defineRule({
|
|
|
18870
18132
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
18871
18133
|
context.report({
|
|
18872
18134
|
node: langAttr,
|
|
18873
|
-
message: MESSAGE$
|
|
18135
|
+
message: MESSAGE$39
|
|
18874
18136
|
});
|
|
18875
18137
|
return;
|
|
18876
18138
|
}
|
|
@@ -18879,7 +18141,7 @@ const lang = defineRule({
|
|
|
18879
18141
|
if (value === null) return;
|
|
18880
18142
|
if (!isValidLangTag(value)) context.report({
|
|
18881
18143
|
node: langAttr,
|
|
18882
|
-
message: MESSAGE$
|
|
18144
|
+
message: MESSAGE$39
|
|
18883
18145
|
});
|
|
18884
18146
|
} })
|
|
18885
18147
|
});
|
|
@@ -18924,7 +18186,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
18924
18186
|
});
|
|
18925
18187
|
//#endregion
|
|
18926
18188
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
18927
|
-
const MESSAGE$
|
|
18189
|
+
const MESSAGE$38 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
18928
18190
|
const DEFAULT_AUDIO = ["audio"];
|
|
18929
18191
|
const DEFAULT_VIDEO = ["video"];
|
|
18930
18192
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -19013,7 +18275,7 @@ const mediaHasCaption = defineRule({
|
|
|
19013
18275
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
19014
18276
|
context.report({
|
|
19015
18277
|
node: node.name,
|
|
19016
|
-
message: MESSAGE$
|
|
18278
|
+
message: MESSAGE$38
|
|
19017
18279
|
});
|
|
19018
18280
|
return;
|
|
19019
18281
|
}
|
|
@@ -19032,7 +18294,7 @@ const mediaHasCaption = defineRule({
|
|
|
19032
18294
|
return kindValue.value.toLowerCase() === "captions";
|
|
19033
18295
|
})) context.report({
|
|
19034
18296
|
node: node.name,
|
|
19035
|
-
message: MESSAGE$
|
|
18297
|
+
message: MESSAGE$38
|
|
19036
18298
|
});
|
|
19037
18299
|
} };
|
|
19038
18300
|
}
|
|
@@ -21166,7 +20428,7 @@ const astMentionsSuspense = (programNode) => {
|
|
|
21166
20428
|
return false;
|
|
21167
20429
|
}
|
|
21168
20430
|
if (isNodeOfType(child, "ImportDeclaration") && child.source?.value === "react") {
|
|
21169
|
-
if ((child.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ImportSpecifier") && getImportedName(specifier) === "Suspense")) {
|
|
20431
|
+
if ((child.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ImportSpecifier") && getImportedName$1(specifier) === "Suspense")) {
|
|
21170
20432
|
didDetect = true;
|
|
21171
20433
|
return false;
|
|
21172
20434
|
}
|
|
@@ -21210,7 +20472,7 @@ const collectSuspenseLocalNames = (programNode) => {
|
|
|
21210
20472
|
for (const statement of programNode.body ?? []) {
|
|
21211
20473
|
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
21212
20474
|
if (statement.source?.value !== "react") continue;
|
|
21213
|
-
for (const specifier of statement.specifiers ?? []) if (isNodeOfType(specifier, "ImportSpecifier") && getImportedName(specifier) === "Suspense" && specifier.local?.name) names.add(specifier.local.name);
|
|
20475
|
+
for (const specifier of statement.specifiers ?? []) if (isNodeOfType(specifier, "ImportSpecifier") && getImportedName$1(specifier) === "Suspense" && specifier.local?.name) names.add(specifier.local.name);
|
|
21214
20476
|
}
|
|
21215
20477
|
return names;
|
|
21216
20478
|
};
|
|
@@ -21302,7 +20564,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
21302
20564
|
});
|
|
21303
20565
|
//#endregion
|
|
21304
20566
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
21305
|
-
const MESSAGE$
|
|
20567
|
+
const MESSAGE$37 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
21306
20568
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
21307
20569
|
const noAccessKey = defineRule({
|
|
21308
20570
|
id: "no-access-key",
|
|
@@ -21321,7 +20583,7 @@ const noAccessKey = defineRule({
|
|
|
21321
20583
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
21322
20584
|
context.report({
|
|
21323
20585
|
node: accessKey,
|
|
21324
|
-
message: MESSAGE$
|
|
20586
|
+
message: MESSAGE$37
|
|
21325
20587
|
});
|
|
21326
20588
|
return;
|
|
21327
20589
|
}
|
|
@@ -21331,7 +20593,7 @@ const noAccessKey = defineRule({
|
|
|
21331
20593
|
if (isUndefinedIdentifier(expression)) return;
|
|
21332
20594
|
context.report({
|
|
21333
20595
|
node: accessKey,
|
|
21334
|
-
message: MESSAGE$
|
|
20596
|
+
message: MESSAGE$37
|
|
21335
20597
|
});
|
|
21336
20598
|
}
|
|
21337
20599
|
} };
|
|
@@ -22199,7 +21461,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
22199
21461
|
});
|
|
22200
21462
|
//#endregion
|
|
22201
21463
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
22202
|
-
const MESSAGE$
|
|
21464
|
+
const MESSAGE$36 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
|
|
22203
21465
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
22204
21466
|
"button",
|
|
22205
21467
|
"embed",
|
|
@@ -22311,7 +21573,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
22311
21573
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
22312
21574
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
22313
21575
|
node: ariaHidden,
|
|
22314
|
-
message: MESSAGE$
|
|
21576
|
+
message: MESSAGE$36
|
|
22315
21577
|
});
|
|
22316
21578
|
} })
|
|
22317
21579
|
});
|
|
@@ -23192,7 +22454,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
23192
22454
|
};
|
|
23193
22455
|
//#endregion
|
|
23194
22456
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
23195
|
-
const MESSAGE$
|
|
22457
|
+
const MESSAGE$35 = "Your users can see & submit the wrong data when this list reorders.";
|
|
23196
22458
|
const SECOND_INDEX_METHODS = new Set([
|
|
23197
22459
|
"every",
|
|
23198
22460
|
"filter",
|
|
@@ -23311,14 +22573,14 @@ const noArrayIndexKey = defineRule({
|
|
|
23311
22573
|
if (propName !== "key") continue;
|
|
23312
22574
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
23313
22575
|
node: property,
|
|
23314
|
-
message: MESSAGE$
|
|
22576
|
+
message: MESSAGE$35
|
|
23315
22577
|
});
|
|
23316
22578
|
}
|
|
23317
22579
|
} })
|
|
23318
22580
|
});
|
|
23319
22581
|
//#endregion
|
|
23320
22582
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
23321
|
-
const MESSAGE$
|
|
22583
|
+
const MESSAGE$34 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
|
|
23322
22584
|
const noAsyncEffectCallback = defineRule({
|
|
23323
22585
|
id: "no-async-effect-callback",
|
|
23324
22586
|
title: "Async effect callback",
|
|
@@ -23332,13 +22594,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
23332
22594
|
if (!callback.async) return;
|
|
23333
22595
|
context.report({
|
|
23334
22596
|
node: callback,
|
|
23335
|
-
message: MESSAGE$
|
|
22597
|
+
message: MESSAGE$34
|
|
23336
22598
|
});
|
|
23337
22599
|
} })
|
|
23338
22600
|
});
|
|
23339
22601
|
//#endregion
|
|
23340
22602
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
23341
|
-
const MESSAGE$
|
|
22603
|
+
const MESSAGE$33 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
23342
22604
|
const resolveSettings$21 = (settings) => {
|
|
23343
22605
|
const reactDoctor = settings?.["react-doctor"];
|
|
23344
22606
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -23435,7 +22697,7 @@ const noAutofocus = defineRule({
|
|
|
23435
22697
|
if (isConditionallyRendered(node)) return;
|
|
23436
22698
|
context.report({
|
|
23437
22699
|
node: autoFocusAttribute,
|
|
23438
|
-
message: MESSAGE$
|
|
22700
|
+
message: MESSAGE$33
|
|
23439
22701
|
});
|
|
23440
22702
|
} };
|
|
23441
22703
|
}
|
|
@@ -23795,10 +23057,6 @@ const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
|
|
|
23795
23057
|
"ClassDeclaration",
|
|
23796
23058
|
"ClassExpression"
|
|
23797
23059
|
]);
|
|
23798
|
-
const REACT_CREATE_ELEMENT_OPTIONS = {
|
|
23799
|
-
allowGlobalReactNamespace: false,
|
|
23800
|
-
allowUnboundBareCalls: false
|
|
23801
|
-
};
|
|
23802
23060
|
const isCallArgumentFunctionExpression = (node) => {
|
|
23803
23061
|
if (node.type !== "ArrowFunctionExpression" && node.type !== "FunctionExpression") return false;
|
|
23804
23062
|
const parent = node.parent;
|
|
@@ -23806,10 +23064,44 @@ const isCallArgumentFunctionExpression = (node) => {
|
|
|
23806
23064
|
return parent.arguments.some((argumentNode) => argumentNode === node);
|
|
23807
23065
|
};
|
|
23808
23066
|
const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
|
|
23067
|
+
const isReactImport$1 = (symbol) => {
|
|
23068
|
+
let importDeclaration = symbol.declarationNode?.parent;
|
|
23069
|
+
while (importDeclaration && !isNodeOfType(importDeclaration, "ImportDeclaration")) importDeclaration = importDeclaration.parent ?? null;
|
|
23070
|
+
if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return false;
|
|
23071
|
+
return importDeclaration.source.value === "react";
|
|
23072
|
+
};
|
|
23073
|
+
const getImportedName = (symbol) => {
|
|
23074
|
+
if (symbol.kind !== "import") return null;
|
|
23075
|
+
if (!isReactImport$1(symbol)) return null;
|
|
23076
|
+
return getImportedName$1(symbol.declarationNode) ?? null;
|
|
23077
|
+
};
|
|
23078
|
+
const isReactNamespaceImport = (symbol) => {
|
|
23079
|
+
if (symbol.kind !== "import") return false;
|
|
23080
|
+
if (!isReactImport$1(symbol)) return false;
|
|
23081
|
+
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier");
|
|
23082
|
+
};
|
|
23083
|
+
const isReactCreateElementIdentifierCall = (callee, scopes) => {
|
|
23084
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
23085
|
+
const symbol = scopes.symbolFor(callee);
|
|
23086
|
+
return Boolean(symbol && getImportedName(symbol) === "createElement");
|
|
23087
|
+
};
|
|
23088
|
+
const isReactCreateElementMemberCall = (callee, scopes) => {
|
|
23089
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
23090
|
+
if (callee.computed) return false;
|
|
23091
|
+
if (!isNodeOfType(callee.object, "Identifier")) return false;
|
|
23092
|
+
if (!isNodeOfType(callee.property, "Identifier")) return false;
|
|
23093
|
+
if (callee.property.name !== "createElement") return false;
|
|
23094
|
+
const symbol = scopes.symbolFor(callee.object);
|
|
23095
|
+
return Boolean(symbol && isReactNamespaceImport(symbol));
|
|
23096
|
+
};
|
|
23097
|
+
const isReactCreateElementCall = (node, scopes) => {
|
|
23098
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
23099
|
+
return isReactCreateElementIdentifierCall(node.callee, scopes) || isReactCreateElementMemberCall(node.callee, scopes);
|
|
23100
|
+
};
|
|
23809
23101
|
const containsRenderOutput$1 = (node, rootNode, scopes) => {
|
|
23810
23102
|
if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
|
|
23811
23103
|
if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
|
|
23812
|
-
if (
|
|
23104
|
+
if (isReactCreateElementCall(node, scopes)) return true;
|
|
23813
23105
|
const nodeRecord = node;
|
|
23814
23106
|
for (const key of Object.keys(nodeRecord)) {
|
|
23815
23107
|
if (key === "parent") continue;
|
|
@@ -24472,7 +23764,7 @@ const noChainStateUpdates = defineRule({
|
|
|
24472
23764
|
});
|
|
24473
23765
|
//#endregion
|
|
24474
23766
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
24475
|
-
const MESSAGE$
|
|
23767
|
+
const MESSAGE$32 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
24476
23768
|
const noChildrenProp = defineRule({
|
|
24477
23769
|
id: "no-children-prop",
|
|
24478
23770
|
title: "Children passed as a prop",
|
|
@@ -24484,7 +23776,7 @@ const noChildrenProp = defineRule({
|
|
|
24484
23776
|
if (node.name.name !== "children") return;
|
|
24485
23777
|
context.report({
|
|
24486
23778
|
node: node.name,
|
|
24487
|
-
message: MESSAGE$
|
|
23779
|
+
message: MESSAGE$32
|
|
24488
23780
|
});
|
|
24489
23781
|
},
|
|
24490
23782
|
CallExpression(node) {
|
|
@@ -24497,7 +23789,7 @@ const noChildrenProp = defineRule({
|
|
|
24497
23789
|
const propertyKey = property.key;
|
|
24498
23790
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
24499
23791
|
node: propertyKey,
|
|
24500
|
-
message: MESSAGE$
|
|
23792
|
+
message: MESSAGE$32
|
|
24501
23793
|
});
|
|
24502
23794
|
}
|
|
24503
23795
|
}
|
|
@@ -24505,7 +23797,7 @@ const noChildrenProp = defineRule({
|
|
|
24505
23797
|
});
|
|
24506
23798
|
//#endregion
|
|
24507
23799
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
24508
|
-
const MESSAGE$
|
|
23800
|
+
const MESSAGE$31 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
24509
23801
|
const noCloneElement = defineRule({
|
|
24510
23802
|
id: "no-clone-element",
|
|
24511
23803
|
title: "cloneElement makes child props fragile",
|
|
@@ -24518,7 +23810,7 @@ const noCloneElement = defineRule({
|
|
|
24518
23810
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
24519
23811
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
24520
23812
|
node: callee,
|
|
24521
|
-
message: MESSAGE$
|
|
23813
|
+
message: MESSAGE$31
|
|
24522
23814
|
});
|
|
24523
23815
|
return;
|
|
24524
23816
|
}
|
|
@@ -24531,14 +23823,14 @@ const noCloneElement = defineRule({
|
|
|
24531
23823
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
24532
23824
|
context.report({
|
|
24533
23825
|
node: callee,
|
|
24534
|
-
message: MESSAGE$
|
|
23826
|
+
message: MESSAGE$31
|
|
24535
23827
|
});
|
|
24536
23828
|
}
|
|
24537
23829
|
} })
|
|
24538
23830
|
});
|
|
24539
23831
|
//#endregion
|
|
24540
23832
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
24541
|
-
const MESSAGE$
|
|
23833
|
+
const MESSAGE$30 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
24542
23834
|
const CONTEXT_MODULES = [
|
|
24543
23835
|
"react",
|
|
24544
23836
|
"use-context-selector",
|
|
@@ -24574,13 +23866,13 @@ const noCreateContextInRender = defineRule({
|
|
|
24574
23866
|
if (!componentOrHookName) return;
|
|
24575
23867
|
context.report({
|
|
24576
23868
|
node,
|
|
24577
|
-
message: `${MESSAGE$
|
|
23869
|
+
message: `${MESSAGE$30} (called inside "${componentOrHookName}")`
|
|
24578
23870
|
});
|
|
24579
23871
|
} })
|
|
24580
23872
|
});
|
|
24581
23873
|
//#endregion
|
|
24582
23874
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
24583
|
-
const MESSAGE$
|
|
23875
|
+
const MESSAGE$29 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
|
|
24584
23876
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
24585
23877
|
const parent = functionNode.parent;
|
|
24586
23878
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -24610,7 +23902,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
24610
23902
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
|
|
24611
23903
|
context.report({
|
|
24612
23904
|
node,
|
|
24613
|
-
message: MESSAGE$
|
|
23905
|
+
message: MESSAGE$29
|
|
24614
23906
|
});
|
|
24615
23907
|
} })
|
|
24616
23908
|
});
|
|
@@ -24750,7 +24042,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
24750
24042
|
});
|
|
24751
24043
|
//#endregion
|
|
24752
24044
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
24753
|
-
const MESSAGE$
|
|
24045
|
+
const MESSAGE$28 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
24754
24046
|
const noDanger = defineRule({
|
|
24755
24047
|
id: "no-danger",
|
|
24756
24048
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -24764,7 +24056,7 @@ const noDanger = defineRule({
|
|
|
24764
24056
|
if (!propAttribute) return;
|
|
24765
24057
|
context.report({
|
|
24766
24058
|
node: propAttribute.name,
|
|
24767
|
-
message: MESSAGE$
|
|
24059
|
+
message: MESSAGE$28
|
|
24768
24060
|
});
|
|
24769
24061
|
},
|
|
24770
24062
|
CallExpression(node) {
|
|
@@ -24776,7 +24068,7 @@ const noDanger = defineRule({
|
|
|
24776
24068
|
const propertyKey = property.key;
|
|
24777
24069
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
24778
24070
|
node: propertyKey,
|
|
24779
|
-
message: MESSAGE$
|
|
24071
|
+
message: MESSAGE$28
|
|
24780
24072
|
});
|
|
24781
24073
|
}
|
|
24782
24074
|
}
|
|
@@ -24799,7 +24091,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
24799
24091
|
};
|
|
24800
24092
|
//#endregion
|
|
24801
24093
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
24802
|
-
const MESSAGE$
|
|
24094
|
+
const MESSAGE$27 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
24803
24095
|
const mergePropsShape = (target, source) => {
|
|
24804
24096
|
target.hasDangerously ||= source.hasDangerously;
|
|
24805
24097
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -24874,7 +24166,7 @@ const noDangerWithChildren = defineRule({
|
|
|
24874
24166
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
24875
24167
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
24876
24168
|
node: opening,
|
|
24877
|
-
message: MESSAGE$
|
|
24169
|
+
message: MESSAGE$27
|
|
24878
24170
|
});
|
|
24879
24171
|
},
|
|
24880
24172
|
CallExpression(node) {
|
|
@@ -24887,7 +24179,7 @@ const noDangerWithChildren = defineRule({
|
|
|
24887
24179
|
const positionalChildren = node.arguments.slice(2);
|
|
24888
24180
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
24889
24181
|
node,
|
|
24890
|
-
message: MESSAGE$
|
|
24182
|
+
message: MESSAGE$27
|
|
24891
24183
|
});
|
|
24892
24184
|
}
|
|
24893
24185
|
})
|
|
@@ -25125,6 +24417,16 @@ const isInitialOnlyPropName = (propName) => {
|
|
|
25125
24417
|
return /^initial[A-Z]/.test(propName) || /^default[A-Z]/.test(propName) || /^seed[A-Z]/.test(propName) || /^starting[A-Z]/.test(propName) || /^baseline[A-Z]/.test(propName) || /^preset[A-Z]/.test(propName);
|
|
25126
24418
|
};
|
|
25127
24419
|
//#endregion
|
|
24420
|
+
//#region src/plugin/rules/state-and-effects/utils/static-member-property-name.ts
|
|
24421
|
+
const getStaticMemberPropertyName = (node) => {
|
|
24422
|
+
if (!node) return null;
|
|
24423
|
+
const unwrappedNode = stripParenExpression(node);
|
|
24424
|
+
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
24425
|
+
if (!unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier")) return unwrappedNode.property.name;
|
|
24426
|
+
if (unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Literal") && typeof unwrappedNode.property.value === "string") return unwrappedNode.property.value;
|
|
24427
|
+
return null;
|
|
24428
|
+
};
|
|
24429
|
+
//#endregion
|
|
25128
24430
|
//#region src/plugin/rules/state-and-effects/utils/event-handler-reference.ts
|
|
25129
24431
|
const isEventHandlerName = (name) => /^on[A-Z]/.test(name);
|
|
25130
24432
|
const getStaticMemberReferenceName = (node, resolveName = (name) => name) => {
|
|
@@ -25133,6 +24435,13 @@ const getStaticMemberReferenceName = (node, resolveName = (name) => name) => {
|
|
|
25133
24435
|
const propertyName = getStaticMemberPropertyName(node);
|
|
25134
24436
|
return propertyName ? `${resolveName(node.object.name)}.${propertyName}` : null;
|
|
25135
24437
|
};
|
|
24438
|
+
const getStaticPropertyKeyName = (node) => {
|
|
24439
|
+
if (!isNodeOfType(node, "Property")) return null;
|
|
24440
|
+
if (node.computed) return null;
|
|
24441
|
+
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
24442
|
+
if (isNodeOfType(node.key, "Literal")) return String(node.key.value);
|
|
24443
|
+
return null;
|
|
24444
|
+
};
|
|
25136
24445
|
const isEventHandlerValue = (node, eventHandlerReferenceNames, resolveName = (name) => name) => {
|
|
25137
24446
|
if (isInlineFunctionExpression(node)) return true;
|
|
25138
24447
|
if (isNodeOfType(node, "Identifier")) return eventHandlerReferenceNames.has(resolveName(node.name));
|
|
@@ -26114,7 +25423,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
26114
25423
|
//#endregion
|
|
26115
25424
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
26116
25425
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
26117
|
-
const MESSAGE$
|
|
25426
|
+
const MESSAGE$26 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
26118
25427
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
26119
25428
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
26120
25429
|
let ancestor = setStateCall.parent;
|
|
@@ -26242,7 +25551,7 @@ const noDidMountSetState = defineRule({
|
|
|
26242
25551
|
}
|
|
26243
25552
|
context.report({
|
|
26244
25553
|
node: node.callee,
|
|
26245
|
-
message: MESSAGE$
|
|
25554
|
+
message: MESSAGE$26
|
|
26246
25555
|
});
|
|
26247
25556
|
} };
|
|
26248
25557
|
}
|
|
@@ -26250,7 +25559,7 @@ const noDidMountSetState = defineRule({
|
|
|
26250
25559
|
//#endregion
|
|
26251
25560
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
26252
25561
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
26253
|
-
const MESSAGE$
|
|
25562
|
+
const MESSAGE$25 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
26254
25563
|
const EQUALITY_OPERATORS = new Set([
|
|
26255
25564
|
"==",
|
|
26256
25565
|
"===",
|
|
@@ -26362,7 +25671,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
26362
25671
|
if (isInsideDiffGuard(node)) return;
|
|
26363
25672
|
context.report({
|
|
26364
25673
|
node: node.callee,
|
|
26365
|
-
message: MESSAGE$
|
|
25674
|
+
message: MESSAGE$25
|
|
26366
25675
|
});
|
|
26367
25676
|
} };
|
|
26368
25677
|
}
|
|
@@ -26385,7 +25694,7 @@ const isStateMemberExpression = (node) => {
|
|
|
26385
25694
|
};
|
|
26386
25695
|
//#endregion
|
|
26387
25696
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
26388
|
-
const MESSAGE$
|
|
25697
|
+
const MESSAGE$24 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
|
|
26389
25698
|
const shouldIgnoreMutation = (node) => {
|
|
26390
25699
|
let isConstructor = false;
|
|
26391
25700
|
let isInsideCallExpression = false;
|
|
@@ -26407,7 +25716,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
26407
25716
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
26408
25717
|
context.report({
|
|
26409
25718
|
node: reportNode,
|
|
26410
|
-
message: MESSAGE$
|
|
25719
|
+
message: MESSAGE$24
|
|
26411
25720
|
});
|
|
26412
25721
|
};
|
|
26413
25722
|
const noDirectMutationState = defineRule({
|
|
@@ -26753,7 +26062,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
26753
26062
|
});
|
|
26754
26063
|
//#endregion
|
|
26755
26064
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
26756
|
-
const MESSAGE$
|
|
26065
|
+
const MESSAGE$23 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
26757
26066
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
26758
26067
|
const noDocumentWrite = defineRule({
|
|
26759
26068
|
id: "no-document-write",
|
|
@@ -26768,7 +26077,7 @@ const noDocumentWrite = defineRule({
|
|
|
26768
26077
|
if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
|
|
26769
26078
|
context.report({
|
|
26770
26079
|
node,
|
|
26771
|
-
message: MESSAGE$
|
|
26080
|
+
message: MESSAGE$23
|
|
26772
26081
|
});
|
|
26773
26082
|
} })
|
|
26774
26083
|
});
|
|
@@ -28166,7 +27475,7 @@ const collectVariableDeclarationReferenceNames = (node, scope, eventHandlerRefer
|
|
|
28166
27475
|
};
|
|
28167
27476
|
const collectPropertyReferenceNames = (node, scope, eventHandlerReferenceNames) => {
|
|
28168
27477
|
if (!isNodeOfType(node, "Property")) return /* @__PURE__ */ new Set();
|
|
28169
|
-
const propertyName = getStaticPropertyKeyName(node
|
|
27478
|
+
const propertyName = getStaticPropertyKeyName(node);
|
|
28170
27479
|
if (propertyName && isEventHandlerName(propertyName) && isEventHandlerValue(node.value, eventHandlerReferenceNames, (name) => resolveBindingName(scope, name))) return /* @__PURE__ */ new Set();
|
|
28171
27480
|
const names = /* @__PURE__ */ new Set();
|
|
28172
27481
|
if (node.computed) addNames$1(names, collectScopedReferenceNames(node.key, scope, eventHandlerReferenceNames));
|
|
@@ -28544,7 +27853,7 @@ const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLoc
|
|
|
28544
27853
|
if (!isNodeOfType(node, "ObjectExpression")) return;
|
|
28545
27854
|
for (const property of node.properties ?? []) {
|
|
28546
27855
|
if (!isNodeOfType(property, "Property")) continue;
|
|
28547
|
-
const propertyName = getStaticPropertyKeyName(property
|
|
27856
|
+
const propertyName = getStaticPropertyKeyName(property);
|
|
28548
27857
|
if (!propertyName) continue;
|
|
28549
27858
|
if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
|
|
28550
27859
|
functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
|
|
@@ -28903,7 +28212,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
28903
28212
|
"ReactDOM",
|
|
28904
28213
|
"ReactDom"
|
|
28905
28214
|
]);
|
|
28906
|
-
const MESSAGE$
|
|
28215
|
+
const MESSAGE$22 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
28907
28216
|
const noFindDomNode = defineRule({
|
|
28908
28217
|
id: "no-find-dom-node",
|
|
28909
28218
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -28914,7 +28223,7 @@ const noFindDomNode = defineRule({
|
|
|
28914
28223
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
28915
28224
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
28916
28225
|
node: callee,
|
|
28917
|
-
message: MESSAGE$
|
|
28226
|
+
message: MESSAGE$22
|
|
28918
28227
|
});
|
|
28919
28228
|
return;
|
|
28920
28229
|
}
|
|
@@ -28925,7 +28234,7 @@ const noFindDomNode = defineRule({
|
|
|
28925
28234
|
if (callee.property.name !== "findDOMNode") return;
|
|
28926
28235
|
context.report({
|
|
28927
28236
|
node: callee.property,
|
|
28928
|
-
message: MESSAGE$
|
|
28237
|
+
message: MESSAGE$22
|
|
28929
28238
|
});
|
|
28930
28239
|
}
|
|
28931
28240
|
} })
|
|
@@ -29055,7 +28364,7 @@ const noFlushSync = defineRule({
|
|
|
29055
28364
|
if (node.source?.value !== "react-dom") return;
|
|
29056
28365
|
for (const specifier of node.specifiers ?? []) {
|
|
29057
28366
|
if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
|
|
29058
|
-
if (getImportedName(specifier) !== "flushSync") continue;
|
|
28367
|
+
if (getImportedName$1(specifier) !== "flushSync") continue;
|
|
29059
28368
|
const program = findProgram(node);
|
|
29060
28369
|
if (program) {
|
|
29061
28370
|
if (importsImperativeDomLibrary(program)) return;
|
|
@@ -29470,7 +28779,7 @@ const noGrayOnColoredBackground = defineRule({
|
|
|
29470
28779
|
});
|
|
29471
28780
|
//#endregion
|
|
29472
28781
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
29473
|
-
const MESSAGE$
|
|
28782
|
+
const MESSAGE$21 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
|
|
29474
28783
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
29475
28784
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
29476
28785
|
title: "Lazy image with high fetchPriority",
|
|
@@ -29484,158 +28793,11 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
29484
28793
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
29485
28794
|
context.report({
|
|
29486
28795
|
node: node.name,
|
|
29487
|
-
message: MESSAGE$
|
|
28796
|
+
message: MESSAGE$21
|
|
29488
28797
|
});
|
|
29489
28798
|
} })
|
|
29490
28799
|
});
|
|
29491
28800
|
//#endregion
|
|
29492
|
-
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
29493
|
-
const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
29494
|
-
const REACT_USE_REF_OPTIONS = {
|
|
29495
|
-
allowGlobalReactNamespace: false,
|
|
29496
|
-
allowUnboundBareCalls: false
|
|
29497
|
-
};
|
|
29498
|
-
const hasVisibleBindingNamed = (node, bindingName, scopes) => {
|
|
29499
|
-
let scope = scopes.scopeFor(node);
|
|
29500
|
-
while (true) {
|
|
29501
|
-
if (scope.symbolsByName.has(bindingName)) return true;
|
|
29502
|
-
if (!scope.parent) return false;
|
|
29503
|
-
scope = scope.parent;
|
|
29504
|
-
}
|
|
29505
|
-
};
|
|
29506
|
-
const hasEnclosingTypeParameterNamed = (node, typeParameterName) => {
|
|
29507
|
-
let ancestor = node.parent;
|
|
29508
|
-
while (ancestor) {
|
|
29509
|
-
if ("typeParameters" in ancestor) {
|
|
29510
|
-
const typeParameters = ancestor.typeParameters;
|
|
29511
|
-
if (typeParameters && isNodeOfType(typeParameters, "TSTypeParameterDeclaration") && typeParameters.params.some((typeParameter) => isNodeOfType(typeParameter, "TSTypeParameter") && isNodeOfType(typeParameter.name, "Identifier") && typeParameter.name.name === typeParameterName)) return true;
|
|
29512
|
-
}
|
|
29513
|
-
ancestor = ancestor.parent;
|
|
29514
|
-
}
|
|
29515
|
-
return false;
|
|
29516
|
-
};
|
|
29517
|
-
const isHtmlInputElementType = (typeNode, scopes) => {
|
|
29518
|
-
if (isNodeOfType(typeNode, "TSTypeReference")) return isNodeOfType(typeNode.typeName, "Identifier") && typeNode.typeName.name === "HTMLInputElement" && !hasVisibleBindingNamed(typeNode, "HTMLInputElement", scopes) && !hasEnclosingTypeParameterNamed(typeNode, "HTMLInputElement");
|
|
29519
|
-
if (!isNodeOfType(typeNode, "TSUnionType")) return false;
|
|
29520
|
-
let hasHtmlInputElementMember = false;
|
|
29521
|
-
for (const unionMember of typeNode.types) {
|
|
29522
|
-
if (isNodeOfType(unionMember, "TSNullKeyword") || isNodeOfType(unionMember, "TSUndefinedKeyword")) continue;
|
|
29523
|
-
if (!isHtmlInputElementType(unionMember, scopes)) return false;
|
|
29524
|
-
hasHtmlInputElementMember = true;
|
|
29525
|
-
}
|
|
29526
|
-
return hasHtmlInputElementMember;
|
|
29527
|
-
};
|
|
29528
|
-
const getBindingTypeAnnotation = (symbol) => {
|
|
29529
|
-
if (!isNodeOfType(symbol.bindingIdentifier, "Identifier")) return null;
|
|
29530
|
-
const annotation = symbol.bindingIdentifier.typeAnnotation;
|
|
29531
|
-
if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
|
|
29532
|
-
return annotation.typeAnnotation;
|
|
29533
|
-
};
|
|
29534
|
-
const getDirectConstInitializer = (symbol) => {
|
|
29535
|
-
if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
29536
|
-
return symbol.initializer;
|
|
29537
|
-
};
|
|
29538
|
-
const hasTypedHtmlInputRefOrigin = (rawExpression, scopes) => {
|
|
29539
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
29540
|
-
let expression = stripParenExpression(rawExpression);
|
|
29541
|
-
while (isNodeOfType(expression, "Identifier")) {
|
|
29542
|
-
const symbol = scopes.symbolFor(expression);
|
|
29543
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
29544
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
29545
|
-
if (!initializer) return false;
|
|
29546
|
-
visitedSymbolIds.add(symbol.id);
|
|
29547
|
-
expression = stripParenExpression(initializer);
|
|
29548
|
-
}
|
|
29549
|
-
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
29550
|
-
if (!isReactApiCall(expression, "useRef", scopes, REACT_USE_REF_OPTIONS)) return false;
|
|
29551
|
-
if (!isNodeOfType(expression.typeArguments, "TSTypeParameterInstantiation")) return false;
|
|
29552
|
-
const typeArgument = expression.typeArguments.params[0];
|
|
29553
|
-
return Boolean(typeArgument && isHtmlInputElementType(typeArgument, scopes));
|
|
29554
|
-
};
|
|
29555
|
-
const isProvenHtmlInputElement = (rawExpression, scopes) => {
|
|
29556
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
29557
|
-
let expression = stripParenExpression(rawExpression);
|
|
29558
|
-
while (true) {
|
|
29559
|
-
if (isNodeOfType(expression, "MemberExpression") && !expression.computed && isNodeOfType(expression.property, "Identifier") && expression.property.name === "current") return hasTypedHtmlInputRefOrigin(expression.object, scopes);
|
|
29560
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
29561
|
-
const symbol = scopes.symbolFor(expression);
|
|
29562
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
29563
|
-
const typeAnnotation = getBindingTypeAnnotation(symbol);
|
|
29564
|
-
if (typeAnnotation && isHtmlInputElementType(typeAnnotation, scopes)) return true;
|
|
29565
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
29566
|
-
if (!initializer) return false;
|
|
29567
|
-
visitedSymbolIds.add(symbol.id);
|
|
29568
|
-
expression = stripParenExpression(initializer);
|
|
29569
|
-
}
|
|
29570
|
-
};
|
|
29571
|
-
const getIndeterminateAttributeReceiver = (node) => {
|
|
29572
|
-
const callee = stripParenExpression(node.callee);
|
|
29573
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setAttribute" && callee.property.name !== "toggleAttribute") return null;
|
|
29574
|
-
const attributeName = node.arguments[0];
|
|
29575
|
-
if (!isNodeOfType(attributeName, "Literal") || attributeName.value !== "indeterminate") return null;
|
|
29576
|
-
if (callee.property.name === "setAttribute") {
|
|
29577
|
-
const attributeValue = node.arguments[1];
|
|
29578
|
-
return attributeValue && !isNodeOfType(attributeValue, "SpreadElement") ? callee.object : null;
|
|
29579
|
-
}
|
|
29580
|
-
if (node.arguments.length === 1) return callee.object;
|
|
29581
|
-
const forceArgument = node.arguments[1];
|
|
29582
|
-
if (!forceArgument || isNodeOfType(forceArgument, "SpreadElement")) return null;
|
|
29583
|
-
const forceExpression = stripParenExpression(forceArgument);
|
|
29584
|
-
if (!isNodeOfType(forceExpression, "Literal") || forceExpression.value !== true) return null;
|
|
29585
|
-
return callee.object;
|
|
29586
|
-
};
|
|
29587
|
-
const noIndeterminateAttribute = defineRule({
|
|
29588
|
-
id: "no-indeterminate-attribute",
|
|
29589
|
-
title: "Indeterminate checkbox state set as an attribute",
|
|
29590
|
-
severity: "warn",
|
|
29591
|
-
recommendation: "Assign the checkbox element's `indeterminate` DOM property, usually through a ref, because the HTML attribute does not control its visual state.",
|
|
29592
|
-
create: (context) => {
|
|
29593
|
-
if (classifyReactNativeFileTarget(context) === "react-native") return {};
|
|
29594
|
-
return {
|
|
29595
|
-
JSXOpeningElement(node) {
|
|
29596
|
-
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
|
|
29597
|
-
let typeAttribute = null;
|
|
29598
|
-
let typeAttributeIndex = null;
|
|
29599
|
-
let indeterminateAttribute = null;
|
|
29600
|
-
let indeterminateAttributeIndex = null;
|
|
29601
|
-
let lastSpreadIndex = null;
|
|
29602
|
-
for (let attributeIndex = 0; attributeIndex < node.attributes.length; attributeIndex++) {
|
|
29603
|
-
const attribute = node.attributes[attributeIndex];
|
|
29604
|
-
if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
|
|
29605
|
-
lastSpreadIndex = attributeIndex;
|
|
29606
|
-
continue;
|
|
29607
|
-
}
|
|
29608
|
-
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) continue;
|
|
29609
|
-
if (attribute.name.name === "type") {
|
|
29610
|
-
typeAttribute = attribute;
|
|
29611
|
-
typeAttributeIndex = attributeIndex;
|
|
29612
|
-
} else if (attribute.name.name === "indeterminate") {
|
|
29613
|
-
indeterminateAttribute = attribute;
|
|
29614
|
-
indeterminateAttributeIndex = attributeIndex;
|
|
29615
|
-
}
|
|
29616
|
-
}
|
|
29617
|
-
if (!typeAttribute || typeAttributeIndex === null || !indeterminateAttribute || indeterminateAttributeIndex === null) return;
|
|
29618
|
-
if (lastSpreadIndex !== null && (lastSpreadIndex > typeAttributeIndex || lastSpreadIndex > indeterminateAttributeIndex)) return;
|
|
29619
|
-
const inputTypeValues = getJsxPropStaticStringValues(typeAttribute, context.scopes);
|
|
29620
|
-
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
29621
|
-
context.report({
|
|
29622
|
-
node: indeterminateAttribute,
|
|
29623
|
-
message: MESSAGE$21
|
|
29624
|
-
});
|
|
29625
|
-
},
|
|
29626
|
-
CallExpression(node) {
|
|
29627
|
-
const receiver = getIndeterminateAttributeReceiver(node);
|
|
29628
|
-
if (!receiver) return;
|
|
29629
|
-
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
29630
|
-
context.report({
|
|
29631
|
-
node,
|
|
29632
|
-
message: MESSAGE$21
|
|
29633
|
-
});
|
|
29634
|
-
}
|
|
29635
|
-
};
|
|
29636
|
-
}
|
|
29637
|
-
});
|
|
29638
|
-
//#endregion
|
|
29639
28801
|
//#region src/plugin/utils/contains-non-deterministic-source.ts
|
|
29640
28802
|
const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
|
|
29641
28803
|
"Math.random",
|
|
@@ -31160,64 +30322,6 @@ const noManyBooleanProps = defineRule({
|
|
|
31160
30322
|
}
|
|
31161
30323
|
});
|
|
31162
30324
|
//#endregion
|
|
31163
|
-
//#region src/plugin/utils/is-global-match-media-call.ts
|
|
31164
|
-
const isGlobalMatchMediaCall = (node, scopes) => {
|
|
31165
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
31166
|
-
const callee = stripParenExpression(node.callee);
|
|
31167
|
-
if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
|
|
31168
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
|
|
31169
|
-
const receiver = stripParenExpression(callee.object);
|
|
31170
|
-
return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
|
|
31171
|
-
};
|
|
31172
|
-
//#endregion
|
|
31173
|
-
//#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
|
|
31174
|
-
const REACT_USE_STATE_OPTIONS = {
|
|
31175
|
-
allowGlobalReactNamespace: true,
|
|
31176
|
-
allowUnboundBareCalls: false
|
|
31177
|
-
};
|
|
31178
|
-
const isReactUseStateCall = (node, context) => isReactApiCall(node, "useState", context.scopes, REACT_USE_STATE_OPTIONS);
|
|
31179
|
-
const findDirectMatchMediaCall = (root, context) => {
|
|
31180
|
-
let matchMediaCall = null;
|
|
31181
|
-
walkAst(root, (visitedNode) => {
|
|
31182
|
-
if (matchMediaCall) return false;
|
|
31183
|
-
if (isFunctionLike$1(visitedNode)) return false;
|
|
31184
|
-
if (isGlobalMatchMediaCall(visitedNode, context.scopes)) {
|
|
31185
|
-
matchMediaCall = visitedNode;
|
|
31186
|
-
return false;
|
|
31187
|
-
}
|
|
31188
|
-
});
|
|
31189
|
-
return matchMediaCall;
|
|
31190
|
-
};
|
|
31191
|
-
const findMatchMediaCallDuringInitialization = (initializer, context) => {
|
|
31192
|
-
const rootInitializer = stripParenExpression(initializer);
|
|
31193
|
-
if (!isFunctionLike$1(rootInitializer)) return findDirectMatchMediaCall(rootInitializer, context);
|
|
31194
|
-
if (rootInitializer.async || rootInitializer.generator) return null;
|
|
31195
|
-
return findDirectMatchMediaCall(rootInitializer.body, context);
|
|
31196
|
-
};
|
|
31197
|
-
const noMatchMediaInStateInitializer = defineRule({
|
|
31198
|
-
id: "no-match-media-in-state-initializer",
|
|
31199
|
-
title: "matchMedia in state initializer",
|
|
31200
|
-
severity: "warn",
|
|
31201
|
-
category: "Correctness",
|
|
31202
|
-
disabledWhen: ["vite", "cra"],
|
|
31203
|
-
recommendation: "Prefer CSS media queries for layout, or subscribe with `useSyncExternalStore` and provide a stable server snapshot.",
|
|
31204
|
-
create: (context) => {
|
|
31205
|
-
if (isTestlikeFilename(context.filename)) return {};
|
|
31206
|
-
if (classifyReactNativeFileTarget(context) === "react-native") return {};
|
|
31207
|
-
return { CallExpression(node) {
|
|
31208
|
-
if (!isReactUseStateCall(node, context)) return;
|
|
31209
|
-
const initializer = node.arguments?.[0];
|
|
31210
|
-
if (!initializer || initializer.type === "SpreadElement") return;
|
|
31211
|
-
const matchMediaCall = findMatchMediaCallDuringInitialization(initializer, context);
|
|
31212
|
-
if (!matchMediaCall) return;
|
|
31213
|
-
context.report({
|
|
31214
|
-
node: matchMediaCall,
|
|
31215
|
-
message: "`matchMedia()` in a useState initializer can cause an SSR crash or seed different server and hydration state. Prefer CSS media queries for layout, or use `useSyncExternalStore` with a stable server snapshot."
|
|
31216
|
-
});
|
|
31217
|
-
} };
|
|
31218
|
-
}
|
|
31219
|
-
});
|
|
31220
|
-
//#endregion
|
|
31221
30325
|
//#region src/plugin/rules/state-and-effects/no-mirror-prop-effect.ts
|
|
31222
30326
|
const getPropRootName = (expression, propNames) => {
|
|
31223
30327
|
const rootName = getRootIdentifierName(expression, { followCallChains: true });
|
|
@@ -34001,7 +33105,7 @@ const createDeprecatedReactImportRule = ({ source, messages, handleExtraSource }
|
|
|
34001
33105
|
for (const specifier of node.specifiers ?? []) {
|
|
34002
33106
|
if (isNodeOfType(specifier, "ImportSpecifier")) {
|
|
34003
33107
|
if (specifier.importKind === "type") continue;
|
|
34004
|
-
const importedName = getImportedName(specifier);
|
|
33108
|
+
const importedName = getImportedName$1(specifier);
|
|
34005
33109
|
if (!importedName) continue;
|
|
34006
33110
|
const message = messages.get(importedName);
|
|
34007
33111
|
if (message) context.report({
|
|
@@ -34054,7 +33158,7 @@ const buildTestUtilsMessage = (importedName) => {
|
|
|
34054
33158
|
const reportTestUtilsImports = (node, context) => {
|
|
34055
33159
|
for (const specifier of node.specifiers ?? []) {
|
|
34056
33160
|
if (isNodeOfType(specifier, "ImportSpecifier")) {
|
|
34057
|
-
const importedName = getImportedName(specifier) ?? "default";
|
|
33161
|
+
const importedName = getImportedName$1(specifier) ?? "default";
|
|
34058
33162
|
context.report({
|
|
34059
33163
|
node: specifier,
|
|
34060
33164
|
message: buildTestUtilsMessage(importedName)
|
|
@@ -44114,7 +43218,7 @@ const rnNoDeprecatedModules = defineRule({
|
|
|
44114
43218
|
for (const specifier of node.specifiers ?? []) {
|
|
44115
43219
|
if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
|
|
44116
43220
|
if (specifier.importKind === "type") continue;
|
|
44117
|
-
const importedName = getImportedName(specifier);
|
|
43221
|
+
const importedName = getImportedName$1(specifier);
|
|
44118
43222
|
if (!importedName) continue;
|
|
44119
43223
|
if (!DEPRECATED_RN_MODULE_REPLACEMENTS.get(importedName)) continue;
|
|
44120
43224
|
context.report({
|
|
@@ -44666,7 +43770,7 @@ const rnNoPanresponder = defineRule({
|
|
|
44666
43770
|
for (const specifier of node.specifiers ?? []) {
|
|
44667
43771
|
if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
|
|
44668
43772
|
if (specifier.importKind === "type") continue;
|
|
44669
|
-
if (getImportedName(specifier) !== "PanResponder") continue;
|
|
43773
|
+
if (getImportedName$1(specifier) !== "PanResponder") continue;
|
|
44670
43774
|
context.report({
|
|
44671
43775
|
node: specifier,
|
|
44672
43776
|
message: "PanResponder runs gesture handling on the JS thread, which stutters under load. Use react-native-gesture-handler (`Gesture.Pan()`) so gestures run on the native UI thread."
|
|
@@ -45596,7 +44700,7 @@ const rnPreferExpoImage = defineRule({
|
|
|
45596
44700
|
for (const specifier of node.specifiers ?? []) {
|
|
45597
44701
|
if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
|
|
45598
44702
|
if (specifier.importKind === "type") continue;
|
|
45599
|
-
const importedName = getImportedName(specifier);
|
|
44703
|
+
const importedName = getImportedName$1(specifier);
|
|
45600
44704
|
if (importedName !== "Image" && importedName !== "ImageBackground") continue;
|
|
45601
44705
|
flaggedImports.push({
|
|
45602
44706
|
localName: specifier.local.name,
|
|
@@ -45658,7 +44762,7 @@ const rnPreferPressable = defineRule({
|
|
|
45658
44762
|
for (const specifier of node.specifiers ?? []) {
|
|
45659
44763
|
if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
|
|
45660
44764
|
if (specifier.importKind === "type") continue;
|
|
45661
|
-
const importedName = getImportedName(specifier);
|
|
44765
|
+
const importedName = getImportedName$1(specifier);
|
|
45662
44766
|
if (!importedName || !TOUCHABLE_COMPONENTS.has(importedName)) continue;
|
|
45663
44767
|
context.report({
|
|
45664
44768
|
node: specifier,
|
|
@@ -45760,7 +44864,7 @@ const rnPreferReanimated = defineRule({
|
|
|
45760
44864
|
for (const specifier of node.specifiers ?? []) {
|
|
45761
44865
|
if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
|
|
45762
44866
|
if (specifier.importKind === "type") continue;
|
|
45763
|
-
const importedName = getImportedName(specifier);
|
|
44867
|
+
const importedName = getImportedName$1(specifier);
|
|
45764
44868
|
if (!importedName || !JS_THREAD_ANIMATION_IMPORTS.has(importedName)) continue;
|
|
45765
44869
|
const suggestion = importedName === "LayoutAnimation" ? "Your users see stutter when LayoutAnimation runs on the JS thread." : "Your users see stutter when Animated from react-native runs on the JS thread.";
|
|
45766
44870
|
context.report({
|
|
@@ -52394,7 +51498,7 @@ const useLazyMotion = defineRule({
|
|
|
52394
51498
|
if (node.specifiers?.some((specifier) => {
|
|
52395
51499
|
if (!isNodeOfType(specifier, "ImportSpecifier")) return false;
|
|
52396
51500
|
if (specifier.importKind === "type") return false;
|
|
52397
|
-
return getImportedName(specifier) === "motion";
|
|
51501
|
+
return getImportedName$1(specifier) === "motion";
|
|
52398
51502
|
})) context.report({
|
|
52399
51503
|
node,
|
|
52400
51504
|
message: "Importing \"motion\" ships about 30 kb of extra code and slows page load. Use \"m\" with LazyMotion instead."
|
|
@@ -53421,18 +52525,6 @@ const reactDoctorRules = [
|
|
|
53421
52525
|
requires: [...new Set(["react", ...displayName.requires ?? []])]
|
|
53422
52526
|
}
|
|
53423
52527
|
},
|
|
53424
|
-
{
|
|
53425
|
-
key: "react-doctor/effect-listener-cleanup-mismatch",
|
|
53426
|
-
id: "effect-listener-cleanup-mismatch",
|
|
53427
|
-
source: "react-doctor",
|
|
53428
|
-
originallyExternal: false,
|
|
53429
|
-
rule: {
|
|
53430
|
-
...effectListenerCleanupMismatch,
|
|
53431
|
-
framework: "global",
|
|
53432
|
-
category: "Bugs",
|
|
53433
|
-
requires: [...new Set(["react", ...effectListenerCleanupMismatch.requires ?? []])]
|
|
53434
|
-
}
|
|
53435
|
-
},
|
|
53436
52528
|
{
|
|
53437
52529
|
key: "react-doctor/effect-needs-cleanup",
|
|
53438
52530
|
id: "effect-needs-cleanup",
|
|
@@ -55108,17 +54200,6 @@ const reactDoctorRules = [
|
|
|
55108
54200
|
requires: [...new Set(["react", ...noImgLazyWithHighFetchpriority.requires ?? []])]
|
|
55109
54201
|
}
|
|
55110
54202
|
},
|
|
55111
|
-
{
|
|
55112
|
-
key: "react-doctor/no-indeterminate-attribute",
|
|
55113
|
-
id: "no-indeterminate-attribute",
|
|
55114
|
-
source: "react-doctor",
|
|
55115
|
-
originallyExternal: false,
|
|
55116
|
-
rule: {
|
|
55117
|
-
...noIndeterminateAttribute,
|
|
55118
|
-
framework: "global",
|
|
55119
|
-
category: "Bugs"
|
|
55120
|
-
}
|
|
55121
|
-
},
|
|
55122
54203
|
{
|
|
55123
54204
|
key: "react-doctor/no-initialize-state",
|
|
55124
54205
|
id: "no-initialize-state",
|
|
@@ -55313,18 +54394,6 @@ const reactDoctorRules = [
|
|
|
55313
54394
|
category: "Maintainability"
|
|
55314
54395
|
}
|
|
55315
54396
|
},
|
|
55316
|
-
{
|
|
55317
|
-
key: "react-doctor/no-match-media-in-state-initializer",
|
|
55318
|
-
id: "no-match-media-in-state-initializer",
|
|
55319
|
-
source: "react-doctor",
|
|
55320
|
-
originallyExternal: false,
|
|
55321
|
-
rule: {
|
|
55322
|
-
...noMatchMediaInStateInitializer,
|
|
55323
|
-
framework: "global",
|
|
55324
|
-
category: "Bugs",
|
|
55325
|
-
requires: [...new Set(["react", ...noMatchMediaInStateInitializer.requires ?? []])]
|
|
55326
|
-
}
|
|
55327
|
-
},
|
|
55328
54397
|
{
|
|
55329
54398
|
key: "react-doctor/no-mirror-prop-effect",
|
|
55330
54399
|
id: "no-mirror-prop-effect",
|
|
@@ -58191,9 +57260,7 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
58191
57260
|
"nextjs-no-use-search-params-without-suspense",
|
|
58192
57261
|
"no-dynamic-import-path",
|
|
58193
57262
|
"no-full-lodash-import",
|
|
58194
|
-
"no-indeterminate-attribute",
|
|
58195
57263
|
"no-locale-format-in-render",
|
|
58196
|
-
"no-match-media-in-state-initializer",
|
|
58197
57264
|
"no-mutating-reducer-state",
|
|
58198
57265
|
"prefer-dynamic-import",
|
|
58199
57266
|
"rendering-hydration-mismatch-time",
|
|
@@ -58328,9 +57395,7 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
|
|
|
58328
57395
|
["nextjs-no-use-search-params-without-suspense", collectNextjsSearchParamsDependencies],
|
|
58329
57396
|
["no-dynamic-import-path", collectNearestManifestDependencies],
|
|
58330
57397
|
["no-full-lodash-import", collectNearestManifestDependencies],
|
|
58331
|
-
["no-indeterminate-attribute", collectNearestManifestDependencies],
|
|
58332
57398
|
["no-locale-format-in-render", collectNearestManifestDependencies],
|
|
58333
|
-
["no-match-media-in-state-initializer", collectNearestManifestDependencies],
|
|
58334
57399
|
["no-mutating-reducer-state", collectMutatingReducerDependencies],
|
|
58335
57400
|
["prefer-dynamic-import", collectNearestManifestDependencies],
|
|
58336
57401
|
["rendering-hydration-mismatch-time", collectNearestManifestDependencies],
|