oxlint-plugin-react-doctor 0.8.1-dev.e6a1557 → 0.8.1-dev.f4aa821
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 +1 -323
- package/dist/index.js +355 -2139
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1953,13 +1953,13 @@ const getImportDeclaration = (node) => {
|
|
|
1953
1953
|
}
|
|
1954
1954
|
return null;
|
|
1955
1955
|
};
|
|
1956
|
-
const getImportSource
|
|
1956
|
+
const getImportSource = (declaration) => typeof declaration.source.value === "string" ? declaration.source.value : null;
|
|
1957
1957
|
const isNamedImport = (symbol, importedName, moduleSources) => {
|
|
1958
1958
|
if (symbol.kind !== "import") return false;
|
|
1959
1959
|
const declaration = symbol.declarationNode;
|
|
1960
1960
|
if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
|
|
1961
1961
|
const importDeclaration = getImportDeclaration(declaration);
|
|
1962
|
-
const source = importDeclaration ? getImportSource
|
|
1962
|
+
const source = importDeclaration ? getImportSource(importDeclaration) : null;
|
|
1963
1963
|
if (!source || !moduleSources.has(source)) return false;
|
|
1964
1964
|
const imported = declaration.imported;
|
|
1965
1965
|
return isNodeOfType(imported, "Identifier") && imported.name === importedName || isNodeOfType(imported, "Literal") && imported.value === importedName;
|
|
@@ -1968,7 +1968,7 @@ const isSatoriImport = (symbol) => {
|
|
|
1968
1968
|
if (symbol.kind !== "import") return false;
|
|
1969
1969
|
const declaration = symbol.declarationNode;
|
|
1970
1970
|
const importDeclaration = getImportDeclaration(declaration);
|
|
1971
|
-
if (!importDeclaration || getImportSource
|
|
1971
|
+
if (!importDeclaration || getImportSource(importDeclaration) !== "satori") return false;
|
|
1972
1972
|
if (isNodeOfType(declaration, "ImportDefaultSpecifier")) return true;
|
|
1973
1973
|
if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
|
|
1974
1974
|
const imported = declaration.imported;
|
|
@@ -1989,7 +1989,7 @@ const isGeneratedImageRendererCall = (node, scopes) => {
|
|
|
1989
1989
|
const declaration = symbol.declarationNode;
|
|
1990
1990
|
if (!isNodeOfType(declaration, "ImportNamespaceSpecifier")) return false;
|
|
1991
1991
|
const importDeclaration = getImportDeclaration(declaration);
|
|
1992
|
-
const source = importDeclaration ? getImportSource
|
|
1992
|
+
const source = importDeclaration ? getImportSource(importDeclaration) : null;
|
|
1993
1993
|
return Boolean(source && IMAGE_RESPONSE_MODULES.has(source));
|
|
1994
1994
|
};
|
|
1995
1995
|
//#endregion
|
|
@@ -3173,7 +3173,7 @@ const getJsxPropStaticStringValues = (attribute, scopes) => getJsxPropStaticStri
|
|
|
3173
3173
|
const getJsxPropExhaustiveStaticStringValues = (attribute, scopes) => getJsxPropStaticStringValuesWithMode(attribute, scopes, null, true);
|
|
3174
3174
|
//#endregion
|
|
3175
3175
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
3176
|
-
const MESSAGE$
|
|
3176
|
+
const MESSAGE$63 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
3177
3177
|
const hasLinkRole = (roleValue) => roleValue.trim().split(/\s+/).find((roleToken) => VALID_ARIA_ROLES.has(roleToken)) === "link";
|
|
3178
3178
|
const isTransComponentsTemplate = (node) => {
|
|
3179
3179
|
let current = node.parent;
|
|
@@ -3215,7 +3215,7 @@ const anchorHasContent = defineRule({
|
|
|
3215
3215
|
if (isTransComponentsTemplate(node)) return;
|
|
3216
3216
|
context.report({
|
|
3217
3217
|
node: opening.name,
|
|
3218
|
-
message: MESSAGE$
|
|
3218
|
+
message: MESSAGE$63
|
|
3219
3219
|
});
|
|
3220
3220
|
} };
|
|
3221
3221
|
}
|
|
@@ -3703,7 +3703,7 @@ const parseJsxValue = (value) => {
|
|
|
3703
3703
|
};
|
|
3704
3704
|
//#endregion
|
|
3705
3705
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
3706
|
-
const MESSAGE$
|
|
3706
|
+
const MESSAGE$62 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
3707
3707
|
const ariaActivedescendantHasTabindex = defineRule({
|
|
3708
3708
|
id: "aria-activedescendant-has-tabindex",
|
|
3709
3709
|
title: "aria-activedescendant missing tabindex",
|
|
@@ -3721,7 +3721,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
3721
3721
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
3722
3722
|
context.report({
|
|
3723
3723
|
node: node.name,
|
|
3724
|
-
message: MESSAGE$
|
|
3724
|
+
message: MESSAGE$62
|
|
3725
3725
|
});
|
|
3726
3726
|
return;
|
|
3727
3727
|
}
|
|
@@ -3729,7 +3729,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
3729
3729
|
if (canContentEditableBeTabbable(node, context.scopes, context.settings)) return;
|
|
3730
3730
|
context.report({
|
|
3731
3731
|
node: node.name,
|
|
3732
|
-
message: MESSAGE$
|
|
3732
|
+
message: MESSAGE$62
|
|
3733
3733
|
});
|
|
3734
3734
|
} })
|
|
3735
3735
|
});
|
|
@@ -6827,7 +6827,7 @@ const stripThisParameter = (parameters) => {
|
|
|
6827
6827
|
};
|
|
6828
6828
|
//#endregion
|
|
6829
6829
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
6830
|
-
const MESSAGE$
|
|
6830
|
+
const MESSAGE$61 = "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.";
|
|
6831
6831
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
6832
6832
|
const STORAGE_GLOBALS = new Set([
|
|
6833
6833
|
"window",
|
|
@@ -6999,7 +6999,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
6999
6999
|
})) return;
|
|
7000
7000
|
context.report({
|
|
7001
7001
|
node,
|
|
7002
|
-
message: MESSAGE$
|
|
7002
|
+
message: MESSAGE$61
|
|
7003
7003
|
});
|
|
7004
7004
|
},
|
|
7005
7005
|
AssignmentExpression(node) {
|
|
@@ -7010,7 +7010,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
7010
7010
|
if (!propertyName || !isAuthCredentialKey(propertyName)) return;
|
|
7011
7011
|
context.report({
|
|
7012
7012
|
node: target,
|
|
7013
|
-
message: MESSAGE$
|
|
7013
|
+
message: MESSAGE$61
|
|
7014
7014
|
});
|
|
7015
7015
|
}
|
|
7016
7016
|
}))
|
|
@@ -7149,7 +7149,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
|
|
|
7149
7149
|
const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
|
|
7150
7150
|
const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
|
|
7151
7151
|
const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
|
|
7152
|
-
const MESSAGE$
|
|
7152
|
+
const MESSAGE$60 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
|
|
7153
7153
|
const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
|
|
7154
7154
|
const scanWorkflowContent = (content) => {
|
|
7155
7155
|
const lines = content.split("\n");
|
|
@@ -7194,7 +7194,7 @@ const scanWorkflowContent = (content) => {
|
|
|
7194
7194
|
const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
|
|
7195
7195
|
const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
|
|
7196
7196
|
return [{
|
|
7197
|
-
message: MESSAGE$
|
|
7197
|
+
message: MESSAGE$60,
|
|
7198
7198
|
line: step.startLineIndex + installLineOffset + 1,
|
|
7199
7199
|
column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
|
|
7200
7200
|
}];
|
|
@@ -7204,7 +7204,7 @@ const scanWorkflowContent = (content) => {
|
|
|
7204
7204
|
const scanNonWorkflowConfig = scanByPattern({
|
|
7205
7205
|
shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
|
|
7206
7206
|
pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
|
|
7207
|
-
message: MESSAGE$
|
|
7207
|
+
message: MESSAGE$60
|
|
7208
7208
|
});
|
|
7209
7209
|
const scan = (file) => {
|
|
7210
7210
|
if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
|
|
@@ -7954,7 +7954,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
7954
7954
|
};
|
|
7955
7955
|
//#endregion
|
|
7956
7956
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
7957
|
-
const MESSAGE$
|
|
7957
|
+
const MESSAGE$59 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
7958
7958
|
const KEY_HANDLERS = [
|
|
7959
7959
|
"onKeyUp",
|
|
7960
7960
|
"onKeyDown",
|
|
@@ -8165,7 +8165,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
8165
8165
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
|
|
8166
8166
|
context.report({
|
|
8167
8167
|
node: node.name,
|
|
8168
|
-
message: MESSAGE$
|
|
8168
|
+
message: MESSAGE$59
|
|
8169
8169
|
});
|
|
8170
8170
|
} };
|
|
8171
8171
|
}
|
|
@@ -10255,7 +10255,7 @@ const getJsxAttributeStaticString = (attribute) => {
|
|
|
10255
10255
|
};
|
|
10256
10256
|
//#endregion
|
|
10257
10257
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
10258
|
-
const MESSAGE$
|
|
10258
|
+
const MESSAGE$58 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
10259
10259
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
10260
10260
|
"td",
|
|
10261
10261
|
"th",
|
|
@@ -10785,7 +10785,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
10785
10785
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
10786
10786
|
context.report({
|
|
10787
10787
|
node: candidate.opening,
|
|
10788
|
-
message: MESSAGE$
|
|
10788
|
+
message: MESSAGE$58
|
|
10789
10789
|
});
|
|
10790
10790
|
}
|
|
10791
10791
|
}
|
|
@@ -12592,7 +12592,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
12592
12592
|
});
|
|
12593
12593
|
//#endregion
|
|
12594
12594
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
12595
|
-
const MESSAGE$
|
|
12595
|
+
const MESSAGE$57 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
12596
12596
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
12597
12597
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
12598
12598
|
"aria-label",
|
|
@@ -12617,7 +12617,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
12617
12617
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
12618
12618
|
context.report({
|
|
12619
12619
|
node: node.name,
|
|
12620
|
-
message: MESSAGE$
|
|
12620
|
+
message: MESSAGE$57
|
|
12621
12621
|
});
|
|
12622
12622
|
} };
|
|
12623
12623
|
}
|
|
@@ -12657,7 +12657,7 @@ const isEs6Component = (node) => {
|
|
|
12657
12657
|
};
|
|
12658
12658
|
//#endregion
|
|
12659
12659
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
12660
|
-
const MESSAGE$
|
|
12660
|
+
const MESSAGE$56 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
12661
12661
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
12662
12662
|
"observer",
|
|
12663
12663
|
"lazy",
|
|
@@ -12850,7 +12850,7 @@ const displayName = defineRule({
|
|
|
12850
12850
|
const reportAt = (node) => {
|
|
12851
12851
|
context.report({
|
|
12852
12852
|
node,
|
|
12853
|
-
message: MESSAGE$
|
|
12853
|
+
message: MESSAGE$56
|
|
12854
12854
|
});
|
|
12855
12855
|
};
|
|
12856
12856
|
return {
|
|
@@ -19264,7 +19264,7 @@ const forbidElements = defineRule({
|
|
|
19264
19264
|
});
|
|
19265
19265
|
//#endregion
|
|
19266
19266
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
19267
|
-
const MESSAGE$
|
|
19267
|
+
const MESSAGE$55 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
19268
19268
|
const forwardRefUsesRef = defineRule({
|
|
19269
19269
|
id: "forward-ref-uses-ref",
|
|
19270
19270
|
title: "forwardRef without ref parameter",
|
|
@@ -19287,7 +19287,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
19287
19287
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
19288
19288
|
context.report({
|
|
19289
19289
|
node: inner,
|
|
19290
|
-
message: MESSAGE$
|
|
19290
|
+
message: MESSAGE$55
|
|
19291
19291
|
});
|
|
19292
19292
|
} })
|
|
19293
19293
|
});
|
|
@@ -19328,7 +19328,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
19328
19328
|
});
|
|
19329
19329
|
//#endregion
|
|
19330
19330
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
19331
|
-
const MESSAGE$
|
|
19331
|
+
const MESSAGE$54 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
19332
19332
|
const DEFAULT_HEADING_TAGS = [
|
|
19333
19333
|
"h1",
|
|
19334
19334
|
"h2",
|
|
@@ -19362,7 +19362,7 @@ const headingHasContent = defineRule({
|
|
|
19362
19362
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
19363
19363
|
context.report({
|
|
19364
19364
|
node,
|
|
19365
|
-
message: MESSAGE$
|
|
19365
|
+
message: MESSAGE$54
|
|
19366
19366
|
});
|
|
19367
19367
|
} };
|
|
19368
19368
|
}
|
|
@@ -19530,7 +19530,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19530
19530
|
});
|
|
19531
19531
|
//#endregion
|
|
19532
19532
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
19533
|
-
const MESSAGE$
|
|
19533
|
+
const MESSAGE$53 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
19534
19534
|
const resolveSettings$38 = (settings) => {
|
|
19535
19535
|
const reactDoctor = settings?.["react-doctor"];
|
|
19536
19536
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -19577,13 +19577,13 @@ const htmlHasLang = defineRule({
|
|
|
19577
19577
|
if (!lang) {
|
|
19578
19578
|
context.report({
|
|
19579
19579
|
node: node.name,
|
|
19580
|
-
message: MESSAGE$
|
|
19580
|
+
message: MESSAGE$53
|
|
19581
19581
|
});
|
|
19582
19582
|
return;
|
|
19583
19583
|
}
|
|
19584
19584
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
19585
19585
|
node: lang,
|
|
19586
|
-
message: MESSAGE$
|
|
19586
|
+
message: MESSAGE$53
|
|
19587
19587
|
});
|
|
19588
19588
|
} };
|
|
19589
19589
|
}
|
|
@@ -19856,7 +19856,7 @@ const isJsxFragmentElement = (node, scopes) => {
|
|
|
19856
19856
|
};
|
|
19857
19857
|
//#endregion
|
|
19858
19858
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
19859
|
-
const MESSAGE$
|
|
19859
|
+
const MESSAGE$52 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
19860
19860
|
const isStaticallyAriaHidden = (openingElement) => {
|
|
19861
19861
|
const ariaHiddenAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "aria-hidden", false);
|
|
19862
19862
|
if (!ariaHiddenAttribute) return false;
|
|
@@ -19945,14 +19945,14 @@ const iframeHasTitle = defineRule({
|
|
|
19945
19945
|
if (!titleAttr) {
|
|
19946
19946
|
if (hasSpread || tag === "iframe") context.report({
|
|
19947
19947
|
node: node.name,
|
|
19948
|
-
message: MESSAGE$
|
|
19948
|
+
message: MESSAGE$52
|
|
19949
19949
|
});
|
|
19950
19950
|
return;
|
|
19951
19951
|
}
|
|
19952
19952
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
19953
19953
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
19954
19954
|
node: titleAttr,
|
|
19955
|
-
message: MESSAGE$
|
|
19955
|
+
message: MESSAGE$52
|
|
19956
19956
|
});
|
|
19957
19957
|
} })
|
|
19958
19958
|
});
|
|
@@ -20077,7 +20077,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
20077
20077
|
});
|
|
20078
20078
|
//#endregion
|
|
20079
20079
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
20080
|
-
const MESSAGE$
|
|
20080
|
+
const MESSAGE$51 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
20081
20081
|
const DEFAULT_COMPONENTS = ["img"];
|
|
20082
20082
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
20083
20083
|
"image",
|
|
@@ -20142,7 +20142,7 @@ const imgRedundantAlt = defineRule({
|
|
|
20142
20142
|
if (!altAttribute) return;
|
|
20143
20143
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
20144
20144
|
node: altAttribute,
|
|
20145
|
-
message: MESSAGE$
|
|
20145
|
+
message: MESSAGE$51
|
|
20146
20146
|
});
|
|
20147
20147
|
} };
|
|
20148
20148
|
}
|
|
@@ -21795,7 +21795,7 @@ const isDiscardedProbeInsideTry = (node) => {
|
|
|
21795
21795
|
return false;
|
|
21796
21796
|
};
|
|
21797
21797
|
const isComponentOrHookName = (name) => /^[A-Z]/.test(name) || /^use[A-Z0-9]/.test(name);
|
|
21798
|
-
const getFunctionName
|
|
21798
|
+
const getFunctionName = (functionNode) => {
|
|
21799
21799
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id.name;
|
|
21800
21800
|
const holder = functionNode.parent;
|
|
21801
21801
|
if (holder && isNodeOfType(holder, "VariableDeclarator") && isNodeOfType(holder.id, "Identifier")) return holder.id.name;
|
|
@@ -21804,7 +21804,7 @@ const getFunctionName$1 = (functionNode) => {
|
|
|
21804
21804
|
const isUncacheableOptionsMergeUtility = (node) => {
|
|
21805
21805
|
const enclosingFunction = findEnclosingFunction$1(node);
|
|
21806
21806
|
if (!enclosingFunction || !isFunctionLike$1(enclosingFunction)) return false;
|
|
21807
|
-
const functionName = getFunctionName
|
|
21807
|
+
const functionName = getFunctionName(enclosingFunction);
|
|
21808
21808
|
if (!functionName || isComponentOrHookName(functionName)) return false;
|
|
21809
21809
|
const parameterNames = /* @__PURE__ */ new Set();
|
|
21810
21810
|
for (const parameter of enclosingFunction.params ?? []) collectPatternNames(parameter, parameterNames);
|
|
@@ -22106,7 +22106,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
22106
22106
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
22107
22107
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
22108
22108
|
};
|
|
22109
|
-
const MESSAGE$
|
|
22109
|
+
const MESSAGE$50 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
22110
22110
|
const jsHoistRegexp = defineRule({
|
|
22111
22111
|
id: "js-hoist-regexp",
|
|
22112
22112
|
title: "RegExp built inside a loop",
|
|
@@ -22123,7 +22123,7 @@ const jsHoistRegexp = defineRule({
|
|
|
22123
22123
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
22124
22124
|
context.report({
|
|
22125
22125
|
node,
|
|
22126
|
-
message: MESSAGE$
|
|
22126
|
+
message: MESSAGE$50
|
|
22127
22127
|
});
|
|
22128
22128
|
};
|
|
22129
22129
|
return createLoopAwareVisitors({
|
|
@@ -24890,7 +24890,7 @@ const jsxMaxDepth = defineRule({
|
|
|
24890
24890
|
});
|
|
24891
24891
|
//#endregion
|
|
24892
24892
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
24893
|
-
const MESSAGE$
|
|
24893
|
+
const MESSAGE$49 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
24894
24894
|
const LITERAL_TEXT_TAGS = new Set([
|
|
24895
24895
|
"code",
|
|
24896
24896
|
"pre",
|
|
@@ -24960,7 +24960,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
24960
24960
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
24961
24961
|
context.report({
|
|
24962
24962
|
node,
|
|
24963
|
-
message: MESSAGE$
|
|
24963
|
+
message: MESSAGE$49
|
|
24964
24964
|
});
|
|
24965
24965
|
} })
|
|
24966
24966
|
});
|
|
@@ -24991,7 +24991,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
24991
24991
|
};
|
|
24992
24992
|
//#endregion
|
|
24993
24993
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
24994
|
-
const MESSAGE$
|
|
24994
|
+
const MESSAGE$48 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
24995
24995
|
const CONTEXT_MODULES$1 = [
|
|
24996
24996
|
"react",
|
|
24997
24997
|
"use-context-selector",
|
|
@@ -25094,7 +25094,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
25094
25094
|
if (!isConstructedValue(innerExpression)) continue;
|
|
25095
25095
|
context.report({
|
|
25096
25096
|
node: attribute,
|
|
25097
|
-
message: MESSAGE$
|
|
25097
|
+
message: MESSAGE$48
|
|
25098
25098
|
});
|
|
25099
25099
|
}
|
|
25100
25100
|
}
|
|
@@ -25960,7 +25960,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
25960
25960
|
];
|
|
25961
25961
|
//#endregion
|
|
25962
25962
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
25963
|
-
const MESSAGE$
|
|
25963
|
+
const MESSAGE$47 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
25964
25964
|
const isDataArrayPropName = (propName) => {
|
|
25965
25965
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
25966
25966
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -26047,7 +26047,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
26047
26047
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
26048
26048
|
context.report({
|
|
26049
26049
|
node,
|
|
26050
|
-
message: MESSAGE$
|
|
26050
|
+
message: MESSAGE$47
|
|
26051
26051
|
});
|
|
26052
26052
|
}
|
|
26053
26053
|
};
|
|
@@ -26305,7 +26305,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
26305
26305
|
]);
|
|
26306
26306
|
//#endregion
|
|
26307
26307
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
26308
|
-
const MESSAGE$
|
|
26308
|
+
const MESSAGE$46 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
26309
26309
|
const isAccessorPredicateName = (propName) => {
|
|
26310
26310
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
26311
26311
|
if (propName.length <= prefix.length) continue;
|
|
@@ -26512,7 +26512,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
26512
26512
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
26513
26513
|
context.report({
|
|
26514
26514
|
node,
|
|
26515
|
-
message: MESSAGE$
|
|
26515
|
+
message: MESSAGE$46
|
|
26516
26516
|
});
|
|
26517
26517
|
}
|
|
26518
26518
|
};
|
|
@@ -26732,7 +26732,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
26732
26732
|
];
|
|
26733
26733
|
//#endregion
|
|
26734
26734
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
26735
|
-
const MESSAGE$
|
|
26735
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
26736
26736
|
const isConfigObjectPropName = (propName) => {
|
|
26737
26737
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
26738
26738
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -26821,7 +26821,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26821
26821
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
26822
26822
|
context.report({
|
|
26823
26823
|
node,
|
|
26824
|
-
message: MESSAGE$
|
|
26824
|
+
message: MESSAGE$45
|
|
26825
26825
|
});
|
|
26826
26826
|
}
|
|
26827
26827
|
};
|
|
@@ -26829,7 +26829,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26829
26829
|
});
|
|
26830
26830
|
//#endregion
|
|
26831
26831
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
26832
|
-
const MESSAGE$
|
|
26832
|
+
const MESSAGE$44 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
26833
26833
|
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;
|
|
26834
26834
|
const resolveSettings$28 = (settings) => {
|
|
26835
26835
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -26867,7 +26867,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26867
26867
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
26868
26868
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
26869
26869
|
node: attribute,
|
|
26870
|
-
message: MESSAGE$
|
|
26870
|
+
message: MESSAGE$44
|
|
26871
26871
|
});
|
|
26872
26872
|
}
|
|
26873
26873
|
} };
|
|
@@ -27210,7 +27210,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
27210
27210
|
});
|
|
27211
27211
|
//#endregion
|
|
27212
27212
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
27213
|
-
const MESSAGE$
|
|
27213
|
+
const MESSAGE$43 = "You can't tell what props reach this element when you spread them.";
|
|
27214
27214
|
const resolveSettings$25 = (settings) => {
|
|
27215
27215
|
const reactDoctor = settings?.["react-doctor"];
|
|
27216
27216
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -27253,7 +27253,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
27253
27253
|
}
|
|
27254
27254
|
context.report({
|
|
27255
27255
|
node: attribute,
|
|
27256
|
-
message: MESSAGE$
|
|
27256
|
+
message: MESSAGE$43
|
|
27257
27257
|
});
|
|
27258
27258
|
didReportInFile = true;
|
|
27259
27259
|
return;
|
|
@@ -27529,7 +27529,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
27529
27529
|
});
|
|
27530
27530
|
//#endregion
|
|
27531
27531
|
//#region src/plugin/rules/a11y/lang.ts
|
|
27532
|
-
const MESSAGE$
|
|
27532
|
+
const MESSAGE$42 = "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`.";
|
|
27533
27533
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
27534
27534
|
"aa",
|
|
27535
27535
|
"ab",
|
|
@@ -28009,7 +28009,7 @@ const lang = defineRule({
|
|
|
28009
28009
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
28010
28010
|
context.report({
|
|
28011
28011
|
node: langAttr,
|
|
28012
|
-
message: MESSAGE$
|
|
28012
|
+
message: MESSAGE$42
|
|
28013
28013
|
});
|
|
28014
28014
|
return;
|
|
28015
28015
|
}
|
|
@@ -28018,7 +28018,7 @@ const lang = defineRule({
|
|
|
28018
28018
|
if (value === null) return;
|
|
28019
28019
|
if (!isValidLangTag(value)) context.report({
|
|
28020
28020
|
node: langAttr,
|
|
28021
|
-
message: MESSAGE$
|
|
28021
|
+
message: MESSAGE$42
|
|
28022
28022
|
});
|
|
28023
28023
|
} })
|
|
28024
28024
|
});
|
|
@@ -28069,7 +28069,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
28069
28069
|
});
|
|
28070
28070
|
//#endregion
|
|
28071
28071
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
28072
|
-
const MESSAGE$
|
|
28072
|
+
const MESSAGE$41 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
28073
28073
|
const DEFAULT_AUDIO = ["audio"];
|
|
28074
28074
|
const DEFAULT_VIDEO = ["video"];
|
|
28075
28075
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -28159,7 +28159,7 @@ const mediaHasCaption = defineRule({
|
|
|
28159
28159
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
28160
28160
|
context.report({
|
|
28161
28161
|
node: node.name,
|
|
28162
|
-
message: MESSAGE$
|
|
28162
|
+
message: MESSAGE$41
|
|
28163
28163
|
});
|
|
28164
28164
|
return;
|
|
28165
28165
|
}
|
|
@@ -28178,553 +28178,12 @@ const mediaHasCaption = defineRule({
|
|
|
28178
28178
|
return kindValue.value.toLowerCase() === "captions";
|
|
28179
28179
|
})) context.report({
|
|
28180
28180
|
node: node.name,
|
|
28181
|
-
message: MESSAGE$
|
|
28181
|
+
message: MESSAGE$41
|
|
28182
28182
|
});
|
|
28183
28183
|
} };
|
|
28184
28184
|
}
|
|
28185
28185
|
});
|
|
28186
28186
|
//#endregion
|
|
28187
|
-
//#region src/plugin/utils/get-class-binding-symbol.ts
|
|
28188
|
-
const getClassBindingSymbol = (classNode, scopes) => {
|
|
28189
|
-
if (isNodeOfType(classNode.id, "Identifier")) return scopes.symbolFor(classNode.id);
|
|
28190
|
-
const parent = classNode.parent;
|
|
28191
|
-
return isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier") ? scopes.symbolFor(parent.id) : null;
|
|
28192
|
-
};
|
|
28193
|
-
//#endregion
|
|
28194
|
-
//#region src/plugin/utils/mobx-rule-gates.ts
|
|
28195
|
-
const MOBX_RULE_GATES = {
|
|
28196
|
-
"mobx-reaction-disposer-discarded": { requires: ["mobx:4"] },
|
|
28197
|
-
"mobx-no-make-auto-observable-in-inheritance": { requires: ["mobx:6"] },
|
|
28198
|
-
"mobx-no-computed-side-effects": { requires: ["mobx:4"] },
|
|
28199
|
-
"mobx-async-action-requires-action": { requires: ["mobx:4"] },
|
|
28200
|
-
"mobx-no-observer-wrapped-memo": { requires: [
|
|
28201
|
-
"mobx:4",
|
|
28202
|
-
"mobx-react-binding",
|
|
28203
|
-
"react"
|
|
28204
|
-
] },
|
|
28205
|
-
"mobx-make-observable-unconditional": { requires: ["mobx:6"] },
|
|
28206
|
-
"mobx-legacy-decorator-needs-make-observable": { requires: ["mobx:6"] },
|
|
28207
|
-
"mobx-initialize-before-make-auto-observable": { requires: ["mobx:6"] },
|
|
28208
|
-
"mobx-observable-read-needs-observer": {
|
|
28209
|
-
requires: [
|
|
28210
|
-
"mobx:4",
|
|
28211
|
-
"mobx-react-binding",
|
|
28212
|
-
"react"
|
|
28213
|
-
],
|
|
28214
|
-
disabledWhen: ["mobx-react-observer"]
|
|
28215
|
-
},
|
|
28216
|
-
"mobx-observer-before-inject": { requires: [
|
|
28217
|
-
"mobx:4",
|
|
28218
|
-
"mobx-react",
|
|
28219
|
-
"react"
|
|
28220
|
-
] },
|
|
28221
|
-
"mobx-reaction-requires-observable": { requires: ["mobx:4"] },
|
|
28222
|
-
"mobx-no-invalid-observable-override": { requires: ["mobx:6"] },
|
|
28223
|
-
"mobx-no-observable-prop-to-untracked-child": { requires: [
|
|
28224
|
-
"mobx:4",
|
|
28225
|
-
"mobx-react-binding",
|
|
28226
|
-
"react"
|
|
28227
|
-
] },
|
|
28228
|
-
"mobx-no-stale-observable-snapshot-after-await": { requires: ["mobx:4"] },
|
|
28229
|
-
"mobx-no-reaction-comparison-value-mutation": { requires: ["mobx:4"] },
|
|
28230
|
-
"mobx-observer-class-no-should-component-update": { requires: [
|
|
28231
|
-
"mobx:4",
|
|
28232
|
-
"mobx-react",
|
|
28233
|
-
"react"
|
|
28234
|
-
] },
|
|
28235
|
-
"mobx-enable-static-rendering-for-ssr": { requires: [
|
|
28236
|
-
"mobx:4",
|
|
28237
|
-
"mobx-react-binding",
|
|
28238
|
-
"react",
|
|
28239
|
-
"ssr"
|
|
28240
|
-
] },
|
|
28241
|
-
"mobx-no-rest-destructure-observable": { requires: [
|
|
28242
|
-
"mobx:4",
|
|
28243
|
-
"mobx-react-binding",
|
|
28244
|
-
"react"
|
|
28245
|
-
] },
|
|
28246
|
-
"mobx-computed-depends-on-non-observable": { requires: ["mobx:4"] },
|
|
28247
|
-
"mobx-no-keepalive-computed-without-disposal": { requires: ["mobx:4"] }
|
|
28248
|
-
};
|
|
28249
|
-
//#endregion
|
|
28250
|
-
//#region src/plugin/utils/resolve-imported-api-reference.ts
|
|
28251
|
-
const resolveImportSymbol = (symbol) => {
|
|
28252
|
-
const importDeclaration = getImportDeclarationForSymbol(symbol);
|
|
28253
|
-
if (!importDeclaration || typeof importDeclaration.source.value !== "string") return null;
|
|
28254
|
-
if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return {
|
|
28255
|
-
source: importDeclaration.source.value,
|
|
28256
|
-
importedName: null,
|
|
28257
|
-
isNamespace: true
|
|
28258
|
-
};
|
|
28259
|
-
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return {
|
|
28260
|
-
source: importDeclaration.source.value,
|
|
28261
|
-
importedName: "default",
|
|
28262
|
-
isNamespace: false
|
|
28263
|
-
};
|
|
28264
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
28265
|
-
return importedName ? {
|
|
28266
|
-
source: importDeclaration.source.value,
|
|
28267
|
-
importedName,
|
|
28268
|
-
isNamespace: false
|
|
28269
|
-
} : null;
|
|
28270
|
-
};
|
|
28271
|
-
const resolveIdentifierReference = (identifier, scopes, visitedSymbolIds) => {
|
|
28272
|
-
const symbol = scopes.symbolFor(identifier);
|
|
28273
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
28274
|
-
if (symbol.kind === "import") return resolveImportSymbol(symbol);
|
|
28275
|
-
if (symbol.kind !== "const" || !symbol.initializer) return null;
|
|
28276
|
-
visitedSymbolIds.add(symbol.id);
|
|
28277
|
-
if (isNodeOfType(symbol.declarationNode, "VariableDeclarator") && isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) {
|
|
28278
|
-
const receiver = resolveImportedApiReference(symbol.initializer, scopes, visitedSymbolIds);
|
|
28279
|
-
if (!receiver || !receiver.isNamespace && receiver.importedName !== "default") return null;
|
|
28280
|
-
for (const property of symbol.declarationNode.id.properties) {
|
|
28281
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
28282
|
-
if ((isNodeOfType(property.value, "AssignmentPattern") ? property.value.left : property.value) !== symbol.bindingIdentifier) continue;
|
|
28283
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
28284
|
-
return propertyName ? {
|
|
28285
|
-
source: receiver.source,
|
|
28286
|
-
importedName: propertyName,
|
|
28287
|
-
isNamespace: false
|
|
28288
|
-
} : null;
|
|
28289
|
-
}
|
|
28290
|
-
return null;
|
|
28291
|
-
}
|
|
28292
|
-
return resolveImportedApiReference(symbol.initializer, scopes, visitedSymbolIds);
|
|
28293
|
-
};
|
|
28294
|
-
const resolveImportedApiReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
28295
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
28296
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) return resolveIdentifierReference(unwrappedExpression, scopes, visitedSymbolIds);
|
|
28297
|
-
if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
|
|
28298
|
-
const propertyName = getStaticPropertyName(unwrappedExpression);
|
|
28299
|
-
if (!propertyName) return null;
|
|
28300
|
-
const receiver = resolveImportedApiReference(unwrappedExpression.object, scopes, visitedSymbolIds);
|
|
28301
|
-
if (!receiver || !receiver.isNamespace && receiver.importedName !== "default") return null;
|
|
28302
|
-
return {
|
|
28303
|
-
source: receiver.source,
|
|
28304
|
-
importedName: propertyName,
|
|
28305
|
-
isNamespace: false
|
|
28306
|
-
};
|
|
28307
|
-
};
|
|
28308
|
-
//#endregion
|
|
28309
|
-
//#region src/plugin/rules/mobx/mobx-no-make-auto-observable-in-inheritance.ts
|
|
28310
|
-
const MESSAGE$44 = "MobX does not support `makeAutoObservable(this)` in inherited classes. Use composition or explicit `makeObservable` annotations.";
|
|
28311
|
-
const getEnclosingConstructorClass = (node) => {
|
|
28312
|
-
let ancestor = node.parent;
|
|
28313
|
-
while (ancestor) {
|
|
28314
|
-
if (!isFunctionLike$1(ancestor)) {
|
|
28315
|
-
ancestor = ancestor.parent ?? null;
|
|
28316
|
-
continue;
|
|
28317
|
-
}
|
|
28318
|
-
const methodDefinition = ancestor.parent;
|
|
28319
|
-
if (!isNodeOfType(methodDefinition, "MethodDefinition") || methodDefinition.kind !== "constructor") return null;
|
|
28320
|
-
const classNode = methodDefinition.parent?.parent;
|
|
28321
|
-
return isNodeOfType(classNode, "ClassDeclaration") || isNodeOfType(classNode, "ClassExpression") ? classNode : null;
|
|
28322
|
-
}
|
|
28323
|
-
return null;
|
|
28324
|
-
};
|
|
28325
|
-
const isNonNullSuperclass = (classNode) => {
|
|
28326
|
-
if (!classNode.superClass) return false;
|
|
28327
|
-
const superClass = stripParenExpression(classNode.superClass);
|
|
28328
|
-
return !(isNodeOfType(superClass, "Literal") && superClass.value === null);
|
|
28329
|
-
};
|
|
28330
|
-
const subclassedClassSymbolIdsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
28331
|
-
const getSubclassedClassSymbolIds = (node, scopes) => {
|
|
28332
|
-
const cached = subclassedClassSymbolIdsByAnalysis.get(scopes);
|
|
28333
|
-
if (cached) return cached;
|
|
28334
|
-
const symbolIds = /* @__PURE__ */ new Set();
|
|
28335
|
-
const program = findProgramRoot(node);
|
|
28336
|
-
if (program) walkAst(program, (candidate) => {
|
|
28337
|
-
if (!isNodeOfType(candidate, "ClassDeclaration") && !isNodeOfType(candidate, "ClassExpression") || !isNonNullSuperclass(candidate)) return;
|
|
28338
|
-
const superClassExpression = candidate.superClass;
|
|
28339
|
-
if (!superClassExpression) return;
|
|
28340
|
-
const superClass = stripParenExpression(superClassExpression);
|
|
28341
|
-
if (!isNodeOfType(superClass, "Identifier")) return;
|
|
28342
|
-
const symbol = resolveConstIdentifierAlias(superClass, scopes);
|
|
28343
|
-
if (symbol?.kind === "class" || symbol?.kind === "const" && isNodeOfType(symbol.initializer, "ClassExpression")) symbolIds.add(symbol.id);
|
|
28344
|
-
});
|
|
28345
|
-
subclassedClassSymbolIdsByAnalysis.set(scopes, symbolIds);
|
|
28346
|
-
return symbolIds;
|
|
28347
|
-
};
|
|
28348
|
-
const isMakeAutoObservableCall = (callExpression, scopes) => {
|
|
28349
|
-
const reference = resolveImportedApiReference(callExpression.callee, scopes);
|
|
28350
|
-
return reference?.source === "mobx" && reference.importedName === "makeAutoObservable";
|
|
28351
|
-
};
|
|
28352
|
-
const mobxNoMakeAutoObservableInInheritance = defineRule({
|
|
28353
|
-
id: "mobx-no-make-auto-observable-in-inheritance",
|
|
28354
|
-
title: "Unsupported MobX auto-observable inheritance",
|
|
28355
|
-
severity: "error",
|
|
28356
|
-
category: "Bugs",
|
|
28357
|
-
requires: MOBX_RULE_GATES["mobx-no-make-auto-observable-in-inheritance"].requires,
|
|
28358
|
-
recommendation: "Replace inheritance with composition, or annotate inherited members explicitly with `makeObservable`.",
|
|
28359
|
-
create: (context) => ({ CallExpression(callExpression) {
|
|
28360
|
-
if (!isMakeAutoObservableCall(callExpression, context.scopes)) return;
|
|
28361
|
-
const target = callExpression.arguments[0];
|
|
28362
|
-
if (!target || !isNodeOfType(stripParenExpression(target), "ThisExpression")) return;
|
|
28363
|
-
const classNode = getEnclosingConstructorClass(callExpression);
|
|
28364
|
-
if (!classNode) return;
|
|
28365
|
-
const classSymbol = getClassBindingSymbol(classNode, context.scopes);
|
|
28366
|
-
const isSubclassed = Boolean(classSymbol && getSubclassedClassSymbolIds(callExpression, context.scopes).has(classSymbol.id));
|
|
28367
|
-
if (!isNonNullSuperclass(classNode) && !isSubclassed) return;
|
|
28368
|
-
context.report({
|
|
28369
|
-
node: callExpression,
|
|
28370
|
-
message: MESSAGE$44
|
|
28371
|
-
});
|
|
28372
|
-
} })
|
|
28373
|
-
});
|
|
28374
|
-
//#endregion
|
|
28375
|
-
//#region src/plugin/rules/mobx/mobx-no-observer-wrapped-memo.ts
|
|
28376
|
-
const OBSERVER_MODULES = new Set(["mobx-react", "mobx-react-lite"]);
|
|
28377
|
-
const MESSAGE$43 = "`observer` cannot wrap an already memoized or observed component. Apply `observer` first, then place `memo` outside only if needed.";
|
|
28378
|
-
const isObserverCall = (callExpression, scopes) => {
|
|
28379
|
-
const reference = resolveImportedApiReference(callExpression.callee, scopes);
|
|
28380
|
-
return Boolean(reference?.importedName === "observer" && OBSERVER_MODULES.has(reference.source));
|
|
28381
|
-
};
|
|
28382
|
-
const hasInvalidInnerWrapper = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
28383
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
28384
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
28385
|
-
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
28386
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
28387
|
-
visitedSymbolIds.add(symbol.id);
|
|
28388
|
-
return hasInvalidInnerWrapper(symbol.initializer, scopes, visitedSymbolIds);
|
|
28389
|
-
}
|
|
28390
|
-
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
|
|
28391
|
-
const reference = resolveImportedApiReference(unwrappedExpression.callee, scopes);
|
|
28392
|
-
if (reference?.importedName === "observer" && OBSERVER_MODULES.has(reference.source)) return true;
|
|
28393
|
-
return reference?.source === "react" && reference.importedName === "memo";
|
|
28394
|
-
};
|
|
28395
|
-
const mobxNoObserverWrappedMemo = defineRule({
|
|
28396
|
-
id: "mobx-no-observer-wrapped-memo",
|
|
28397
|
-
title: "Invalid MobX observer wrapper order",
|
|
28398
|
-
severity: "error",
|
|
28399
|
-
category: "Bugs",
|
|
28400
|
-
requires: MOBX_RULE_GATES["mobx-no-observer-wrapped-memo"].requires,
|
|
28401
|
-
recommendation: "Pass the component directly to `observer`, or apply React `memo` outside the resulting observer component.",
|
|
28402
|
-
create: (context) => ({ CallExpression(callExpression) {
|
|
28403
|
-
if (!isObserverCall(callExpression, context.scopes)) return;
|
|
28404
|
-
const componentArgument = callExpression.arguments[0];
|
|
28405
|
-
if (!componentArgument) return;
|
|
28406
|
-
if (!hasInvalidInnerWrapper(componentArgument, context.scopes)) return;
|
|
28407
|
-
context.report({
|
|
28408
|
-
node: callExpression,
|
|
28409
|
-
message: MESSAGE$43
|
|
28410
|
-
});
|
|
28411
|
-
} })
|
|
28412
|
-
});
|
|
28413
|
-
//#endregion
|
|
28414
|
-
//#region src/plugin/utils/is-result-discarded-call.ts
|
|
28415
|
-
const isResultDiscardedCall = (callExpression) => {
|
|
28416
|
-
let node = callExpression;
|
|
28417
|
-
let parent = node.parent;
|
|
28418
|
-
while (parent) {
|
|
28419
|
-
if (isNodeOfType(parent, "ExpressionStatement")) return true;
|
|
28420
|
-
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
|
|
28421
|
-
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
|
|
28422
|
-
if (isNodeOfType(parent, "ChainExpression")) {
|
|
28423
|
-
node = parent;
|
|
28424
|
-
parent = node.parent;
|
|
28425
|
-
continue;
|
|
28426
|
-
}
|
|
28427
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
|
|
28428
|
-
node = parent;
|
|
28429
|
-
parent = node.parent;
|
|
28430
|
-
continue;
|
|
28431
|
-
}
|
|
28432
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
|
|
28433
|
-
node = parent;
|
|
28434
|
-
parent = node.parent;
|
|
28435
|
-
continue;
|
|
28436
|
-
}
|
|
28437
|
-
if (isNodeOfType(parent, "SequenceExpression")) {
|
|
28438
|
-
const expressions = parent.expressions ?? [];
|
|
28439
|
-
if (expressions[expressions.length - 1] !== node) return true;
|
|
28440
|
-
node = parent;
|
|
28441
|
-
parent = node.parent;
|
|
28442
|
-
continue;
|
|
28443
|
-
}
|
|
28444
|
-
return false;
|
|
28445
|
-
}
|
|
28446
|
-
return false;
|
|
28447
|
-
};
|
|
28448
|
-
//#endregion
|
|
28449
|
-
//#region src/plugin/utils/resolve-stable-options-object.ts
|
|
28450
|
-
const resolveStableOptionsObject = (expression, observedPropertyNames, scopes, referenceNode = expression, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
28451
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
28452
|
-
if (isNodeOfType(unwrappedExpression, "ObjectExpression")) return unwrappedExpression;
|
|
28453
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
28454
|
-
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
28455
|
-
if (!symbol?.initializer || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, referenceNode, scopes) || observedPropertyNames.some((propertyName) => hasPossibleStaticPropertyWriteBefore(unwrappedExpression, propertyName, referenceNode, scopes))) return null;
|
|
28456
|
-
visitedSymbolIds.add(symbol.id);
|
|
28457
|
-
return resolveStableOptionsObject(symbol.initializer, observedPropertyNames, scopes, referenceNode, visitedSymbolIds);
|
|
28458
|
-
};
|
|
28459
|
-
//#endregion
|
|
28460
|
-
//#region src/plugin/rules/mobx/mobx-reaction-disposer-discarded.ts
|
|
28461
|
-
const MESSAGE$42 = "This MobX reaction discards its disposer and can outlive its owner. Store and dispose it during teardown, or provide an AbortSignal.";
|
|
28462
|
-
const LEAKING_SUBSCRIPTION_NAMES = new Set(["reaction", "autorun"]);
|
|
28463
|
-
const OPTIONS_ARGUMENT_INDEX = {
|
|
28464
|
-
autorun: 1,
|
|
28465
|
-
reaction: 2
|
|
28466
|
-
};
|
|
28467
|
-
const DISPOSER_COERCION_NAMES = new Set([
|
|
28468
|
-
"Boolean",
|
|
28469
|
-
"Number",
|
|
28470
|
-
"String"
|
|
28471
|
-
]);
|
|
28472
|
-
const NON_OBSERVABLE_GLOBAL_RECEIVER_NAMES = new Set([
|
|
28473
|
-
"Array",
|
|
28474
|
-
"Boolean",
|
|
28475
|
-
"JSON",
|
|
28476
|
-
"Math",
|
|
28477
|
-
"Number",
|
|
28478
|
-
"Object",
|
|
28479
|
-
"String",
|
|
28480
|
-
"console"
|
|
28481
|
-
]);
|
|
28482
|
-
const NON_OBSERVING_IMPORTED_METHOD_NAMES = new Set([
|
|
28483
|
-
"add",
|
|
28484
|
-
"clear",
|
|
28485
|
-
"delete",
|
|
28486
|
-
"remove",
|
|
28487
|
-
"save",
|
|
28488
|
-
"set",
|
|
28489
|
-
"update",
|
|
28490
|
-
"write"
|
|
28491
|
-
]);
|
|
28492
|
-
const REACTION_PARAMETER_INDEX = {
|
|
28493
|
-
autorun: 0,
|
|
28494
|
-
reaction: 2
|
|
28495
|
-
};
|
|
28496
|
-
const REACTION_CALLBACK_INDEX = {
|
|
28497
|
-
autorun: 0,
|
|
28498
|
-
reaction: 1
|
|
28499
|
-
};
|
|
28500
|
-
const OBSERVATION_CALLBACK_INDEX = {
|
|
28501
|
-
autorun: 0,
|
|
28502
|
-
reaction: 0
|
|
28503
|
-
};
|
|
28504
|
-
const PROCESS_LIFETIME_WIRING_NAME_PATTERN = /^(?:register.*(?:reactions?|autoruns?)|init.*(?:stores?|reactions?|autoruns?)|setup.*(?:stores?|reactions?|autoruns?)|bootstrap(?:app(?:lication)?|stores?|reactions?|autoruns?))$/i;
|
|
28505
|
-
const resolveLeakingSubscriptionName = (callExpression, scopes) => {
|
|
28506
|
-
const reference = resolveImportedApiReference(callExpression.callee, scopes);
|
|
28507
|
-
if (reference?.source !== "mobx" || !reference.importedName || !LEAKING_SUBSCRIPTION_NAMES.has(reference.importedName)) return null;
|
|
28508
|
-
return reference.importedName;
|
|
28509
|
-
};
|
|
28510
|
-
const isEvaluatedAtModuleScope = (node) => {
|
|
28511
|
-
let ancestor = node.parent;
|
|
28512
|
-
while (ancestor) {
|
|
28513
|
-
if (isNodeOfType(ancestor, "PropertyDefinition") || isNodeOfType(ancestor, "AccessorProperty")) {
|
|
28514
|
-
if (!ancestor.static) return false;
|
|
28515
|
-
ancestor = ancestor.parent ?? null;
|
|
28516
|
-
continue;
|
|
28517
|
-
}
|
|
28518
|
-
if (isNodeOfType(ancestor, "StaticBlock")) {
|
|
28519
|
-
ancestor = ancestor.parent ?? null;
|
|
28520
|
-
continue;
|
|
28521
|
-
}
|
|
28522
|
-
if (isFunctionLike$1(ancestor)) {
|
|
28523
|
-
const functionRoot = findTransparentExpressionRoot(ancestor);
|
|
28524
|
-
const invocation = functionRoot.parent;
|
|
28525
|
-
if (isNodeOfType(invocation, "CallExpression") && stripParenExpression(invocation.callee) === functionRoot) {
|
|
28526
|
-
ancestor = invocation.parent ?? null;
|
|
28527
|
-
continue;
|
|
28528
|
-
}
|
|
28529
|
-
return false;
|
|
28530
|
-
}
|
|
28531
|
-
ancestor = ancestor.parent ?? null;
|
|
28532
|
-
}
|
|
28533
|
-
return true;
|
|
28534
|
-
};
|
|
28535
|
-
const getFunctionName = (functionNode) => {
|
|
28536
|
-
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id.name;
|
|
28537
|
-
const parent = functionNode.parent;
|
|
28538
|
-
return isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier") ? parent.id.name : null;
|
|
28539
|
-
};
|
|
28540
|
-
const isModuleScopedFunction = (functionNode) => {
|
|
28541
|
-
let ancestor = functionNode.parent;
|
|
28542
|
-
while (ancestor) {
|
|
28543
|
-
if (isFunctionLike$1(ancestor) || isNodeOfType(ancestor, "ClassBody")) return false;
|
|
28544
|
-
if (isNodeOfType(ancestor, "Program")) return true;
|
|
28545
|
-
ancestor = ancestor.parent ?? null;
|
|
28546
|
-
}
|
|
28547
|
-
return false;
|
|
28548
|
-
};
|
|
28549
|
-
const getDirectCalls = (functionNode, scopes) => {
|
|
28550
|
-
const bindingIdentifier = isNodeOfType(functionNode, "FunctionDeclaration") ? functionNode.id : isNodeOfType(functionNode.parent, "VariableDeclarator") ? functionNode.parent.id : null;
|
|
28551
|
-
if (!bindingIdentifier || !isNodeOfType(bindingIdentifier, "Identifier")) return [];
|
|
28552
|
-
const symbol = scopes.scopeFor(functionNode).symbolsByName.get(bindingIdentifier.name);
|
|
28553
|
-
if (!symbol) return [];
|
|
28554
|
-
const calls = [];
|
|
28555
|
-
const program = findProgramRoot(functionNode);
|
|
28556
|
-
if (!program) return calls;
|
|
28557
|
-
walkAst(program, (candidate) => {
|
|
28558
|
-
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
28559
|
-
const callee = stripParenExpression(candidate.callee);
|
|
28560
|
-
if (!isNodeOfType(callee, "Identifier")) return;
|
|
28561
|
-
if (resolveConstIdentifierAlias(callee, scopes)?.id === symbol.id) calls.push(candidate);
|
|
28562
|
-
});
|
|
28563
|
-
return calls;
|
|
28564
|
-
};
|
|
28565
|
-
const processLifetimeClassSymbolIdsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
28566
|
-
const getProcessLifetimeClassSymbolIds = (node, scopes) => {
|
|
28567
|
-
const cached = processLifetimeClassSymbolIdsByAnalysis.get(scopes);
|
|
28568
|
-
if (cached) return cached;
|
|
28569
|
-
const instantiationScopeBySymbolId = /* @__PURE__ */ new Map();
|
|
28570
|
-
const program = findProgramRoot(node);
|
|
28571
|
-
if (program) walkAst(program, (candidate) => {
|
|
28572
|
-
if (!isNodeOfType(candidate, "NewExpression")) return;
|
|
28573
|
-
const callee = stripParenExpression(candidate.callee);
|
|
28574
|
-
const symbol = isNodeOfType(callee, "Identifier") ? resolveConstIdentifierAlias(callee, scopes) : null;
|
|
28575
|
-
if (!symbol) return;
|
|
28576
|
-
const wasModuleOnly = instantiationScopeBySymbolId.get(symbol.id) ?? true;
|
|
28577
|
-
instantiationScopeBySymbolId.set(symbol.id, wasModuleOnly && isEvaluatedAtModuleScope(candidate));
|
|
28578
|
-
});
|
|
28579
|
-
const processLifetimeSymbolIds = /* @__PURE__ */ new Set();
|
|
28580
|
-
for (const [symbolId, isModuleOnly] of instantiationScopeBySymbolId) if (isModuleOnly) processLifetimeSymbolIds.add(symbolId);
|
|
28581
|
-
processLifetimeClassSymbolIdsByAnalysis.set(scopes, processLifetimeSymbolIds);
|
|
28582
|
-
return processLifetimeSymbolIds;
|
|
28583
|
-
};
|
|
28584
|
-
const isProcessLifetimeWiring = (node, scopes) => {
|
|
28585
|
-
let ancestor = node.parent;
|
|
28586
|
-
while (ancestor) {
|
|
28587
|
-
if (!isFunctionLike$1(ancestor)) {
|
|
28588
|
-
ancestor = ancestor.parent ?? null;
|
|
28589
|
-
continue;
|
|
28590
|
-
}
|
|
28591
|
-
const functionName = getFunctionName(ancestor);
|
|
28592
|
-
if (functionName && PROCESS_LIFETIME_WIRING_NAME_PATTERN.test(functionName) && isModuleScopedFunction(ancestor)) {
|
|
28593
|
-
const calls = getDirectCalls(ancestor, scopes);
|
|
28594
|
-
return calls.length > 0 && calls.every(isEvaluatedAtModuleScope);
|
|
28595
|
-
}
|
|
28596
|
-
const methodDefinition = ancestor.parent;
|
|
28597
|
-
if (isNodeOfType(methodDefinition, "MethodDefinition") && methodDefinition.kind === "constructor") {
|
|
28598
|
-
let classNode = methodDefinition.parent?.parent;
|
|
28599
|
-
if (isNodeOfType(classNode, "ClassDeclaration") || isNodeOfType(classNode, "ClassExpression")) {
|
|
28600
|
-
const classRoot = findTransparentExpressionRoot(classNode);
|
|
28601
|
-
const classInstantiation = classRoot.parent;
|
|
28602
|
-
if (isNodeOfType(classNode, "ClassExpression") && isNodeOfType(classInstantiation, "NewExpression") && stripParenExpression(classInstantiation.callee) === classRoot) return isEvaluatedAtModuleScope(classInstantiation);
|
|
28603
|
-
const classSymbol = getClassBindingSymbol(classNode, scopes);
|
|
28604
|
-
return Boolean(classSymbol && getProcessLifetimeClassSymbolIds(node, scopes).has(classSymbol.id));
|
|
28605
|
-
}
|
|
28606
|
-
}
|
|
28607
|
-
return false;
|
|
28608
|
-
}
|
|
28609
|
-
return false;
|
|
28610
|
-
};
|
|
28611
|
-
const mayCarryAbortSignal = (optionsArgument, scopes) => {
|
|
28612
|
-
if (!optionsArgument) return false;
|
|
28613
|
-
const options = resolveStableOptionsObject(optionsArgument, ["signal"], scopes);
|
|
28614
|
-
if (!options) return true;
|
|
28615
|
-
return options.properties.some((property) => {
|
|
28616
|
-
if (!isNodeOfType(property, "Property")) return true;
|
|
28617
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
28618
|
-
if (propertyName === null) return true;
|
|
28619
|
-
if (propertyName !== "signal") return false;
|
|
28620
|
-
const value = property.value;
|
|
28621
|
-
if (isNodeOfType(value, "Identifier") && value.name === "undefined") return false;
|
|
28622
|
-
if (isNodeOfType(value, "Literal") && value.value == null) return false;
|
|
28623
|
-
return !(isNodeOfType(value, "UnaryExpression") && value.operator === "void");
|
|
28624
|
-
});
|
|
28625
|
-
};
|
|
28626
|
-
const callbackDisposesReaction = (callExpression, subscriptionName, scopes) => {
|
|
28627
|
-
const callbackArgument = callExpression.arguments[REACTION_CALLBACK_INDEX[subscriptionName]];
|
|
28628
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return false;
|
|
28629
|
-
const callback = resolveExactLocalFunction(callbackArgument, scopes);
|
|
28630
|
-
if (!callback || !isFunctionLike$1(callback)) return false;
|
|
28631
|
-
const reactionParameter = callback.params?.[REACTION_PARAMETER_INDEX[subscriptionName]];
|
|
28632
|
-
if (!reactionParameter || !isNodeOfType(reactionParameter, "Identifier")) return false;
|
|
28633
|
-
const reactionSymbol = scopes.symbolFor(reactionParameter);
|
|
28634
|
-
if (!reactionSymbol) return false;
|
|
28635
|
-
let doesDisposeReaction = false;
|
|
28636
|
-
walkAst(callback, (candidate) => {
|
|
28637
|
-
if (doesDisposeReaction || !isNodeOfType(candidate, "CallExpression")) return;
|
|
28638
|
-
const callee = stripParenExpression(candidate.callee);
|
|
28639
|
-
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
28640
|
-
const receiver = stripParenExpression(callee.object);
|
|
28641
|
-
if (isNodeOfType(receiver, "Identifier") && scopes.symbolFor(receiver)?.id === reactionSymbol.id && getStaticPropertyKeyName(callee, { allowComputedString: true }) === "dispose") doesDisposeReaction = true;
|
|
28642
|
-
});
|
|
28643
|
-
return doesDisposeReaction;
|
|
28644
|
-
};
|
|
28645
|
-
const getMemberReceiverRoot = (memberExpression) => {
|
|
28646
|
-
let receiver = stripParenExpression(memberExpression);
|
|
28647
|
-
while (isNodeOfType(receiver, "MemberExpression")) receiver = stripParenExpression(receiver.object);
|
|
28648
|
-
return receiver;
|
|
28649
|
-
};
|
|
28650
|
-
const observesOnlyInstanceRootedState = (callExpression, subscriptionName, scopes) => {
|
|
28651
|
-
const callbackArgument = callExpression.arguments[OBSERVATION_CALLBACK_INDEX[subscriptionName]];
|
|
28652
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return false;
|
|
28653
|
-
const callback = resolveExactLocalFunction(callbackArgument, scopes);
|
|
28654
|
-
if (!callback || !isFunctionLike$1(callback)) return false;
|
|
28655
|
-
let observesExternalState = false;
|
|
28656
|
-
walkAst(callback, (candidate) => {
|
|
28657
|
-
if (candidate !== callback && isFunctionLike$1(candidate)) return false;
|
|
28658
|
-
if (observesExternalState || !isNodeOfType(candidate, "MemberExpression")) return;
|
|
28659
|
-
const receiver = getMemberReceiverRoot(candidate);
|
|
28660
|
-
const candidateRoot = findTransparentExpressionRoot(candidate);
|
|
28661
|
-
const parent = candidateRoot.parent;
|
|
28662
|
-
const isDirectMethodCall = isNodeOfType(parent, "CallExpression") && parent.callee === candidateRoot;
|
|
28663
|
-
if (isNodeOfType(receiver, "ThisExpression")) {
|
|
28664
|
-
if (isDirectMethodCall) observesExternalState = true;
|
|
28665
|
-
return;
|
|
28666
|
-
}
|
|
28667
|
-
if (!isNodeOfType(receiver, "Identifier")) {
|
|
28668
|
-
observesExternalState = true;
|
|
28669
|
-
return;
|
|
28670
|
-
}
|
|
28671
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
28672
|
-
if (receiverSymbol && isAstDescendant(receiverSymbol.declarationNode, callback) || scopes.isGlobalReference(receiver) && NON_OBSERVABLE_GLOBAL_RECEIVER_NAMES.has(receiver.name)) return;
|
|
28673
|
-
if (receiverSymbol?.kind === "import" && isDirectMethodCall && NON_OBSERVING_IMPORTED_METHOD_NAMES.has(getStaticPropertyKeyName(candidate, { allowComputedString: true }) ?? "")) return;
|
|
28674
|
-
observesExternalState = true;
|
|
28675
|
-
});
|
|
28676
|
-
return !observesExternalState;
|
|
28677
|
-
};
|
|
28678
|
-
const isForwardedFromConciseArrow = (callExpression) => {
|
|
28679
|
-
let expressionRoot = findTransparentExpressionRoot(callExpression);
|
|
28680
|
-
let parent = expressionRoot.parent;
|
|
28681
|
-
while (parent) {
|
|
28682
|
-
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === expressionRoot) return true;
|
|
28683
|
-
if (isNodeOfType(parent, "LogicalExpression") && (parent.right === expressionRoot || parent.left === expressionRoot && parent.operator !== "&&") || isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === expressionRoot || parent.alternate === expressionRoot) || isNodeOfType(parent, "SequenceExpression") && parent.expressions[parent.expressions.length - 1] === expressionRoot) {
|
|
28684
|
-
expressionRoot = findTransparentExpressionRoot(parent);
|
|
28685
|
-
parent = expressionRoot.parent;
|
|
28686
|
-
continue;
|
|
28687
|
-
}
|
|
28688
|
-
return false;
|
|
28689
|
-
}
|
|
28690
|
-
return false;
|
|
28691
|
-
};
|
|
28692
|
-
const isDisposerDiscarded = (callExpression) => {
|
|
28693
|
-
if (isForwardedFromConciseArrow(callExpression)) return false;
|
|
28694
|
-
const expressionRoot = findTransparentExpressionRoot(callExpression);
|
|
28695
|
-
if (isResultDiscardedCall(callExpression)) return true;
|
|
28696
|
-
const parent = expressionRoot.parent;
|
|
28697
|
-
if (!parent) return false;
|
|
28698
|
-
if (isNodeOfType(parent, "UnaryExpression") || isNodeOfType(parent, "BinaryExpression")) return true;
|
|
28699
|
-
if ((isNodeOfType(parent, "IfStatement") || isNodeOfType(parent, "WhileStatement") || isNodeOfType(parent, "DoWhileStatement") || isNodeOfType(parent, "ForStatement")) && parent.test === expressionRoot) return true;
|
|
28700
|
-
if (isNodeOfType(parent, "ConditionalExpression") && parent.test === expressionRoot || isNodeOfType(parent, "SwitchStatement") && parent.discriminant === expressionRoot) return true;
|
|
28701
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.left === expressionRoot) return parent.operator === "&&" || isResultDiscardedCall(parent);
|
|
28702
|
-
const callee = isNodeOfType(parent, "CallExpression") ? stripParenExpression(parent.callee) : null;
|
|
28703
|
-
return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments.some((argument) => argument === expressionRoot) && isNodeOfType(callee, "Identifier") && DISPOSER_COERCION_NAMES.has(callee.name));
|
|
28704
|
-
};
|
|
28705
|
-
const mobxReactionDisposerDiscarded = defineRule({
|
|
28706
|
-
id: "mobx-reaction-disposer-discarded",
|
|
28707
|
-
title: "MobX reaction disposer discarded",
|
|
28708
|
-
severity: "warn",
|
|
28709
|
-
category: "Bugs",
|
|
28710
|
-
requires: MOBX_RULE_GATES["mobx-reaction-disposer-discarded"].requires,
|
|
28711
|
-
recommendation: "Keep the disposer returned by `reaction` or `autorun` and invoke it during teardown, or provide an AbortSignal.",
|
|
28712
|
-
create: (context) => ({ CallExpression(callExpression) {
|
|
28713
|
-
const subscriptionName = resolveLeakingSubscriptionName(callExpression, context.scopes);
|
|
28714
|
-
if (!subscriptionName || !isDisposerDiscarded(callExpression)) return;
|
|
28715
|
-
if (isEvaluatedAtModuleScope(callExpression)) return;
|
|
28716
|
-
if (isProcessLifetimeWiring(callExpression, context.scopes)) return;
|
|
28717
|
-
if (callbackDisposesReaction(callExpression, subscriptionName, context.scopes)) return;
|
|
28718
|
-
if (observesOnlyInstanceRootedState(callExpression, subscriptionName, context.scopes)) return;
|
|
28719
|
-
const optionsArgument = callExpression.arguments[OPTIONS_ARGUMENT_INDEX[subscriptionName]];
|
|
28720
|
-
if (mayCarryAbortSignal(optionsArgument, context.scopes)) return;
|
|
28721
|
-
context.report({
|
|
28722
|
-
node: callExpression,
|
|
28723
|
-
message: MESSAGE$42
|
|
28724
|
-
});
|
|
28725
|
-
} })
|
|
28726
|
-
});
|
|
28727
|
-
//#endregion
|
|
28728
28187
|
//#region src/plugin/utils/has-jsx-prop.ts
|
|
28729
28188
|
const hasJsxProp = (attributes, targetProp) => {
|
|
28730
28189
|
for (const attribute of attributes) {
|
|
@@ -30225,20 +29684,17 @@ const catchClauseRethrowsCaught = (handler) => {
|
|
|
30225
29684
|
return didRethrow;
|
|
30226
29685
|
};
|
|
30227
29686
|
//#endregion
|
|
30228
|
-
//#region src/plugin/utils/
|
|
30229
|
-
const
|
|
29687
|
+
//#region src/plugin/utils/is-immediately-invoked-function.ts
|
|
29688
|
+
const isImmediatelyInvokedFunction = (functionNode) => {
|
|
30230
29689
|
let wrappedCallee = functionNode;
|
|
30231
29690
|
let enclosing = functionNode.parent;
|
|
30232
29691
|
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
30233
29692
|
wrappedCallee = enclosing;
|
|
30234
29693
|
enclosing = enclosing.parent ?? null;
|
|
30235
29694
|
}
|
|
30236
|
-
return enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee
|
|
29695
|
+
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
30237
29696
|
};
|
|
30238
29697
|
//#endregion
|
|
30239
|
-
//#region src/plugin/utils/is-immediately-invoked-function.ts
|
|
30240
|
-
const isImmediatelyInvokedFunction = (functionNode) => Boolean(findImmediatelyInvokedCallExpression(functionNode));
|
|
30241
|
-
//#endregion
|
|
30242
29698
|
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
30243
29699
|
const findGuardingTryStatement = (node) => {
|
|
30244
29700
|
let child = node;
|
|
@@ -30789,7 +30245,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
30789
30245
|
});
|
|
30790
30246
|
//#endregion
|
|
30791
30247
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
30792
|
-
const MESSAGE$
|
|
30248
|
+
const MESSAGE$40 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
30793
30249
|
const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
30794
30250
|
const noAccessKey = defineRule({
|
|
30795
30251
|
id: "no-access-key",
|
|
@@ -30808,7 +30264,7 @@ const noAccessKey = defineRule({
|
|
|
30808
30264
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
30809
30265
|
context.report({
|
|
30810
30266
|
node: accessKey,
|
|
30811
|
-
message: MESSAGE$
|
|
30267
|
+
message: MESSAGE$40
|
|
30812
30268
|
});
|
|
30813
30269
|
return;
|
|
30814
30270
|
}
|
|
@@ -30818,7 +30274,7 @@ const noAccessKey = defineRule({
|
|
|
30818
30274
|
if (isUndefinedIdentifier$1(expression)) return;
|
|
30819
30275
|
context.report({
|
|
30820
30276
|
node: accessKey,
|
|
30821
|
-
message: MESSAGE$
|
|
30277
|
+
message: MESSAGE$40
|
|
30822
30278
|
});
|
|
30823
30279
|
}
|
|
30824
30280
|
} };
|
|
@@ -33161,7 +32617,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
33161
32617
|
});
|
|
33162
32618
|
//#endregion
|
|
33163
32619
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
33164
|
-
const MESSAGE$
|
|
32620
|
+
const MESSAGE$39 = "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.";
|
|
33165
32621
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
33166
32622
|
"button",
|
|
33167
32623
|
"embed",
|
|
@@ -33273,7 +32729,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
33273
32729
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
33274
32730
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
33275
32731
|
node: ariaHidden,
|
|
33276
|
-
message: MESSAGE$
|
|
32732
|
+
message: MESSAGE$39
|
|
33277
32733
|
});
|
|
33278
32734
|
} })
|
|
33279
32735
|
});
|
|
@@ -34320,7 +33776,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
34320
33776
|
};
|
|
34321
33777
|
//#endregion
|
|
34322
33778
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
34323
|
-
const MESSAGE$
|
|
33779
|
+
const MESSAGE$38 = "Your users can see & submit the wrong data when this list reorders.";
|
|
34324
33780
|
const SECOND_INDEX_METHODS = new Set([
|
|
34325
33781
|
"every",
|
|
34326
33782
|
"filter",
|
|
@@ -34439,14 +33895,14 @@ const noArrayIndexKey = defineRule({
|
|
|
34439
33895
|
if (propName !== "key") continue;
|
|
34440
33896
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
34441
33897
|
node: property,
|
|
34442
|
-
message: MESSAGE$
|
|
33898
|
+
message: MESSAGE$38
|
|
34443
33899
|
});
|
|
34444
33900
|
}
|
|
34445
33901
|
} })
|
|
34446
33902
|
});
|
|
34447
33903
|
//#endregion
|
|
34448
33904
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
34449
|
-
const MESSAGE$
|
|
33905
|
+
const MESSAGE$37 = "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.";
|
|
34450
33906
|
const noAsyncEffectCallback = defineRule({
|
|
34451
33907
|
id: "no-async-effect-callback",
|
|
34452
33908
|
title: "Async effect callback",
|
|
@@ -34464,13 +33920,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
34464
33920
|
if (!callback.async) return;
|
|
34465
33921
|
context.report({
|
|
34466
33922
|
node: callback,
|
|
34467
|
-
message: MESSAGE$
|
|
33923
|
+
message: MESSAGE$37
|
|
34468
33924
|
});
|
|
34469
33925
|
} })
|
|
34470
33926
|
});
|
|
34471
33927
|
//#endregion
|
|
34472
33928
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
34473
|
-
const MESSAGE$
|
|
33929
|
+
const MESSAGE$36 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
34474
33930
|
const resolveSettings$21 = (settings) => {
|
|
34475
33931
|
const reactDoctor = settings?.["react-doctor"];
|
|
34476
33932
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -34567,7 +34023,7 @@ const noAutofocus = defineRule({
|
|
|
34567
34023
|
if (isConditionallyRendered(node)) return;
|
|
34568
34024
|
context.report({
|
|
34569
34025
|
node: autoFocusAttribute,
|
|
34570
|
-
message: MESSAGE$
|
|
34026
|
+
message: MESSAGE$36
|
|
34571
34027
|
});
|
|
34572
34028
|
} };
|
|
34573
34029
|
}
|
|
@@ -35896,7 +35352,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35896
35352
|
});
|
|
35897
35353
|
//#endregion
|
|
35898
35354
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
35899
|
-
const MESSAGE$
|
|
35355
|
+
const MESSAGE$35 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
35900
35356
|
const noChildrenProp = defineRule({
|
|
35901
35357
|
id: "no-children-prop",
|
|
35902
35358
|
title: "Children passed as a prop",
|
|
@@ -35908,7 +35364,7 @@ const noChildrenProp = defineRule({
|
|
|
35908
35364
|
if (node.name.name !== "children") return;
|
|
35909
35365
|
context.report({
|
|
35910
35366
|
node: node.name,
|
|
35911
|
-
message: MESSAGE$
|
|
35367
|
+
message: MESSAGE$35
|
|
35912
35368
|
});
|
|
35913
35369
|
},
|
|
35914
35370
|
CallExpression(node) {
|
|
@@ -35921,7 +35377,7 @@ const noChildrenProp = defineRule({
|
|
|
35921
35377
|
const propertyKey = property.key;
|
|
35922
35378
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
35923
35379
|
node: propertyKey,
|
|
35924
|
-
message: MESSAGE$
|
|
35380
|
+
message: MESSAGE$35
|
|
35925
35381
|
});
|
|
35926
35382
|
}
|
|
35927
35383
|
}
|
|
@@ -35929,7 +35385,7 @@ const noChildrenProp = defineRule({
|
|
|
35929
35385
|
});
|
|
35930
35386
|
//#endregion
|
|
35931
35387
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
35932
|
-
const MESSAGE$
|
|
35388
|
+
const MESSAGE$34 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
35933
35389
|
const noCloneElement = defineRule({
|
|
35934
35390
|
id: "no-clone-element",
|
|
35935
35391
|
title: "cloneElement makes child props fragile",
|
|
@@ -35942,7 +35398,7 @@ const noCloneElement = defineRule({
|
|
|
35942
35398
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
35943
35399
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
35944
35400
|
node: callee,
|
|
35945
|
-
message: MESSAGE$
|
|
35401
|
+
message: MESSAGE$34
|
|
35946
35402
|
});
|
|
35947
35403
|
return;
|
|
35948
35404
|
}
|
|
@@ -35955,7 +35411,7 @@ const noCloneElement = defineRule({
|
|
|
35955
35411
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
35956
35412
|
context.report({
|
|
35957
35413
|
node: callee,
|
|
35958
|
-
message: MESSAGE$
|
|
35414
|
+
message: MESSAGE$34
|
|
35959
35415
|
});
|
|
35960
35416
|
}
|
|
35961
35417
|
} })
|
|
@@ -35975,7 +35431,7 @@ const getCallMethodName = (callee) => {
|
|
|
35975
35431
|
};
|
|
35976
35432
|
//#endregion
|
|
35977
35433
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
35978
|
-
const MESSAGE$
|
|
35434
|
+
const MESSAGE$33 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
35979
35435
|
const CONTEXT_MODULES = [
|
|
35980
35436
|
"react",
|
|
35981
35437
|
"use-context-selector",
|
|
@@ -36023,7 +35479,7 @@ const noCreateContextInRender = defineRule({
|
|
|
36023
35479
|
if (!componentOrHookName) return;
|
|
36024
35480
|
context.report({
|
|
36025
35481
|
node,
|
|
36026
|
-
message: `${MESSAGE$
|
|
35482
|
+
message: `${MESSAGE$33} (called inside "${componentOrHookName}")`
|
|
36027
35483
|
});
|
|
36028
35484
|
} })
|
|
36029
35485
|
});
|
|
@@ -36770,7 +36226,7 @@ const isProvenOneShotTestingLibraryComponent = (functionNode, filename, scopes)
|
|
|
36770
36226
|
};
|
|
36771
36227
|
//#endregion
|
|
36772
36228
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
36773
|
-
const MESSAGE$
|
|
36229
|
+
const MESSAGE$32 = "`createRef()` may escape or be observed beyond the render that created it, so a later render can replace the ref object and detach the observed one. Hoist a `useRef()` call to the component's unconditional top level instead.";
|
|
36774
36230
|
const isUseMemoCallbackArgument = (functionNode, scopes) => {
|
|
36775
36231
|
const parent = functionNode.parent;
|
|
36776
36232
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -36824,7 +36280,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
36824
36280
|
if (isCreateRefResultWriteOnly(node, context.filename, context.scopes)) return;
|
|
36825
36281
|
context.report({
|
|
36826
36282
|
node,
|
|
36827
|
-
message: MESSAGE$
|
|
36283
|
+
message: MESSAGE$32
|
|
36828
36284
|
});
|
|
36829
36285
|
} })
|
|
36830
36286
|
});
|
|
@@ -36964,7 +36420,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
36964
36420
|
});
|
|
36965
36421
|
//#endregion
|
|
36966
36422
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
36967
|
-
const MESSAGE$
|
|
36423
|
+
const MESSAGE$31 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
36968
36424
|
const noDanger = defineRule({
|
|
36969
36425
|
id: "no-danger",
|
|
36970
36426
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -36978,7 +36434,7 @@ const noDanger = defineRule({
|
|
|
36978
36434
|
if (!propAttribute) return;
|
|
36979
36435
|
context.report({
|
|
36980
36436
|
node: propAttribute.name,
|
|
36981
|
-
message: MESSAGE$
|
|
36437
|
+
message: MESSAGE$31
|
|
36982
36438
|
});
|
|
36983
36439
|
},
|
|
36984
36440
|
CallExpression(node) {
|
|
@@ -36990,7 +36446,7 @@ const noDanger = defineRule({
|
|
|
36990
36446
|
const propertyKey = property.key;
|
|
36991
36447
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
36992
36448
|
node: propertyKey,
|
|
36993
|
-
message: MESSAGE$
|
|
36449
|
+
message: MESSAGE$31
|
|
36994
36450
|
});
|
|
36995
36451
|
}
|
|
36996
36452
|
}
|
|
@@ -37013,7 +36469,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
37013
36469
|
};
|
|
37014
36470
|
//#endregion
|
|
37015
36471
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
37016
|
-
const MESSAGE$
|
|
36472
|
+
const MESSAGE$30 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
37017
36473
|
const mergePropsShape = (target, source) => {
|
|
37018
36474
|
target.hasDangerously ||= source.hasDangerously;
|
|
37019
36475
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -37088,7 +36544,7 @@ const noDangerWithChildren = defineRule({
|
|
|
37088
36544
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
37089
36545
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
37090
36546
|
node: opening,
|
|
37091
|
-
message: MESSAGE$
|
|
36547
|
+
message: MESSAGE$30
|
|
37092
36548
|
});
|
|
37093
36549
|
},
|
|
37094
36550
|
CallExpression(node) {
|
|
@@ -37101,7 +36557,7 @@ const noDangerWithChildren = defineRule({
|
|
|
37101
36557
|
const positionalChildren = node.arguments.slice(2);
|
|
37102
36558
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
37103
36559
|
node,
|
|
37104
|
-
message: MESSAGE$
|
|
36560
|
+
message: MESSAGE$30
|
|
37105
36561
|
});
|
|
37106
36562
|
}
|
|
37107
36563
|
})
|
|
@@ -38076,7 +37532,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
38076
37532
|
//#endregion
|
|
38077
37533
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
38078
37534
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
38079
|
-
const MESSAGE$
|
|
37535
|
+
const MESSAGE$29 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
38080
37536
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
38081
37537
|
let ancestor = setStateCall.parent;
|
|
38082
37538
|
while (ancestor) {
|
|
@@ -38203,7 +37659,7 @@ const noDidMountSetState = defineRule({
|
|
|
38203
37659
|
}
|
|
38204
37660
|
context.report({
|
|
38205
37661
|
node: node.callee,
|
|
38206
|
-
message: MESSAGE$
|
|
37662
|
+
message: MESSAGE$29
|
|
38207
37663
|
});
|
|
38208
37664
|
} };
|
|
38209
37665
|
}
|
|
@@ -38221,7 +37677,7 @@ const findEnclosingClass = (node) => {
|
|
|
38221
37677
|
//#endregion
|
|
38222
37678
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
38223
37679
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
38224
|
-
const MESSAGE$
|
|
37680
|
+
const MESSAGE$28 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
38225
37681
|
const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
|
|
38226
37682
|
const EQUALITY_OPERATORS = new Set([
|
|
38227
37683
|
"==",
|
|
@@ -38615,7 +38071,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
38615
38071
|
if (isInsideDiffGuard(node, context.scopes)) return;
|
|
38616
38072
|
context.report({
|
|
38617
38073
|
node: node.callee,
|
|
38618
|
-
message: MESSAGE$
|
|
38074
|
+
message: MESSAGE$28
|
|
38619
38075
|
});
|
|
38620
38076
|
} };
|
|
38621
38077
|
}
|
|
@@ -38638,7 +38094,7 @@ const isStateMemberExpression = (node) => {
|
|
|
38638
38094
|
};
|
|
38639
38095
|
//#endregion
|
|
38640
38096
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
38641
|
-
const MESSAGE$
|
|
38097
|
+
const MESSAGE$27 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
|
|
38642
38098
|
const shouldIgnoreMutation = (node) => {
|
|
38643
38099
|
let isConstructor = false;
|
|
38644
38100
|
let isInsideCallExpression = false;
|
|
@@ -38660,7 +38116,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
38660
38116
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
38661
38117
|
context.report({
|
|
38662
38118
|
node: reportNode,
|
|
38663
|
-
message: MESSAGE$
|
|
38119
|
+
message: MESSAGE$27
|
|
38664
38120
|
});
|
|
38665
38121
|
};
|
|
38666
38122
|
const noDirectMutationState = defineRule({
|
|
@@ -39011,7 +38467,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
39011
38467
|
});
|
|
39012
38468
|
//#endregion
|
|
39013
38469
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
39014
|
-
const MESSAGE$
|
|
38470
|
+
const MESSAGE$26 = "`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.";
|
|
39015
38471
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
39016
38472
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
39017
38473
|
const noDocumentWrite = defineRule({
|
|
@@ -39028,7 +38484,7 @@ const noDocumentWrite = defineRule({
|
|
|
39028
38484
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
39029
38485
|
context.report({
|
|
39030
38486
|
node,
|
|
39031
|
-
message: MESSAGE$
|
|
38487
|
+
message: MESSAGE$26
|
|
39032
38488
|
});
|
|
39033
38489
|
} })
|
|
39034
38490
|
});
|
|
@@ -42289,10 +41745,18 @@ const isCompletionSinkAfterCancellationEarlyExit = (completionSink, cancellation
|
|
|
42289
41745
|
return false;
|
|
42290
41746
|
};
|
|
42291
41747
|
const isCompletionSinkGuardedByCancellationFlag = (completionSink, cancellationFlagKey, context) => isCompletionSinkInsideCancellationGuard(completionSink, cancellationFlagKey, context) || isCompletionSinkAfterCancellationEarlyExit(completionSink, cancellationFlagKey, context);
|
|
41748
|
+
const isDescendantOf = (node, ancestor) => {
|
|
41749
|
+
let currentNode = node;
|
|
41750
|
+
while (currentNode) {
|
|
41751
|
+
if (currentNode === ancestor) return true;
|
|
41752
|
+
currentNode = currentNode.parent ?? null;
|
|
41753
|
+
}
|
|
41754
|
+
return false;
|
|
41755
|
+
};
|
|
42292
41756
|
const isPromiseContinuationForRequest = (functionNode, request) => {
|
|
42293
41757
|
const callNode = functionNode.parent;
|
|
42294
41758
|
if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((callArgument) => callArgument === functionNode) || !isNodeOfType(callNode.callee, "MemberExpression") || callNode.callee.computed || !isNodeOfType(callNode.callee.property, "Identifier") || !PROMISE_CONTINUATION_METHOD_NAMES$1.has(callNode.callee.property.name)) return false;
|
|
42295
|
-
return
|
|
41759
|
+
return isDescendantOf(request, callNode.callee.object);
|
|
42296
41760
|
};
|
|
42297
41761
|
const isAwaitedInFunction = (request, functionNode) => {
|
|
42298
41762
|
let currentNode = request.parent;
|
|
@@ -42360,7 +41824,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
42360
41824
|
"ReactDOM",
|
|
42361
41825
|
"ReactDom"
|
|
42362
41826
|
]);
|
|
42363
|
-
const MESSAGE$
|
|
41827
|
+
const MESSAGE$25 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
42364
41828
|
const noFindDomNode = defineRule({
|
|
42365
41829
|
id: "no-find-dom-node",
|
|
42366
41830
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -42371,7 +41835,7 @@ const noFindDomNode = defineRule({
|
|
|
42371
41835
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
42372
41836
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
42373
41837
|
node: callee,
|
|
42374
|
-
message: MESSAGE$
|
|
41838
|
+
message: MESSAGE$25
|
|
42375
41839
|
});
|
|
42376
41840
|
return;
|
|
42377
41841
|
}
|
|
@@ -42382,7 +41846,7 @@ const noFindDomNode = defineRule({
|
|
|
42382
41846
|
if (callee.property.name !== "findDOMNode") return;
|
|
42383
41847
|
context.report({
|
|
42384
41848
|
node: callee.property,
|
|
42385
|
-
message: MESSAGE$
|
|
41849
|
+
message: MESSAGE$25
|
|
42386
41850
|
});
|
|
42387
41851
|
}
|
|
42388
41852
|
} })
|
|
@@ -43520,7 +42984,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
43520
42984
|
});
|
|
43521
42985
|
//#endregion
|
|
43522
42986
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
43523
|
-
const MESSAGE$
|
|
42987
|
+
const MESSAGE$24 = "`<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.";
|
|
43524
42988
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
43525
42989
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
43526
42990
|
title: "Lazy image with high fetchPriority",
|
|
@@ -43534,7 +42998,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
43534
42998
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
43535
42999
|
context.report({
|
|
43536
43000
|
node: node.name,
|
|
43537
|
-
message: MESSAGE$
|
|
43001
|
+
message: MESSAGE$24
|
|
43538
43002
|
});
|
|
43539
43003
|
} })
|
|
43540
43004
|
});
|
|
@@ -43723,7 +43187,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
43723
43187
|
});
|
|
43724
43188
|
//#endregion
|
|
43725
43189
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
43726
|
-
const MESSAGE$
|
|
43190
|
+
const MESSAGE$23 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
43727
43191
|
const REACT_USE_REF_OPTIONS = {
|
|
43728
43192
|
allowGlobalReactNamespace: false,
|
|
43729
43193
|
allowUnboundBareCalls: false
|
|
@@ -43824,7 +43288,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
43824
43288
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
43825
43289
|
context.report({
|
|
43826
43290
|
node: indeterminateAttribute,
|
|
43827
|
-
message: MESSAGE$
|
|
43291
|
+
message: MESSAGE$23
|
|
43828
43292
|
});
|
|
43829
43293
|
},
|
|
43830
43294
|
CallExpression(node) {
|
|
@@ -43833,7 +43297,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
43833
43297
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
43834
43298
|
context.report({
|
|
43835
43299
|
node,
|
|
43836
|
-
message: MESSAGE$
|
|
43300
|
+
message: MESSAGE$23
|
|
43837
43301
|
});
|
|
43838
43302
|
}
|
|
43839
43303
|
};
|
|
@@ -44121,7 +43585,7 @@ const noIsMounted = defineRule({
|
|
|
44121
43585
|
});
|
|
44122
43586
|
//#endregion
|
|
44123
43587
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
44124
|
-
const MESSAGE$
|
|
43588
|
+
const MESSAGE$22 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
|
|
44125
43589
|
const isJsonMethodCall = (node, method) => {
|
|
44126
43590
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
44127
43591
|
const callee = node.callee;
|
|
@@ -44185,13 +43649,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
44185
43649
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
44186
43650
|
context.report({
|
|
44187
43651
|
node,
|
|
44188
|
-
message: MESSAGE$
|
|
43652
|
+
message: MESSAGE$22
|
|
44189
43653
|
});
|
|
44190
43654
|
} })
|
|
44191
43655
|
});
|
|
44192
43656
|
//#endregion
|
|
44193
43657
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
44194
|
-
const MESSAGE$
|
|
43658
|
+
const MESSAGE$21 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
44195
43659
|
const isJsxElementTypeReference = (node) => {
|
|
44196
43660
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
44197
43661
|
const typeName = node.typeName;
|
|
@@ -44243,7 +43707,7 @@ const noJsxElementType = defineRule({
|
|
|
44243
43707
|
if (isJsxImported) return;
|
|
44244
43708
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
44245
43709
|
node: typeAnnotation,
|
|
44246
|
-
message: MESSAGE$
|
|
43710
|
+
message: MESSAGE$21
|
|
44247
43711
|
});
|
|
44248
43712
|
}
|
|
44249
43713
|
};
|
|
@@ -45524,7 +44988,7 @@ const noMoment = defineRule({
|
|
|
45524
44988
|
});
|
|
45525
44989
|
//#endregion
|
|
45526
44990
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
45527
|
-
const MESSAGE$
|
|
44991
|
+
const MESSAGE$20 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
45528
44992
|
const resolveSettings$16 = (settings) => {
|
|
45529
44993
|
const reactDoctor = settings?.["react-doctor"];
|
|
45530
44994
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -45876,7 +45340,7 @@ const noMultiComp = defineRule({
|
|
|
45876
45340
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
45877
45341
|
for (const component of flagged.slice(1)) context.report({
|
|
45878
45342
|
node: component.reportNode,
|
|
45879
|
-
message: MESSAGE$
|
|
45343
|
+
message: MESSAGE$20
|
|
45880
45344
|
});
|
|
45881
45345
|
} };
|
|
45882
45346
|
}
|
|
@@ -45964,115 +45428,39 @@ const noMutableInDeps = defineRule({
|
|
|
45964
45428
|
}
|
|
45965
45429
|
});
|
|
45966
45430
|
//#endregion
|
|
45967
|
-
//#region src/plugin/utils/
|
|
45968
|
-
const
|
|
45969
|
-
|
|
45970
|
-
|
|
45971
|
-
|
|
45972
|
-
|
|
45973
|
-
|
|
45974
|
-
|
|
45975
|
-
|
|
45976
|
-
|
|
45977
|
-
|
|
45978
|
-
|
|
45979
|
-
let current = stripParenExpression(node);
|
|
45980
|
-
while (current && isNodeOfType(current, "MemberExpression")) current = stripParenExpression(current.object);
|
|
45981
|
-
return isNodeOfType(current, "Identifier") && state.mutableStateSourceNames.has(current.name) || Boolean(current && state.isAdditionalMutableStateSource?.(current));
|
|
45982
|
-
};
|
|
45983
|
-
const isExpressionReachableFromMutableState = (node, state) => {
|
|
45984
|
-
if (!node) return false;
|
|
45985
|
-
const expression = stripParenExpression(node);
|
|
45986
|
-
return isNodeOfType(expression, "Identifier") && state.mutableStateSourceNames.has(expression.name) || isNodeOfType(expression, "MemberExpression") && isExpressionRootedInMutableStateSource(expression, state) || Boolean(state.isAdditionalMutableStateSource?.(expression));
|
|
45987
|
-
};
|
|
45988
|
-
const addMutableStateReferenceBindings = (pattern, state) => {
|
|
45989
|
-
if (isNodeOfType(pattern, "Identifier")) {
|
|
45990
|
-
state.mutableStateSourceNames.add(pattern.name);
|
|
45991
|
-
return;
|
|
45992
|
-
}
|
|
45993
|
-
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
45994
|
-
for (const property of pattern.properties) {
|
|
45995
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
45996
|
-
const value = property.value;
|
|
45997
|
-
if (isNodeOfType(value, "Identifier")) state.mutableStateSourceNames.add(value.name);
|
|
45998
|
-
else if (isNodeOfType(value, "AssignmentPattern") && isNodeOfType(value.left, "Identifier")) state.mutableStateSourceNames.add(value.left.name);
|
|
45999
|
-
}
|
|
46000
|
-
return;
|
|
46001
|
-
}
|
|
46002
|
-
if (!isNodeOfType(pattern, "ArrayPattern")) return;
|
|
46003
|
-
for (const element of pattern.elements) if (isNodeOfType(element, "Identifier")) state.mutableStateSourceNames.add(element.name);
|
|
46004
|
-
else if (isNodeOfType(element, "AssignmentPattern") && isNodeOfType(element.left, "Identifier")) state.mutableStateSourceNames.add(element.left.name);
|
|
46005
|
-
};
|
|
46006
|
-
const updateMutableStateReferencesForVariableDeclaration = (declaration, state) => {
|
|
46007
|
-
for (const declarator of declaration.declarations) {
|
|
46008
|
-
const bindingNames = /* @__PURE__ */ new Set();
|
|
46009
|
-
collectPatternNames(declarator.id, bindingNames);
|
|
46010
|
-
for (const bindingName of bindingNames) state.mutableStateSourceNames.delete(bindingName);
|
|
46011
|
-
if (!isExpressionReachableFromMutableState(declarator.init, state)) continue;
|
|
46012
|
-
if (isNodeOfType(declarator.id, "Identifier")) state.mutableStateSourceNames.add(declarator.id.name);
|
|
46013
|
-
else addMutableStateReferenceBindings(declarator.id, state);
|
|
46014
|
-
}
|
|
46015
|
-
};
|
|
46016
|
-
const updateMutableStateReferencesForIdentifierAssignment = (assignment, state) => {
|
|
46017
|
-
if (!isNodeOfType(assignment.left, "Identifier")) return;
|
|
46018
|
-
state.mutableStateSourceNames.delete(assignment.left.name);
|
|
46019
|
-
if (isExpressionReachableFromMutableState(assignment.right, state)) state.mutableStateSourceNames.add(assignment.left.name);
|
|
46020
|
-
};
|
|
46021
|
-
const collectMutableStateReferenceMutations = (node, state, options = {}) => {
|
|
46022
|
-
if (isFunctionLike$1(node)) return [];
|
|
46023
|
-
const mutations = [];
|
|
46024
|
-
walkAst(node, (child) => {
|
|
46025
|
-
const candidate = stripParenExpression(child);
|
|
46026
|
-
if (child !== node && isFunctionLike$1(candidate)) return false;
|
|
46027
|
-
if (isNodeOfType(candidate, "AssignmentExpression")) {
|
|
46028
|
-
const assignmentTarget = stripParenExpression(candidate.left);
|
|
46029
|
-
if (isNodeOfType(assignmentTarget, "MemberExpression") && isExpressionRootedInMutableStateSource(candidate.left, state)) mutations.push({
|
|
46030
|
-
node: candidate,
|
|
46031
|
-
receiver: assignmentTarget.object
|
|
46032
|
-
});
|
|
46033
|
-
return;
|
|
46034
|
-
}
|
|
46035
|
-
if (isNodeOfType(candidate, "UpdateExpression")) {
|
|
46036
|
-
const updateTarget = stripParenExpression(candidate.argument);
|
|
46037
|
-
if (isNodeOfType(updateTarget, "MemberExpression") && isExpressionRootedInMutableStateSource(candidate.argument, state)) mutations.push({
|
|
46038
|
-
node: candidate,
|
|
46039
|
-
receiver: updateTarget.object
|
|
46040
|
-
});
|
|
46041
|
-
return;
|
|
45431
|
+
//#region src/plugin/utils/is-result-discarded-call.ts
|
|
45432
|
+
const isResultDiscardedCall = (callExpression) => {
|
|
45433
|
+
let node = callExpression;
|
|
45434
|
+
let parent = node.parent;
|
|
45435
|
+
while (parent) {
|
|
45436
|
+
if (isNodeOfType(parent, "ExpressionStatement")) return true;
|
|
45437
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
|
|
45438
|
+
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
|
|
45439
|
+
if (isNodeOfType(parent, "ChainExpression")) {
|
|
45440
|
+
node = parent;
|
|
45441
|
+
parent = node.parent;
|
|
45442
|
+
continue;
|
|
46042
45443
|
}
|
|
46043
|
-
if (isNodeOfType(
|
|
46044
|
-
|
|
46045
|
-
|
|
46046
|
-
|
|
46047
|
-
receiver: deleteTarget.object
|
|
46048
|
-
});
|
|
46049
|
-
return;
|
|
45444
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
|
|
45445
|
+
node = parent;
|
|
45446
|
+
parent = node.parent;
|
|
45447
|
+
continue;
|
|
46050
45448
|
}
|
|
46051
|
-
if (
|
|
46052
|
-
|
|
46053
|
-
|
|
46054
|
-
|
|
46055
|
-
node: candidate,
|
|
46056
|
-
receiver: firstArgument
|
|
46057
|
-
});
|
|
46058
|
-
return;
|
|
45449
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
|
|
45450
|
+
node = parent;
|
|
45451
|
+
parent = node.parent;
|
|
45452
|
+
continue;
|
|
46059
45453
|
}
|
|
46060
|
-
|
|
46061
|
-
|
|
46062
|
-
|
|
46063
|
-
|
|
46064
|
-
|
|
46065
|
-
|
|
46066
|
-
} else {
|
|
46067
|
-
if (!MUTATING_ARRAY_METHODS.has(methodName) && !MUTATING_COLLECTION_METHODS.has(methodName)) return;
|
|
46068
|
-
if (MUTATING_COLLECTION_METHODS.has(methodName) && !MUTATING_ARRAY_METHODS.has(methodName) && !isResultDiscardedCall(candidate)) return;
|
|
45454
|
+
if (isNodeOfType(parent, "SequenceExpression")) {
|
|
45455
|
+
const expressions = parent.expressions ?? [];
|
|
45456
|
+
if (expressions[expressions.length - 1] !== node) return true;
|
|
45457
|
+
node = parent;
|
|
45458
|
+
parent = node.parent;
|
|
45459
|
+
continue;
|
|
46069
45460
|
}
|
|
46070
|
-
|
|
46071
|
-
|
|
46072
|
-
|
|
46073
|
-
});
|
|
46074
|
-
});
|
|
46075
|
-
return mutations;
|
|
45461
|
+
return false;
|
|
45462
|
+
}
|
|
45463
|
+
return false;
|
|
46076
45464
|
};
|
|
46077
45465
|
//#endregion
|
|
46078
45466
|
//#region src/plugin/rules/state-and-effects/utils/lodash-mutator-call.ts
|
|
@@ -46165,13 +45553,14 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
46165
45553
|
};
|
|
46166
45554
|
//#endregion
|
|
46167
45555
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
46168
|
-
const MESSAGE$
|
|
45556
|
+
const MESSAGE$19 = "This reducer changes state in place, so your update is silently skipped.";
|
|
46169
45557
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
46170
45558
|
"copyWithin",
|
|
46171
45559
|
"fill",
|
|
46172
45560
|
"reverse",
|
|
46173
45561
|
"sort"
|
|
46174
45562
|
]);
|
|
45563
|
+
const REFLECT_MUTATION_METHODS = new Set(["deleteProperty", "set"]);
|
|
46175
45564
|
const cloneReducerPathState = (state) => ({
|
|
46176
45565
|
originalStateReferenceNames: new Set(state.originalStateReferenceNames),
|
|
46177
45566
|
mutableStateSourceNames: new Set(state.mutableStateSourceNames),
|
|
@@ -46203,11 +45592,33 @@ const isCallToImportedReactUseReducer = (node) => {
|
|
|
46203
45592
|
const binding = findVariableInitializer(callee.object, callee.object.name);
|
|
46204
45593
|
return Boolean(binding?.initializer && isReactNamespaceOrDefaultImportSpecifier(binding.initializer));
|
|
46205
45594
|
};
|
|
45595
|
+
const isStaticMethodCallOnNamedObject = (node, objectName, methodNames) => {
|
|
45596
|
+
const unwrappedNode = stripParenExpression(node);
|
|
45597
|
+
if (!isNodeOfType(unwrappedNode, "CallExpression")) return false;
|
|
45598
|
+
if (!isNodeOfType(unwrappedNode.callee, "MemberExpression")) return false;
|
|
45599
|
+
const calleeObject = unwrappedNode.callee.object;
|
|
45600
|
+
if (!isNodeOfType(calleeObject, "Identifier")) return false;
|
|
45601
|
+
if (calleeObject.name !== objectName) return false;
|
|
45602
|
+
if (!methodNames.has(getStaticMemberPropertyName(unwrappedNode.callee) ?? "")) return false;
|
|
45603
|
+
if (findVariableInitializer(calleeObject, calleeObject.name)) return false;
|
|
45604
|
+
return true;
|
|
45605
|
+
};
|
|
45606
|
+
const isExpressionRootedInMutableReducerStateSource = (node, state) => {
|
|
45607
|
+
let current = stripParenExpression(node);
|
|
45608
|
+
while (current && isNodeOfType(current, "MemberExpression")) current = stripParenExpression(current.object);
|
|
45609
|
+
return isNodeOfType(current, "Identifier") && state.mutableStateSourceNames.has(current.name);
|
|
45610
|
+
};
|
|
46206
45611
|
const isExpressionOriginalReducerStateReference = (node, state) => {
|
|
46207
45612
|
if (!node) return false;
|
|
46208
45613
|
const unwrappedNode = stripParenExpression(node);
|
|
46209
45614
|
return isNodeOfType(unwrappedNode, "Identifier") && state.originalStateReferenceNames.has(unwrappedNode.name);
|
|
46210
45615
|
};
|
|
45616
|
+
const isExpressionReachableFromOriginalReducerState = (node, state) => {
|
|
45617
|
+
if (!node) return false;
|
|
45618
|
+
if (isExpressionOriginalReducerStateReference(node, state)) return true;
|
|
45619
|
+
const unwrappedNode = stripParenExpression(node);
|
|
45620
|
+
return isNodeOfType(unwrappedNode, "MemberExpression") && isExpressionRootedInMutableReducerStateSource(unwrappedNode, state);
|
|
45621
|
+
};
|
|
46211
45622
|
const canExpressionReturnOriginalReducerStateReference = (node, state) => {
|
|
46212
45623
|
if (!node) return false;
|
|
46213
45624
|
const unwrappedNode = stripParenExpression(node);
|
|
@@ -46224,7 +45635,42 @@ const canExpressionReturnOriginalReducerStateReference = (node, state) => {
|
|
|
46224
45635
|
if (isNodeOfType(unwrappedNode, "SequenceExpression")) return canExpressionReturnOriginalReducerStateReference(unwrappedNode.expressions[unwrappedNode.expressions.length - 1], state);
|
|
46225
45636
|
return false;
|
|
46226
45637
|
};
|
|
46227
|
-
const collectReducerStateMutationsInExpressionOrStatement = (node, state) =>
|
|
45638
|
+
const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
|
|
45639
|
+
if (isFunctionLike$1(node)) return [];
|
|
45640
|
+
const mutations = [];
|
|
45641
|
+
walkAst(node, (child) => {
|
|
45642
|
+
const unwrappedChild = stripParenExpression(child);
|
|
45643
|
+
if (child !== node && isFunctionLike$1(unwrappedChild)) return false;
|
|
45644
|
+
if (isNodeOfType(unwrappedChild, "AssignmentExpression")) {
|
|
45645
|
+
if (isNodeOfType(stripParenExpression(unwrappedChild.left), "MemberExpression") && isExpressionRootedInMutableReducerStateSource(unwrappedChild.left, state)) mutations.push({ node: unwrappedChild });
|
|
45646
|
+
return;
|
|
45647
|
+
}
|
|
45648
|
+
if (isNodeOfType(unwrappedChild, "UpdateExpression")) {
|
|
45649
|
+
if (isNodeOfType(stripParenExpression(unwrappedChild.argument), "MemberExpression") && isExpressionRootedInMutableReducerStateSource(unwrappedChild.argument, state)) mutations.push({ node: unwrappedChild });
|
|
45650
|
+
return;
|
|
45651
|
+
}
|
|
45652
|
+
if (isNodeOfType(unwrappedChild, "UnaryExpression") && unwrappedChild.operator === "delete") {
|
|
45653
|
+
if (isNodeOfType(stripParenExpression(unwrappedChild.argument), "MemberExpression") && isExpressionRootedInMutableReducerStateSource(unwrappedChild.argument, state)) mutations.push({ node: unwrappedChild });
|
|
45654
|
+
return;
|
|
45655
|
+
}
|
|
45656
|
+
if (!isNodeOfType(unwrappedChild, "CallExpression")) return;
|
|
45657
|
+
const firstArgument = unwrappedChild.arguments?.[0];
|
|
45658
|
+
if (firstArgument && isExpressionRootedInMutableReducerStateSource(firstArgument, state) && (isStaticMethodCallOnNamedObject(unwrappedChild, "Object", OBJECT_PROPERTY_MUTATION_METHOD_NAMES) || isStaticMethodCallOnNamedObject(unwrappedChild, "Reflect", REFLECT_MUTATION_METHODS))) {
|
|
45659
|
+
mutations.push({ node: unwrappedChild });
|
|
45660
|
+
return;
|
|
45661
|
+
}
|
|
45662
|
+
if (firstArgument && isExpressionRootedInMutableReducerStateSource(firstArgument, state) && isLodashMutatorCall(unwrappedChild)) {
|
|
45663
|
+
mutations.push({ node: unwrappedChild });
|
|
45664
|
+
return;
|
|
45665
|
+
}
|
|
45666
|
+
if (!isNodeOfType(unwrappedChild.callee, "MemberExpression")) return;
|
|
45667
|
+
const methodName = getStaticMemberPropertyName(unwrappedChild.callee);
|
|
45668
|
+
if (!methodName || !MUTATING_ARRAY_METHODS.has(methodName) && !MUTATING_COLLECTION_METHODS.has(methodName)) return;
|
|
45669
|
+
if (MUTATING_COLLECTION_METHODS.has(methodName) && !MUTATING_ARRAY_METHODS.has(methodName) && !isResultDiscardedCall(unwrappedChild)) return;
|
|
45670
|
+
if (isExpressionRootedInMutableReducerStateSource(unwrappedChild.callee.object, state)) mutations.push({ node: unwrappedChild });
|
|
45671
|
+
});
|
|
45672
|
+
return mutations;
|
|
45673
|
+
};
|
|
46228
45674
|
const collectBlockScopedBindingNames = (blockStatement) => {
|
|
46229
45675
|
const blockScopedBindingNames = /* @__PURE__ */ new Set();
|
|
46230
45676
|
for (const statement of blockStatement.body ?? []) {
|
|
@@ -46244,20 +45690,60 @@ const restoreOuterIdentityForBlockScopedNames = (blockState, outerState, blockSc
|
|
|
46244
45690
|
}
|
|
46245
45691
|
return nextState;
|
|
46246
45692
|
};
|
|
45693
|
+
const recordDestructuredAliasNames = (pattern, state) => {
|
|
45694
|
+
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
45695
|
+
for (const property of pattern.properties ?? []) {
|
|
45696
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
45697
|
+
const valueNode = property.value;
|
|
45698
|
+
let leafIdentifier = null;
|
|
45699
|
+
if (isNodeOfType(valueNode, "Identifier")) leafIdentifier = valueNode;
|
|
45700
|
+
else if (isNodeOfType(valueNode, "AssignmentPattern") && isNodeOfType(valueNode.left, "Identifier")) leafIdentifier = valueNode.left;
|
|
45701
|
+
if (!leafIdentifier) continue;
|
|
45702
|
+
state.mutableStateSourceNames.add(leafIdentifier.name);
|
|
45703
|
+
}
|
|
45704
|
+
return;
|
|
45705
|
+
}
|
|
45706
|
+
if (isNodeOfType(pattern, "ArrayPattern")) for (const element of pattern.elements ?? []) {
|
|
45707
|
+
if (!element) continue;
|
|
45708
|
+
if (isNodeOfType(element, "Identifier")) {
|
|
45709
|
+
state.mutableStateSourceNames.add(element.name);
|
|
45710
|
+
continue;
|
|
45711
|
+
}
|
|
45712
|
+
if (isNodeOfType(element, "AssignmentPattern") && isNodeOfType(element.left, "Identifier")) {
|
|
45713
|
+
state.mutableStateSourceNames.add(element.left.name);
|
|
45714
|
+
continue;
|
|
45715
|
+
}
|
|
45716
|
+
if (isNodeOfType(element, "RestElement") && isNodeOfType(element.argument, "Identifier")) {}
|
|
45717
|
+
}
|
|
45718
|
+
};
|
|
46247
45719
|
const updateReducerStateIdentityForVariableDeclaration = (declaration, state) => {
|
|
46248
|
-
|
|
46249
|
-
|
|
46250
|
-
|
|
46251
|
-
|
|
46252
|
-
|
|
45720
|
+
for (const declarator of declaration.declarations ?? []) {
|
|
45721
|
+
if (isNodeOfType(declarator.id, "Identifier")) {
|
|
45722
|
+
const name = declarator.id.name;
|
|
45723
|
+
state.originalStateReferenceNames.delete(name);
|
|
45724
|
+
state.mutableStateSourceNames.delete(name);
|
|
45725
|
+
if (isExpressionOriginalReducerStateReference(declarator.init, state)) {
|
|
45726
|
+
state.originalStateReferenceNames.add(name);
|
|
45727
|
+
state.mutableStateSourceNames.add(name);
|
|
45728
|
+
continue;
|
|
45729
|
+
}
|
|
45730
|
+
if (isExpressionReachableFromOriginalReducerState(declarator.init, state)) state.mutableStateSourceNames.add(name);
|
|
45731
|
+
continue;
|
|
45732
|
+
}
|
|
45733
|
+
if ((isNodeOfType(declarator.id, "ObjectPattern") || isNodeOfType(declarator.id, "ArrayPattern")) && isExpressionReachableFromOriginalReducerState(declarator.init, state)) recordDestructuredAliasNames(declarator.id, state);
|
|
46253
45734
|
}
|
|
46254
45735
|
};
|
|
46255
45736
|
const updateReducerStateIdentityForIdentifierAssignment = (assignment, state) => {
|
|
46256
45737
|
if (!isNodeOfType(assignment.left, "Identifier")) return;
|
|
46257
|
-
updateMutableStateReferencesForIdentifierAssignment(assignment, state);
|
|
46258
45738
|
const name = assignment.left.name;
|
|
46259
45739
|
state.originalStateReferenceNames.delete(name);
|
|
46260
|
-
|
|
45740
|
+
state.mutableStateSourceNames.delete(name);
|
|
45741
|
+
if (isExpressionOriginalReducerStateReference(assignment.right, state)) {
|
|
45742
|
+
state.originalStateReferenceNames.add(name);
|
|
45743
|
+
state.mutableStateSourceNames.add(name);
|
|
45744
|
+
return;
|
|
45745
|
+
}
|
|
45746
|
+
if (isExpressionReachableFromOriginalReducerState(assignment.right, state)) state.mutableStateSourceNames.add(name);
|
|
46261
45747
|
};
|
|
46262
45748
|
const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, reportedNodes, options) => {
|
|
46263
45749
|
if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return;
|
|
@@ -46271,7 +45757,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
46271
45757
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
46272
45758
|
context.report({
|
|
46273
45759
|
node: options.crossFileConsumerCallSite,
|
|
46274
|
-
message: `${MESSAGE$
|
|
45760
|
+
message: `${MESSAGE$19} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
46275
45761
|
});
|
|
46276
45762
|
return;
|
|
46277
45763
|
}
|
|
@@ -46280,7 +45766,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
46280
45766
|
reportedNodes.add(mutation.node);
|
|
46281
45767
|
context.report({
|
|
46282
45768
|
node: mutation.node,
|
|
46283
|
-
message: MESSAGE$
|
|
45769
|
+
message: MESSAGE$19
|
|
46284
45770
|
});
|
|
46285
45771
|
}
|
|
46286
45772
|
};
|
|
@@ -46687,7 +46173,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
46687
46173
|
});
|
|
46688
46174
|
//#endregion
|
|
46689
46175
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
46690
|
-
const MESSAGE$
|
|
46176
|
+
const MESSAGE$18 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
46691
46177
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
46692
46178
|
"onKeyDown",
|
|
46693
46179
|
"onKeyUp",
|
|
@@ -46806,7 +46292,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
46806
46292
|
if (numeric === null) {
|
|
46807
46293
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
46808
46294
|
node: tabIndex,
|
|
46809
|
-
message: MESSAGE$
|
|
46295
|
+
message: MESSAGE$18
|
|
46810
46296
|
});
|
|
46811
46297
|
return;
|
|
46812
46298
|
}
|
|
@@ -46828,7 +46314,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
46828
46314
|
if (!roleAttribute) {
|
|
46829
46315
|
context.report({
|
|
46830
46316
|
node: tabIndex,
|
|
46831
|
-
message: MESSAGE$
|
|
46317
|
+
message: MESSAGE$18
|
|
46832
46318
|
});
|
|
46833
46319
|
return;
|
|
46834
46320
|
}
|
|
@@ -46842,7 +46328,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
46842
46328
|
}
|
|
46843
46329
|
context.report({
|
|
46844
46330
|
node: tabIndex,
|
|
46845
|
-
message: MESSAGE$
|
|
46331
|
+
message: MESSAGE$18
|
|
46846
46332
|
});
|
|
46847
46333
|
} };
|
|
46848
46334
|
}
|
|
@@ -49048,7 +48534,7 @@ const noRandomKey = defineRule({
|
|
|
49048
48534
|
});
|
|
49049
48535
|
//#endregion
|
|
49050
48536
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
49051
|
-
const MESSAGE$
|
|
48537
|
+
const MESSAGE$17 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
49052
48538
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
49053
48539
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
49054
48540
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -49074,13 +48560,13 @@ const noReactChildren = defineRule({
|
|
|
49074
48560
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
49075
48561
|
context.report({
|
|
49076
48562
|
node: calleeOuter,
|
|
49077
|
-
message: MESSAGE$
|
|
48563
|
+
message: MESSAGE$17
|
|
49078
48564
|
});
|
|
49079
48565
|
return;
|
|
49080
48566
|
}
|
|
49081
48567
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
49082
48568
|
node: calleeOuter,
|
|
49083
|
-
message: MESSAGE$
|
|
48569
|
+
message: MESSAGE$17
|
|
49084
48570
|
});
|
|
49085
48571
|
} })
|
|
49086
48572
|
});
|
|
@@ -49962,7 +49448,7 @@ const noRenderPropChildren = defineRule({
|
|
|
49962
49448
|
});
|
|
49963
49449
|
//#endregion
|
|
49964
49450
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
49965
|
-
const MESSAGE$
|
|
49451
|
+
const MESSAGE$16 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
49966
49452
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
49967
49453
|
const isUsedAsReturnValue = (parent) => {
|
|
49968
49454
|
if (!parent) return false;
|
|
@@ -49980,7 +49466,7 @@ const noRenderReturnValue = defineRule({
|
|
|
49980
49466
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
49981
49467
|
context.report({
|
|
49982
49468
|
node: node.callee,
|
|
49983
|
-
message: MESSAGE$
|
|
49469
|
+
message: MESSAGE$16
|
|
49984
49470
|
});
|
|
49985
49471
|
} })
|
|
49986
49472
|
});
|
|
@@ -51407,7 +50893,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
51407
50893
|
});
|
|
51408
50894
|
//#endregion
|
|
51409
50895
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
51410
|
-
const MESSAGE$
|
|
50896
|
+
const MESSAGE$15 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
51411
50897
|
const noSetState = defineRule({
|
|
51412
50898
|
id: "no-set-state",
|
|
51413
50899
|
title: "Local class state forbidden",
|
|
@@ -51422,7 +50908,7 @@ const noSetState = defineRule({
|
|
|
51422
50908
|
if (!getParentComponent(node)) return;
|
|
51423
50909
|
context.report({
|
|
51424
50910
|
node: node.callee,
|
|
51425
|
-
message: MESSAGE$
|
|
50911
|
+
message: MESSAGE$15
|
|
51426
50912
|
});
|
|
51427
50913
|
} })
|
|
51428
50914
|
});
|
|
@@ -51792,7 +51278,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
51792
51278
|
};
|
|
51793
51279
|
//#endregion
|
|
51794
51280
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
51795
|
-
const MESSAGE$
|
|
51281
|
+
const MESSAGE$14 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
|
|
51796
51282
|
const DEFAULT_HANDLERS = [
|
|
51797
51283
|
"onClick",
|
|
51798
51284
|
"onMouseDown",
|
|
@@ -51907,7 +51393,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51907
51393
|
if (!roleAttribute || !roleAttribute.value) {
|
|
51908
51394
|
context.report({
|
|
51909
51395
|
node: node.name,
|
|
51910
|
-
message: MESSAGE$
|
|
51396
|
+
message: MESSAGE$14
|
|
51911
51397
|
});
|
|
51912
51398
|
return;
|
|
51913
51399
|
}
|
|
@@ -51917,7 +51403,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51917
51403
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
51918
51404
|
context.report({
|
|
51919
51405
|
node: node.name,
|
|
51920
|
-
message: MESSAGE$
|
|
51406
|
+
message: MESSAGE$14
|
|
51921
51407
|
});
|
|
51922
51408
|
return;
|
|
51923
51409
|
}
|
|
@@ -51925,7 +51411,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51925
51411
|
if (!settings.allowExpressionValues) {
|
|
51926
51412
|
context.report({
|
|
51927
51413
|
node: node.name,
|
|
51928
|
-
message: MESSAGE$
|
|
51414
|
+
message: MESSAGE$14
|
|
51929
51415
|
});
|
|
51930
51416
|
return;
|
|
51931
51417
|
}
|
|
@@ -51933,7 +51419,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51933
51419
|
if (isStaticNullishExpression(expression)) {
|
|
51934
51420
|
context.report({
|
|
51935
51421
|
node: node.name,
|
|
51936
|
-
message: MESSAGE$
|
|
51422
|
+
message: MESSAGE$14
|
|
51937
51423
|
});
|
|
51938
51424
|
return;
|
|
51939
51425
|
}
|
|
@@ -51943,7 +51429,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51943
51429
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
51944
51430
|
context.report({
|
|
51945
51431
|
node: node.name,
|
|
51946
|
-
message: MESSAGE$
|
|
51432
|
+
message: MESSAGE$14
|
|
51947
51433
|
});
|
|
51948
51434
|
return;
|
|
51949
51435
|
}
|
|
@@ -51951,7 +51437,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51951
51437
|
}
|
|
51952
51438
|
context.report({
|
|
51953
51439
|
node: node.name,
|
|
51954
|
-
message: MESSAGE$
|
|
51440
|
+
message: MESSAGE$14
|
|
51955
51441
|
});
|
|
51956
51442
|
} };
|
|
51957
51443
|
}
|
|
@@ -52057,7 +51543,7 @@ const noStringRefs = defineRule({
|
|
|
52057
51543
|
});
|
|
52058
51544
|
//#endregion
|
|
52059
51545
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
52060
|
-
const MESSAGE$
|
|
51546
|
+
const MESSAGE$13 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
|
|
52061
51547
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
52062
51548
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
52063
51549
|
const noSyncXhr = defineRule({
|
|
@@ -52078,7 +51564,7 @@ const noSyncXhr = defineRule({
|
|
|
52078
51564
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
52079
51565
|
context.report({
|
|
52080
51566
|
node,
|
|
52081
|
-
message: MESSAGE$
|
|
51567
|
+
message: MESSAGE$13
|
|
52082
51568
|
});
|
|
52083
51569
|
};
|
|
52084
51570
|
return {
|
|
@@ -52103,7 +51589,7 @@ const noSyncXhr = defineRule({
|
|
|
52103
51589
|
});
|
|
52104
51590
|
//#endregion
|
|
52105
51591
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
52106
|
-
const MESSAGE$
|
|
51592
|
+
const MESSAGE$12 = "This value is `undefined` because function components have no `this`.";
|
|
52107
51593
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
52108
51594
|
let ancestor = node.parent;
|
|
52109
51595
|
while (ancestor) {
|
|
@@ -52191,7 +51677,7 @@ const noThisInSfc = defineRule({
|
|
|
52191
51677
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
52192
51678
|
context.report({
|
|
52193
51679
|
node,
|
|
52194
|
-
message: MESSAGE$
|
|
51680
|
+
message: MESSAGE$12
|
|
52195
51681
|
});
|
|
52196
51682
|
} };
|
|
52197
51683
|
}
|
|
@@ -54561,7 +54047,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
54561
54047
|
//#endregion
|
|
54562
54048
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
54563
54049
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
54564
|
-
const MESSAGE$
|
|
54050
|
+
const MESSAGE$11 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
54565
54051
|
const resolveSettings$7 = (settings) => {
|
|
54566
54052
|
const reactDoctor = settings?.["react-doctor"];
|
|
54567
54053
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -54595,7 +54081,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
54595
54081
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
54596
54082
|
context.report({
|
|
54597
54083
|
node: node.callee,
|
|
54598
|
-
message: MESSAGE$
|
|
54084
|
+
message: MESSAGE$11
|
|
54599
54085
|
});
|
|
54600
54086
|
} };
|
|
54601
54087
|
}
|
|
@@ -56395,7 +55881,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
56395
55881
|
});
|
|
56396
55882
|
//#endregion
|
|
56397
55883
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
56398
|
-
const MESSAGE$
|
|
55884
|
+
const MESSAGE$10 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
|
|
56399
55885
|
const preactPreferOndblclick = defineRule({
|
|
56400
55886
|
id: "preact-prefer-ondblclick",
|
|
56401
55887
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -56410,7 +55896,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
56410
55896
|
if (!onDoubleClickAttribute) return;
|
|
56411
55897
|
context.report({
|
|
56412
55898
|
node: onDoubleClickAttribute,
|
|
56413
|
-
message: MESSAGE$
|
|
55899
|
+
message: MESSAGE$10
|
|
56414
55900
|
});
|
|
56415
55901
|
} })
|
|
56416
55902
|
});
|
|
@@ -56650,7 +56136,7 @@ const preferExplicitVariants = defineRule({
|
|
|
56650
56136
|
});
|
|
56651
56137
|
//#endregion
|
|
56652
56138
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
56653
|
-
const MESSAGE$
|
|
56139
|
+
const MESSAGE$9 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
|
|
56654
56140
|
const resolveSettings$4 = (settings) => {
|
|
56655
56141
|
const reactDoctor = settings?.["react-doctor"];
|
|
56656
56142
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -56689,7 +56175,7 @@ const preferFunctionComponent = defineRule({
|
|
|
56689
56175
|
const reportNode = node.id ?? node;
|
|
56690
56176
|
context.report({
|
|
56691
56177
|
node: reportNode,
|
|
56692
|
-
message: MESSAGE$
|
|
56178
|
+
message: MESSAGE$9
|
|
56693
56179
|
});
|
|
56694
56180
|
};
|
|
56695
56181
|
return {
|
|
@@ -58890,7 +58376,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
58890
58376
|
});
|
|
58891
58377
|
//#endregion
|
|
58892
58378
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
58893
|
-
const MESSAGE$
|
|
58379
|
+
const MESSAGE$8 = "This JSX crashes because `React` isn't in scope.";
|
|
58894
58380
|
const reactInJsxScope = defineRule({
|
|
58895
58381
|
id: "react-in-jsx-scope",
|
|
58896
58382
|
title: "React not in scope for JSX",
|
|
@@ -58902,21 +58388,21 @@ const reactInJsxScope = defineRule({
|
|
|
58902
58388
|
if (findVariableInitializer(node, "React")) return;
|
|
58903
58389
|
context.report({
|
|
58904
58390
|
node: node.name,
|
|
58905
|
-
message: MESSAGE$
|
|
58391
|
+
message: MESSAGE$8
|
|
58906
58392
|
});
|
|
58907
58393
|
},
|
|
58908
58394
|
JSXFragment(node) {
|
|
58909
58395
|
if (findVariableInitializer(node, "React")) return;
|
|
58910
58396
|
context.report({
|
|
58911
58397
|
node: node.openingFragment,
|
|
58912
|
-
message: MESSAGE$
|
|
58398
|
+
message: MESSAGE$8
|
|
58913
58399
|
});
|
|
58914
58400
|
}
|
|
58915
58401
|
})
|
|
58916
58402
|
});
|
|
58917
58403
|
//#endregion
|
|
58918
58404
|
//#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
|
|
58919
|
-
const MESSAGE$
|
|
58405
|
+
const MESSAGE$7 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
|
|
58920
58406
|
const REACT_MARKDOWN_MODULE = "react-markdown";
|
|
58921
58407
|
const REHYPE_RAW_MODULE = "rehype-raw";
|
|
58922
58408
|
const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
|
|
@@ -59068,7 +58554,7 @@ const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
|
59068
58554
|
if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
|
|
59069
58555
|
context.report({
|
|
59070
58556
|
node,
|
|
59071
|
-
message: MESSAGE$
|
|
58557
|
+
message: MESSAGE$7
|
|
59072
58558
|
});
|
|
59073
58559
|
} }))
|
|
59074
58560
|
});
|
|
@@ -59124,7 +58610,7 @@ const inlineUseSelectorFunction = (callNode, aliases) => {
|
|
|
59124
58610
|
};
|
|
59125
58611
|
//#endregion
|
|
59126
58612
|
//#region src/plugin/rules/state-and-effects/redux-useselector-inline-derivation.ts
|
|
59127
|
-
const ALLOCATING_ARRAY_METHODS
|
|
58613
|
+
const ALLOCATING_ARRAY_METHODS = new Set([
|
|
59128
58614
|
"filter",
|
|
59129
58615
|
"map",
|
|
59130
58616
|
"flatMap",
|
|
@@ -59160,7 +58646,7 @@ const getAllocatingCallSiteDescription = (expression) => {
|
|
|
59160
58646
|
method: methodName
|
|
59161
58647
|
};
|
|
59162
58648
|
}
|
|
59163
|
-
if (ALLOCATING_ARRAY_METHODS
|
|
58649
|
+
if (ALLOCATING_ARRAY_METHODS.has(methodName)) return {
|
|
59164
58650
|
kind: "method",
|
|
59165
58651
|
method: methodName
|
|
59166
58652
|
};
|
|
@@ -59223,7 +58709,7 @@ const reduxUseselectorInlineDerivation = defineRule({
|
|
|
59223
58709
|
});
|
|
59224
58710
|
//#endregion
|
|
59225
58711
|
//#region src/plugin/rules/state-and-effects/redux-useselector-returns-new-collection.ts
|
|
59226
|
-
const MESSAGE$
|
|
58712
|
+
const MESSAGE$6 = "Your component re-renders on every dispatched action when useSelector returns a new object or array.";
|
|
59227
58713
|
const isConciseBodyReturningCollection = (functionNode) => {
|
|
59228
58714
|
if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression")) return false;
|
|
59229
58715
|
const rawBody = functionNode.body;
|
|
@@ -59258,7 +58744,7 @@ const reduxUseselectorReturnsNewCollection = defineRule({
|
|
|
59258
58744
|
if (!isConciseBodyReturningCollection(selectorArgument)) return;
|
|
59259
58745
|
context.report({
|
|
59260
58746
|
node,
|
|
59261
|
-
message: MESSAGE$
|
|
58747
|
+
message: MESSAGE$6
|
|
59262
58748
|
});
|
|
59263
58749
|
}
|
|
59264
58750
|
};
|
|
@@ -60939,7 +60425,7 @@ const functionBodyHasReturnWithValue = (functionNode) => {
|
|
|
60939
60425
|
//#endregion
|
|
60940
60426
|
//#region src/plugin/rules/react-builtins/require-render-return.ts
|
|
60941
60427
|
const RENDER_METHOD_NAME = "render";
|
|
60942
|
-
const MESSAGE$
|
|
60428
|
+
const MESSAGE$5 = "Your users see nothing because this `render` method returns nothing.";
|
|
60943
60429
|
const isStaticRenderKey = (key) => {
|
|
60944
60430
|
if (isNodeOfType(key, "Identifier")) return key.name === RENDER_METHOD_NAME;
|
|
60945
60431
|
if (isNodeOfType(key, "Literal")) return key.value === RENDER_METHOD_NAME;
|
|
@@ -60999,7 +60485,7 @@ const requireRenderReturn = defineRule({
|
|
|
60999
60485
|
if (functionBodyHasReturnWithValue(functionNode)) return;
|
|
61000
60486
|
context.report({
|
|
61001
60487
|
node: host.reportNode,
|
|
61002
|
-
message: MESSAGE$
|
|
60488
|
+
message: MESSAGE$5
|
|
61003
60489
|
});
|
|
61004
60490
|
};
|
|
61005
60491
|
return {
|
|
@@ -62248,85 +61734,6 @@ const isSetterCalledDuringRender = (root, setterName) => {
|
|
|
62248
61734
|
return found;
|
|
62249
61735
|
};
|
|
62250
61736
|
//#endregion
|
|
62251
|
-
//#region src/plugin/utils/is-descendant-without-function-boundary.ts
|
|
62252
|
-
const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
|
|
62253
|
-
let current = descendant;
|
|
62254
|
-
while (current && current !== ancestor) {
|
|
62255
|
-
if (current !== descendant && isFunctionLike$1(current)) return false;
|
|
62256
|
-
current = current.parent;
|
|
62257
|
-
}
|
|
62258
|
-
return current === ancestor;
|
|
62259
|
-
};
|
|
62260
|
-
//#endregion
|
|
62261
|
-
//#region src/plugin/utils/can-execute-before-async-suspension.ts
|
|
62262
|
-
const collectSuspensionNodes = (functionNode, options) => {
|
|
62263
|
-
const suspensionNodes = new Set(options.suspensionNodes);
|
|
62264
|
-
walkAst(functionNode, (node) => {
|
|
62265
|
-
if (node !== functionNode && isFunctionLike$1(node)) return false;
|
|
62266
|
-
if (!options.suspensionNodes && isNodeOfType(node, "AwaitExpression") || isNodeOfType(node, "ForOfStatement") && node.await) suspensionNodes.add(node);
|
|
62267
|
-
});
|
|
62268
|
-
return suspensionNodes;
|
|
62269
|
-
};
|
|
62270
|
-
const isInsideForAwaitPostSuspensionRegion = (node, functionNode) => {
|
|
62271
|
-
let current = node;
|
|
62272
|
-
while (current && current !== functionNode) {
|
|
62273
|
-
const parent = current.parent;
|
|
62274
|
-
if (parent && isNodeOfType(parent, "ForOfStatement") && parent.await && (parent.left === current || parent.body === current)) return true;
|
|
62275
|
-
current = parent;
|
|
62276
|
-
}
|
|
62277
|
-
return false;
|
|
62278
|
-
};
|
|
62279
|
-
const suspensionBlockNode = (suspensionNode) => isNodeOfType(suspensionNode, "ForOfStatement") ? suspensionNode.right : suspensionNode;
|
|
62280
|
-
const doesSuspensionOccurBeforeNode = (suspensionNode, node) => {
|
|
62281
|
-
if (isNodeOfType(suspensionNode, "AwaitExpression")) {
|
|
62282
|
-
if (isDescendantWithoutFunctionBoundary(node, suspensionNode.argument)) return false;
|
|
62283
|
-
} else if (isNodeOfType(suspensionNode, "ForOfStatement") && isDescendantWithoutFunctionBoundary(node, suspensionNode.right)) return false;
|
|
62284
|
-
if (isDescendantWithoutFunctionBoundary(suspensionNode, node)) return true;
|
|
62285
|
-
const suspensionStart = getRangeStart(suspensionBlockNode(suspensionNode));
|
|
62286
|
-
const nodeStart = getRangeStart(node);
|
|
62287
|
-
return suspensionStart !== null && nodeStart !== null && suspensionStart < nodeStart;
|
|
62288
|
-
};
|
|
62289
|
-
const findExecutionNodeWithinFunction = (node, functionNode, context) => {
|
|
62290
|
-
let executionNode = node;
|
|
62291
|
-
let executionOwner = context.cfg.enclosingFunction(executionNode);
|
|
62292
|
-
while (executionOwner && executionOwner !== functionNode) {
|
|
62293
|
-
if (!isFunctionLike$1(executionOwner) || executionOwner.async || executionOwner.generator) return null;
|
|
62294
|
-
const callExpression = findImmediatelyInvokedCallExpression(executionOwner);
|
|
62295
|
-
if (!callExpression) return null;
|
|
62296
|
-
executionNode = callExpression;
|
|
62297
|
-
executionOwner = context.cfg.enclosingFunction(callExpression);
|
|
62298
|
-
}
|
|
62299
|
-
return executionOwner === functionNode ? executionNode : null;
|
|
62300
|
-
};
|
|
62301
|
-
const canExecuteBeforeAsyncSuspension = (node, functionNode, context, options = {}) => {
|
|
62302
|
-
if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, context);
|
|
62303
|
-
const executionNode = findExecutionNodeWithinFunction(node, functionNode, context);
|
|
62304
|
-
if (!executionNode || isInsideForAwaitPostSuspensionRegion(executionNode, functionNode)) return false;
|
|
62305
|
-
const functionCfg = context.cfg.cfgFor(functionNode);
|
|
62306
|
-
const targetBlock = functionCfg?.blockOf(executionNode);
|
|
62307
|
-
if (!functionCfg || !targetBlock) return false;
|
|
62308
|
-
const suspensionsByBlock = /* @__PURE__ */ new Map();
|
|
62309
|
-
for (const suspensionNode of collectSuspensionNodes(functionNode, options)) {
|
|
62310
|
-
const suspensionBlock = functionCfg.blockOf(suspensionBlockNode(suspensionNode));
|
|
62311
|
-
if (!suspensionBlock) continue;
|
|
62312
|
-
const blockSuspensions = suspensionsByBlock.get(suspensionBlock) ?? [];
|
|
62313
|
-
blockSuspensions.push(suspensionNode);
|
|
62314
|
-
suspensionsByBlock.set(suspensionBlock, blockSuspensions);
|
|
62315
|
-
}
|
|
62316
|
-
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
62317
|
-
const pendingBlocks = [functionCfg.entry];
|
|
62318
|
-
while (pendingBlocks.length > 0) {
|
|
62319
|
-
const block = pendingBlocks.pop();
|
|
62320
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
62321
|
-
visitedBlocks.add(block);
|
|
62322
|
-
const blockSuspensions = suspensionsByBlock.get(block) ?? [];
|
|
62323
|
-
if (block === targetBlock) return !blockSuspensions.some((suspensionNode) => doesSuspensionOccurBeforeNode(suspensionNode, executionNode));
|
|
62324
|
-
if (blockSuspensions.length > 0) continue;
|
|
62325
|
-
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
62326
|
-
}
|
|
62327
|
-
return false;
|
|
62328
|
-
};
|
|
62329
|
-
//#endregion
|
|
62330
61737
|
//#region src/plugin/rules/state-and-effects/utils/create-external-location-invalidation-checker.ts
|
|
62331
61738
|
const HISTORY_LOCATION_MUTATION_METHOD_NAMES = new Set(["pushState", "replaceState"]);
|
|
62332
61739
|
const AGGREGATE_MUTATION_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
|
|
@@ -62519,6 +61926,14 @@ const buildLocationInvalidationIndex = (componentBody, componentFunction, contex
|
|
|
62519
61926
|
});
|
|
62520
61927
|
return index;
|
|
62521
61928
|
};
|
|
61929
|
+
const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
|
|
61930
|
+
let current = descendant;
|
|
61931
|
+
while (current && current !== ancestor) {
|
|
61932
|
+
if (current !== descendant && isFunctionLike$1(current)) return false;
|
|
61933
|
+
current = current.parent;
|
|
61934
|
+
}
|
|
61935
|
+
return current === ancestor;
|
|
61936
|
+
};
|
|
62522
61937
|
const areInMutuallyExclusiveConditionalBranches = (firstNode, secondNode) => {
|
|
62523
61938
|
const firstBranches = /* @__PURE__ */ new Map();
|
|
62524
61939
|
let current = firstNode;
|
|
@@ -62830,7 +62245,39 @@ const canNodeReachNormalFunctionExit = (node, functionNode, index) => {
|
|
|
62830
62245
|
}
|
|
62831
62246
|
return false;
|
|
62832
62247
|
};
|
|
62833
|
-
const
|
|
62248
|
+
const canExecuteBeforeAsyncSuspension = (node, functionNode, index) => {
|
|
62249
|
+
if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, index.context);
|
|
62250
|
+
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
62251
|
+
const targetBlock = functionCfg?.blockOf(node);
|
|
62252
|
+
if (!functionCfg || !targetBlock) return false;
|
|
62253
|
+
const awaitsByBlock = /* @__PURE__ */ new Map();
|
|
62254
|
+
for (const awaitExpression of index.awaitExpressionsByOwner.get(functionNode) ?? []) {
|
|
62255
|
+
const awaitBlock = functionCfg.blockOf(awaitExpression);
|
|
62256
|
+
if (!awaitBlock) continue;
|
|
62257
|
+
const blockAwaits = awaitsByBlock.get(awaitBlock) ?? [];
|
|
62258
|
+
blockAwaits.push(awaitExpression);
|
|
62259
|
+
awaitsByBlock.set(awaitBlock, blockAwaits);
|
|
62260
|
+
}
|
|
62261
|
+
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
62262
|
+
const pendingBlocks = [functionCfg.entry];
|
|
62263
|
+
const targetStart = getRangeStart(node);
|
|
62264
|
+
while (pendingBlocks.length > 0) {
|
|
62265
|
+
const block = pendingBlocks.pop();
|
|
62266
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
62267
|
+
visitedBlocks.add(block);
|
|
62268
|
+
const blockAwaits = awaitsByBlock.get(block) ?? [];
|
|
62269
|
+
if (block === targetBlock) return !blockAwaits.some((awaitExpression) => {
|
|
62270
|
+
if (isNodeOfType(awaitExpression, "AwaitExpression") && isDescendantWithoutFunctionBoundary(node, awaitExpression.argument)) return false;
|
|
62271
|
+
if (isDescendantWithoutFunctionBoundary(awaitExpression, node)) return true;
|
|
62272
|
+
const awaitStart = getRangeStart(awaitExpression);
|
|
62273
|
+
return awaitStart !== null && targetStart !== null && awaitStart < targetStart;
|
|
62274
|
+
});
|
|
62275
|
+
if (blockAwaits.length > 0) continue;
|
|
62276
|
+
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
62277
|
+
}
|
|
62278
|
+
return false;
|
|
62279
|
+
};
|
|
62280
|
+
const canReactBatchMutationAfterExecution = (executionNode, mutationNode, owner, index) => (isExclusiveIntrinsicReactEventHandler(owner, index) || index.effectCallbacks.has(owner)) && canNodeReachNode(executionNode, mutationNode, index) && canExecuteBeforeAsyncSuspension(mutationNode, owner, index) && canNodeReachNormalFunctionExit(mutationNode, owner, index);
|
|
62834
62281
|
const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFunctions, cycleAffectedFunctions) => {
|
|
62835
62282
|
const cachedResult = index.synchronousMutationResultByFunction.get(functionNode);
|
|
62836
62283
|
if (cachedResult !== void 0) return cachedResult;
|
|
@@ -62844,7 +62291,7 @@ const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFun
|
|
|
62844
62291
|
return false;
|
|
62845
62292
|
}
|
|
62846
62293
|
visitingFunctions.add(functionNode);
|
|
62847
|
-
const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index
|
|
62294
|
+
const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index) && canNodeReachNormalFunctionExit(mutationExecution, functionNode, index));
|
|
62848
62295
|
visitingFunctions.delete(functionNode);
|
|
62849
62296
|
if (doesMutateSynchronously || !cycleAffectedFunctions.has(functionNode)) index.synchronousMutationResultByFunction.set(functionNode, doesMutateSynchronously);
|
|
62850
62297
|
return doesMutateSynchronously;
|
|
@@ -62871,20 +62318,20 @@ const functionMustSynchronouslyRemoveLocationListener = (functionNode, registrat
|
|
|
62871
62318
|
for (const removal of index.listenerRemovals) {
|
|
62872
62319
|
if (!isDefinitelyMatchingLocationListenerRemoval(registration, removal)) continue;
|
|
62873
62320
|
if (index.context.cfg.enclosingFunction(removal.callExpression) !== functionNode) continue;
|
|
62874
|
-
if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index
|
|
62321
|
+
if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.push(removal.callExpression);
|
|
62875
62322
|
}
|
|
62876
62323
|
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
62877
62324
|
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
62878
|
-
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index
|
|
62325
|
+
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, nextVisitingFunctions)) removalExecutions.push(expression);
|
|
62879
62326
|
}
|
|
62880
62327
|
return doNodesCoverEveryPathFromFunctionEntry(functionNode, removalExecutions, index.context);
|
|
62881
62328
|
};
|
|
62882
62329
|
const collectSynchronousLocationListenerRemovalExecutions = (functionNode, registration, index) => {
|
|
62883
62330
|
const removalExecutions = /* @__PURE__ */ new Set();
|
|
62884
|
-
for (const removal of index.listenerRemovals) if (isDefinitelyMatchingLocationListenerRemoval(registration, removal) && index.context.cfg.enclosingFunction(removal.callExpression) === functionNode && canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index
|
|
62331
|
+
for (const removal of index.listenerRemovals) if (isDefinitelyMatchingLocationListenerRemoval(registration, removal) && index.context.cfg.enclosingFunction(removal.callExpression) === functionNode && canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.add(removal.callExpression);
|
|
62885
62332
|
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
62886
62333
|
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
62887
|
-
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index
|
|
62334
|
+
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, /* @__PURE__ */ new Set())) removalExecutions.add(expression);
|
|
62888
62335
|
}
|
|
62889
62336
|
return removalExecutions;
|
|
62890
62337
|
};
|
|
@@ -69880,7 +69327,7 @@ const rulesOfHooks = defineRule({
|
|
|
69880
69327
|
});
|
|
69881
69328
|
//#endregion
|
|
69882
69329
|
//#region src/plugin/rules/a11y/scope.ts
|
|
69883
|
-
const MESSAGE$
|
|
69330
|
+
const MESSAGE$4 = "The `scope` attribute only works on `<th>` cells, so screen readers get no table-header help from it here.";
|
|
69884
69331
|
const scope = defineRule({
|
|
69885
69332
|
id: "scope",
|
|
69886
69333
|
title: "scope attribute on non-th element",
|
|
@@ -69895,7 +69342,7 @@ const scope = defineRule({
|
|
|
69895
69342
|
if (!HTML_TAGS.has(tag)) return;
|
|
69896
69343
|
if (tag !== "th") context.report({
|
|
69897
69344
|
node: scopeAttribute,
|
|
69898
|
-
message: MESSAGE$
|
|
69345
|
+
message: MESSAGE$4
|
|
69899
69346
|
});
|
|
69900
69347
|
} })
|
|
69901
69348
|
});
|
|
@@ -69912,7 +69359,7 @@ const secretInFallback = defineRule({
|
|
|
69912
69359
|
});
|
|
69913
69360
|
//#endregion
|
|
69914
69361
|
//#region src/plugin/rules/react-builtins/self-closing-comp.ts
|
|
69915
|
-
const MESSAGE$
|
|
69362
|
+
const MESSAGE$3 = "This tag has no children, so the closing tag adds noise without changing output.";
|
|
69916
69363
|
const resolveSettings$2 = (settings) => {
|
|
69917
69364
|
const reactDoctor = settings?.["react-doctor"];
|
|
69918
69365
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.selfClosingComp ?? {} : {};
|
|
@@ -69951,7 +69398,7 @@ const selfClosingComp = defineRule({
|
|
|
69951
69398
|
if (isHtmlTag && !settings.html) return;
|
|
69952
69399
|
context.report({
|
|
69953
69400
|
node: node.openingElement,
|
|
69954
|
-
message: MESSAGE$
|
|
69401
|
+
message: MESSAGE$3
|
|
69955
69402
|
});
|
|
69956
69403
|
} };
|
|
69957
69404
|
}
|
|
@@ -71335,7 +70782,7 @@ const collectJsxRuntimeImports = (program) => {
|
|
|
71335
70782
|
};
|
|
71336
70783
|
//#endregion
|
|
71337
70784
|
//#region src/plugin/rules/react-builtins/style-prop-object.ts
|
|
71338
|
-
const MESSAGE$
|
|
70785
|
+
const MESSAGE$2 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
|
|
71339
70786
|
const resolveSettings = (settings) => {
|
|
71340
70787
|
const reactDoctor = settings?.["react-doctor"];
|
|
71341
70788
|
return { allow: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.stylePropObject ?? {} : {}).allow ?? [] };
|
|
@@ -71442,7 +70889,7 @@ const stylePropObject = defineRule({
|
|
|
71442
70889
|
if (isNodeOfType(value, "Literal") && typeof value.value === "string") {
|
|
71443
70890
|
context.report({
|
|
71444
70891
|
node: attribute,
|
|
71445
|
-
message: MESSAGE$
|
|
70892
|
+
message: MESSAGE$2
|
|
71446
70893
|
});
|
|
71447
70894
|
return;
|
|
71448
70895
|
}
|
|
@@ -71450,7 +70897,7 @@ const stylePropObject = defineRule({
|
|
|
71450
70897
|
const innerExpression = value.expression;
|
|
71451
70898
|
if (innerExpression && innerExpression.type !== "JSXEmptyExpression" && isInvalidStyleExpression(innerExpression)) context.report({
|
|
71452
70899
|
node: attribute,
|
|
71453
|
-
message: MESSAGE$
|
|
70900
|
+
message: MESSAGE$2
|
|
71454
70901
|
});
|
|
71455
70902
|
}
|
|
71456
70903
|
}
|
|
@@ -71473,7 +70920,7 @@ const stylePropObject = defineRule({
|
|
|
71473
70920
|
if (!(isNodeOfType(key, "Identifier") && key.name === "style" || isNodeOfType(key, "Literal") && key.value === "style")) continue;
|
|
71474
70921
|
if (isInvalidStyleExpression(property.value)) context.report({
|
|
71475
70922
|
node: property,
|
|
71476
|
-
message: MESSAGE$
|
|
70923
|
+
message: MESSAGE$2
|
|
71477
70924
|
});
|
|
71478
70925
|
}
|
|
71479
70926
|
}
|
|
@@ -71809,7 +71256,7 @@ const svgFilterClickjackingRisk = defineRule({
|
|
|
71809
71256
|
});
|
|
71810
71257
|
//#endregion
|
|
71811
71258
|
//#region src/plugin/rules/a11y/tabindex-no-positive.ts
|
|
71812
|
-
const MESSAGE$
|
|
71259
|
+
const MESSAGE$1 = "Keyboard users get jumped out of the normal order by a positive `tabIndex`, so use `0` or `-1`.";
|
|
71813
71260
|
const tabindexNoPositive = defineRule({
|
|
71814
71261
|
id: "tabindex-no-positive",
|
|
71815
71262
|
title: "Positive tabindex value",
|
|
@@ -71829,7 +71276,7 @@ const tabindexNoPositive = defineRule({
|
|
|
71829
71276
|
} else if (tabIndex.value && isNodeOfType(tabIndex.value, "JSXExpressionContainer")) numericValue = parseJsxValue(tabIndex.value);
|
|
71830
71277
|
if (numericValue !== null && numericValue > 0) context.report({
|
|
71831
71278
|
node: tabIndex,
|
|
71832
|
-
message: MESSAGE$
|
|
71279
|
+
message: MESSAGE$1
|
|
71833
71280
|
});
|
|
71834
71281
|
} })
|
|
71835
71282
|
});
|
|
@@ -72744,7 +72191,7 @@ const REACT_DEPENDENCY_ARRAY_HOOK_NAMES = new Set([
|
|
|
72744
72191
|
"useLayoutEffect",
|
|
72745
72192
|
"useMemo"
|
|
72746
72193
|
]);
|
|
72747
|
-
const MESSAGE
|
|
72194
|
+
const MESSAGE = "Read this Valtio value from the useSnapshot result during render. Keep proxy reads for callbacks so render uses the tracked, consistent snapshot.";
|
|
72748
72195
|
const isValtioImportSymbol = (symbol) => {
|
|
72749
72196
|
if (symbol.kind !== "import") return false;
|
|
72750
72197
|
const importDeclaration = symbol.declarationNode.parent;
|
|
@@ -72969,7 +72416,7 @@ const valtioNoProxyReadInRender = defineRule({
|
|
|
72969
72416
|
reportedExpressions.add(readExpression);
|
|
72970
72417
|
context.report({
|
|
72971
72418
|
node: readExpression,
|
|
72972
|
-
message: MESSAGE
|
|
72419
|
+
message: MESSAGE
|
|
72973
72420
|
});
|
|
72974
72421
|
}
|
|
72975
72422
|
}
|
|
@@ -73707,1156 +73154,6 @@ const zodV4PreferTopLevelStringFormats = defineRule({
|
|
|
73707
73154
|
})
|
|
73708
73155
|
});
|
|
73709
73156
|
//#endregion
|
|
73710
|
-
//#region src/plugin/utils/function-returns-collection-at-path.ts
|
|
73711
|
-
const resolveImmutableInitializer = (expression, scopes, visitedSymbolIds) => {
|
|
73712
|
-
const candidate = stripParenExpression(expression);
|
|
73713
|
-
if (!isNodeOfType(candidate, "Identifier")) return candidate;
|
|
73714
|
-
const symbol = scopes.symbolFor(candidate);
|
|
73715
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return candidate;
|
|
73716
|
-
visitedSymbolIds.add(symbol.id);
|
|
73717
|
-
return resolveImmutableInitializer(symbol.initializer, scopes, visitedSymbolIds);
|
|
73718
|
-
};
|
|
73719
|
-
const expressionAtPropertyPath = (expression, propertyPath, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73720
|
-
const candidate = resolveImmutableInitializer(expression, scopes, visitedSymbolIds);
|
|
73721
|
-
const [propertyName, ...remainingPath] = propertyPath;
|
|
73722
|
-
if (!propertyName) return candidate;
|
|
73723
|
-
if (!isNodeOfType(candidate, "ObjectExpression")) return null;
|
|
73724
|
-
for (const property of candidate.properties) {
|
|
73725
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
73726
|
-
if (getStaticPropertyKeyName(property) !== propertyName) continue;
|
|
73727
|
-
return expressionAtPropertyPath(property.value, remainingPath, scopes, visitedSymbolIds);
|
|
73728
|
-
}
|
|
73729
|
-
return null;
|
|
73730
|
-
};
|
|
73731
|
-
const isGlobalConstructor = (expression, constructorNames, scopes) => {
|
|
73732
|
-
const candidate = stripParenExpression(expression);
|
|
73733
|
-
return isNodeOfType(candidate, "Identifier") && constructorNames.has(candidate.name) && scopes.isGlobalReference(candidate);
|
|
73734
|
-
};
|
|
73735
|
-
const isCollectionExpression = (expression, collectionKind, scopes) => {
|
|
73736
|
-
const candidate = stripParenExpression(expression);
|
|
73737
|
-
if (collectionKind === "array" && isNodeOfType(candidate, "ArrayExpression")) return true;
|
|
73738
|
-
if (!isNodeOfType(candidate, "NewExpression")) return false;
|
|
73739
|
-
return isGlobalConstructor(candidate.callee, collectionKind === "array" ? new Set(["Array"]) : new Set(["Map", "Set"]), scopes);
|
|
73740
|
-
};
|
|
73741
|
-
const functionReturnsCollectionAtPath = ({ collectionKind, functionNode, propertyPath, scopes }) => {
|
|
73742
|
-
if (!isFunctionLike$1(functionNode)) return false;
|
|
73743
|
-
const returnedExpressions = isNodeOfType(functionNode.body, "BlockStatement") ? collectFunctionReturnStatements(functionNode).flatMap((returnStatement) => returnStatement.argument ? [returnStatement.argument] : []) : [functionNode.body];
|
|
73744
|
-
return returnedExpressions.length > 0 && returnedExpressions.every((returnedExpression) => {
|
|
73745
|
-
const collectionExpression = expressionAtPropertyPath(returnedExpression, propertyPath, scopes);
|
|
73746
|
-
return Boolean(collectionExpression && isCollectionExpression(collectionExpression, collectionKind, scopes));
|
|
73747
|
-
});
|
|
73748
|
-
};
|
|
73749
|
-
//#endregion
|
|
73750
|
-
//#region src/plugin/utils/resolve-zustand-api.ts
|
|
73751
|
-
const ZUSTAND_APIS_BY_MODULE = new Map([
|
|
73752
|
-
["zustand", new Set([
|
|
73753
|
-
"create",
|
|
73754
|
-
"createStore",
|
|
73755
|
-
"useStore"
|
|
73756
|
-
])],
|
|
73757
|
-
["zustand/vanilla", new Set(["createStore"])],
|
|
73758
|
-
["zustand/react", new Set(["useStore"])],
|
|
73759
|
-
["zustand/traditional", new Set(["createWithEqualityFn", "useStoreWithEqualityFn"])],
|
|
73760
|
-
["zustand/shallow", new Set(["shallow", "useShallow"])],
|
|
73761
|
-
["zustand/react/shallow", new Set(["useShallow"])],
|
|
73762
|
-
["zustand/middleware", new Set([
|
|
73763
|
-
"combine",
|
|
73764
|
-
"createJSONStorage",
|
|
73765
|
-
"devtools",
|
|
73766
|
-
"persist",
|
|
73767
|
-
"redux",
|
|
73768
|
-
"subscribeWithSelector"
|
|
73769
|
-
])],
|
|
73770
|
-
["zustand/middleware/immer", new Set(["immer"])]
|
|
73771
|
-
]);
|
|
73772
|
-
const STATE_CREATOR_MIDDLEWARE_ARGUMENT_INDEX = new Map([
|
|
73773
|
-
["combine", 1],
|
|
73774
|
-
["devtools", 0],
|
|
73775
|
-
["immer", 0],
|
|
73776
|
-
["persist", 0],
|
|
73777
|
-
["subscribeWithSelector", 0]
|
|
73778
|
-
]);
|
|
73779
|
-
const toZustandApiName = (value) => {
|
|
73780
|
-
switch (value) {
|
|
73781
|
-
case "combine":
|
|
73782
|
-
case "create":
|
|
73783
|
-
case "createJSONStorage":
|
|
73784
|
-
case "createStore":
|
|
73785
|
-
case "createWithEqualityFn":
|
|
73786
|
-
case "devtools":
|
|
73787
|
-
case "immer":
|
|
73788
|
-
case "persist":
|
|
73789
|
-
case "redux":
|
|
73790
|
-
case "shallow":
|
|
73791
|
-
case "subscribeWithSelector":
|
|
73792
|
-
case "useShallow":
|
|
73793
|
-
case "useStore":
|
|
73794
|
-
case "useStoreWithEqualityFn": return value;
|
|
73795
|
-
default: return null;
|
|
73796
|
-
}
|
|
73797
|
-
};
|
|
73798
|
-
const getImportSource = (symbol) => {
|
|
73799
|
-
const declaration = getImportDeclarationForSymbol(symbol);
|
|
73800
|
-
if (!declaration || isTypeOnlyImport(declaration) || isNodeOfType(symbol.declarationNode, "ImportSpecifier") && symbol.declarationNode.importKind === "type") return null;
|
|
73801
|
-
return typeof declaration.source.value === "string" ? declaration.source.value : null;
|
|
73802
|
-
};
|
|
73803
|
-
const bindingFromImportSymbol = (symbol) => {
|
|
73804
|
-
const moduleSource = getImportSource(symbol);
|
|
73805
|
-
const supportedApiNames = moduleSource ? ZUSTAND_APIS_BY_MODULE.get(moduleSource) : null;
|
|
73806
|
-
if (!moduleSource || !supportedApiNames) return null;
|
|
73807
|
-
if (moduleSource === "zustand" && isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return {
|
|
73808
|
-
apiName: "create",
|
|
73809
|
-
moduleSource
|
|
73810
|
-
};
|
|
73811
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
73812
|
-
const apiName = importedName ? toZustandApiName(importedName) : null;
|
|
73813
|
-
return apiName && supportedApiNames.has(apiName) ? {
|
|
73814
|
-
apiName,
|
|
73815
|
-
moduleSource
|
|
73816
|
-
} : null;
|
|
73817
|
-
};
|
|
73818
|
-
const namespaceImportSource = (expression, scopes) => {
|
|
73819
|
-
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
73820
|
-
const symbol = resolveConstIdentifierAlias(expression, scopes);
|
|
73821
|
-
if (!symbol || !isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return null;
|
|
73822
|
-
return getImportSource(symbol);
|
|
73823
|
-
};
|
|
73824
|
-
const resolveZustandApiBinding = (expression, scopes) => {
|
|
73825
|
-
const candidate = stripParenExpression(expression);
|
|
73826
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
73827
|
-
const symbol = resolveConstIdentifierAlias(candidate, scopes);
|
|
73828
|
-
if (!symbol) return null;
|
|
73829
|
-
if (symbol.kind === "import") return bindingFromImportSymbol(symbol);
|
|
73830
|
-
if (symbol.kind !== "const" || !symbol.initializer) return null;
|
|
73831
|
-
return resolveZustandApiBinding(symbol.initializer, scopes);
|
|
73832
|
-
}
|
|
73833
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
73834
|
-
const propertyName = getStaticPropertyName(candidate);
|
|
73835
|
-
const moduleSource = namespaceImportSource(stripParenExpression(candidate.object), scopes);
|
|
73836
|
-
const supportedApiNames = moduleSource ? ZUSTAND_APIS_BY_MODULE.get(moduleSource) : null;
|
|
73837
|
-
const apiName = propertyName ? toZustandApiName(propertyName) : null;
|
|
73838
|
-
return apiName && moduleSource && supportedApiNames?.has(apiName) ? {
|
|
73839
|
-
apiName,
|
|
73840
|
-
moduleSource
|
|
73841
|
-
} : null;
|
|
73842
|
-
};
|
|
73843
|
-
const resolveStateCreator = (expression, scopes, middlewareNames, visitedExpressions = /* @__PURE__ */ new Set()) => {
|
|
73844
|
-
const candidate = stripParenExpression(expression);
|
|
73845
|
-
if (visitedExpressions.has(candidate)) return null;
|
|
73846
|
-
visitedExpressions.add(candidate);
|
|
73847
|
-
const creatorFunction = resolveExactLocalFunction(expression, scopes);
|
|
73848
|
-
if (isFunctionLike$1(creatorFunction)) return creatorFunction;
|
|
73849
|
-
if (!isNodeOfType(candidate, "CallExpression")) return null;
|
|
73850
|
-
const middleware = resolveZustandApiBinding(candidate.callee, scopes);
|
|
73851
|
-
const creatorArgumentIndex = middleware ? STATE_CREATOR_MIDDLEWARE_ARGUMENT_INDEX.get(middleware.apiName) : null;
|
|
73852
|
-
if (!middleware || creatorArgumentIndex === void 0 || creatorArgumentIndex === null) return null;
|
|
73853
|
-
const creatorArgument = candidate.arguments[creatorArgumentIndex];
|
|
73854
|
-
if (!creatorArgument || isNodeOfType(creatorArgument, "SpreadElement")) return null;
|
|
73855
|
-
middlewareNames.add(middleware.apiName);
|
|
73856
|
-
return resolveStateCreator(creatorArgument, scopes, middlewareNames, visitedExpressions);
|
|
73857
|
-
};
|
|
73858
|
-
const isStoreFactoryApi = (binding) => binding?.apiName === "create" || binding?.apiName === "createStore" || binding?.apiName === "createWithEqualityFn";
|
|
73859
|
-
const resolveCurriedStoreFactoryBinding = (expression, scopes) => {
|
|
73860
|
-
const candidate = stripParenExpression(expression);
|
|
73861
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
73862
|
-
const symbol = resolveConstIdentifierAlias(candidate, scopes);
|
|
73863
|
-
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
73864
|
-
return resolveCurriedStoreFactoryBinding(symbol.initializer, scopes);
|
|
73865
|
-
}
|
|
73866
|
-
if (!isNodeOfType(candidate, "CallExpression") || candidate.arguments.length > 0) return null;
|
|
73867
|
-
const factoryBinding = resolveZustandApiBinding(candidate.callee, scopes);
|
|
73868
|
-
return isStoreFactoryApi(factoryBinding) ? factoryBinding : null;
|
|
73869
|
-
};
|
|
73870
|
-
const resolveZustandStoreCreator = (callExpression, scopes) => {
|
|
73871
|
-
const factoryCall = resolveZustandStoreFactoryCall(callExpression, scopes);
|
|
73872
|
-
if (!factoryCall) return null;
|
|
73873
|
-
const middlewareNames = /* @__PURE__ */ new Set();
|
|
73874
|
-
const creatorFunction = resolveStateCreator(factoryCall.creatorArgument, scopes, middlewareNames);
|
|
73875
|
-
if (!creatorFunction) return null;
|
|
73876
|
-
return {
|
|
73877
|
-
creatorFunction,
|
|
73878
|
-
factoryApiName: factoryCall.factoryApiName,
|
|
73879
|
-
middlewareNames
|
|
73880
|
-
};
|
|
73881
|
-
};
|
|
73882
|
-
const resolveZustandStoreFactoryCall = (callExpression, scopes) => {
|
|
73883
|
-
let factoryBinding = resolveZustandApiBinding(callExpression.callee, scopes);
|
|
73884
|
-
if (!isStoreFactoryApi(factoryBinding)) {
|
|
73885
|
-
factoryBinding = resolveCurriedStoreFactoryBinding(callExpression.callee, scopes);
|
|
73886
|
-
if (!isStoreFactoryApi(factoryBinding)) return null;
|
|
73887
|
-
}
|
|
73888
|
-
const creatorArgument = callExpression.arguments[0];
|
|
73889
|
-
if (!creatorArgument || isNodeOfType(creatorArgument, "SpreadElement")) return null;
|
|
73890
|
-
return {
|
|
73891
|
-
callExpression,
|
|
73892
|
-
creatorArgument,
|
|
73893
|
-
factoryApiName: factoryBinding.apiName
|
|
73894
|
-
};
|
|
73895
|
-
};
|
|
73896
|
-
//#endregion
|
|
73897
|
-
//#region src/plugin/rules/state-and-effects/zustand-no-fresh-selector-result.ts
|
|
73898
|
-
const ALLOCATING_ARRAY_METHODS = new Set([
|
|
73899
|
-
"filter",
|
|
73900
|
-
"flat",
|
|
73901
|
-
"flatMap",
|
|
73902
|
-
"map",
|
|
73903
|
-
"toReversed",
|
|
73904
|
-
"toSorted",
|
|
73905
|
-
"toSpliced",
|
|
73906
|
-
"with"
|
|
73907
|
-
]);
|
|
73908
|
-
const SAME_REFERENCE_ARRAY_METHODS = new Set(["reverse", "sort"]);
|
|
73909
|
-
const ALLOCATING_NAMESPACE_METHOD_KINDS = new Map([["Array", new Map([["from", "array"], ["of", "array"]])], ["Object", new Map([
|
|
73910
|
-
["create", "object"],
|
|
73911
|
-
["entries", "array"],
|
|
73912
|
-
["fromEntries", "object"],
|
|
73913
|
-
["keys", "array"],
|
|
73914
|
-
["values", "array"]
|
|
73915
|
-
])]]);
|
|
73916
|
-
const isNullishEqualityArgument = (argument, scopes) => {
|
|
73917
|
-
const candidate = stripParenExpression(argument);
|
|
73918
|
-
return isNodeOfType(candidate, "Identifier") && candidate.name === "undefined" && scopes.isGlobalReference(candidate) || isNodeOfType(candidate, "Literal") && candidate.value === null || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "void";
|
|
73919
|
-
};
|
|
73920
|
-
const hasExplicitEqualityArgument = (argumentsList, equalityArgumentIndex, scopes) => {
|
|
73921
|
-
const equalityArgument = argumentsList[equalityArgumentIndex];
|
|
73922
|
-
if (!equalityArgument) return false;
|
|
73923
|
-
if (isNodeOfType(equalityArgument, "SpreadElement")) return true;
|
|
73924
|
-
return !isNullishEqualityArgument(equalityArgument, scopes);
|
|
73925
|
-
};
|
|
73926
|
-
const resolveZustandStoreCreation = (expression, scopes) => {
|
|
73927
|
-
const candidate = stripParenExpression(expression);
|
|
73928
|
-
if (!isNodeOfType(candidate, "CallExpression")) return null;
|
|
73929
|
-
const factoryCall = resolveZustandStoreFactoryCall(candidate, scopes);
|
|
73930
|
-
if (!factoryCall || factoryCall.factoryApiName !== "create" && factoryCall.factoryApiName !== "createWithEqualityFn") return null;
|
|
73931
|
-
return {
|
|
73932
|
-
creatorFunction: resolveZustandStoreCreator(candidate, scopes)?.creatorFunction ?? null,
|
|
73933
|
-
hasDefaultEquality: factoryCall.factoryApiName === "createWithEqualityFn" && hasExplicitEqualityArgument(candidate.arguments, 1, scopes),
|
|
73934
|
-
supportsEqualityArgument: factoryCall.factoryApiName === "createWithEqualityFn"
|
|
73935
|
-
};
|
|
73936
|
-
};
|
|
73937
|
-
const resolveZustandBoundStore = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73938
|
-
const candidate = stripParenExpression(expression);
|
|
73939
|
-
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
73940
|
-
const symbol = scopes.symbolFor(candidate);
|
|
73941
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
73942
|
-
visitedSymbolIds.add(symbol.id);
|
|
73943
|
-
return resolveZustandStoreCreation(symbol.initializer, scopes) ?? resolveZustandBoundStore(symbol.initializer, scopes, visitedSymbolIds);
|
|
73944
|
-
};
|
|
73945
|
-
const getZustandSelectorCall = (callExpression, scopes) => {
|
|
73946
|
-
const apiBinding = resolveZustandApiBinding(callExpression.callee, scopes);
|
|
73947
|
-
if (apiBinding?.apiName === "useStore" || apiBinding?.apiName === "useStoreWithEqualityFn") {
|
|
73948
|
-
const selector = callExpression.arguments[1];
|
|
73949
|
-
if (!selector || isNodeOfType(selector, "SpreadElement")) return null;
|
|
73950
|
-
if (apiBinding.apiName === "useStoreWithEqualityFn" && hasExplicitEqualityArgument(callExpression.arguments, 2, scopes)) return null;
|
|
73951
|
-
return {
|
|
73952
|
-
selector,
|
|
73953
|
-
storeCreatorFunction: null
|
|
73954
|
-
};
|
|
73955
|
-
}
|
|
73956
|
-
const boundStore = resolveZustandBoundStore(callExpression.callee, scopes);
|
|
73957
|
-
const selector = callExpression.arguments[0];
|
|
73958
|
-
if (!boundStore || !selector || isNodeOfType(selector, "SpreadElement")) return null;
|
|
73959
|
-
if (boundStore.hasDefaultEquality || boundStore.supportsEqualityArgument && hasExplicitEqualityArgument(callExpression.arguments, 1, scopes)) return null;
|
|
73960
|
-
return {
|
|
73961
|
-
selector,
|
|
73962
|
-
storeCreatorFunction: boundStore.creatorFunction
|
|
73963
|
-
};
|
|
73964
|
-
};
|
|
73965
|
-
const isUseShallowCall = (callExpression, scopes) => resolveZustandApiBinding(callExpression.callee, scopes)?.apiName === "useShallow";
|
|
73966
|
-
const resolveSelectorFunction = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73967
|
-
const candidate = stripParenExpression(expression);
|
|
73968
|
-
if (isFunctionLike$1(candidate)) return candidate;
|
|
73969
|
-
if (isNodeOfType(candidate, "CallExpression")) {
|
|
73970
|
-
if (isUseShallowCall(candidate, scopes)) return null;
|
|
73971
|
-
if (!isReactApiCall(candidate, "useCallback", scopes, {
|
|
73972
|
-
allowGlobalReactNamespace: true,
|
|
73973
|
-
resolveNamedAliases: true
|
|
73974
|
-
})) return null;
|
|
73975
|
-
const callback = candidate.arguments[0];
|
|
73976
|
-
if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
|
|
73977
|
-
return resolveSelectorFunction(callback, scopes, visitedSymbolIds);
|
|
73978
|
-
}
|
|
73979
|
-
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
73980
|
-
const symbol = scopes.symbolFor(candidate);
|
|
73981
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
73982
|
-
if (symbol.kind === "function" && isFunctionLike$1(symbol.declarationNode)) return symbol.declarationNode;
|
|
73983
|
-
if (symbol.kind !== "const" || !symbol.initializer) return null;
|
|
73984
|
-
visitedSymbolIds.add(symbol.id);
|
|
73985
|
-
return resolveSelectorFunction(symbol.initializer, scopes, visitedSymbolIds);
|
|
73986
|
-
};
|
|
73987
|
-
const selectorStatePropertyPath = (expression, analysis, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73988
|
-
const candidate = stripParenExpression(expression);
|
|
73989
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
73990
|
-
const symbol = analysis.scopes.symbolFor(candidate);
|
|
73991
|
-
const selectorParameter = isFunctionLike$1(analysis.selectorFunction) ? analysis.selectorFunction.params[0] : null;
|
|
73992
|
-
if (selectorParameter && isNodeOfType(selectorParameter, "Identifier") && symbol?.id === analysis.scopes.symbolFor(selectorParameter)?.id) return [];
|
|
73993
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
73994
|
-
visitedSymbolIds.add(symbol.id);
|
|
73995
|
-
return selectorStatePropertyPath(symbol.initializer, analysis, visitedSymbolIds);
|
|
73996
|
-
}
|
|
73997
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
73998
|
-
const propertyName = getStaticPropertyName(candidate);
|
|
73999
|
-
const objectPath = selectorStatePropertyPath(candidate.object, analysis, visitedSymbolIds);
|
|
74000
|
-
return propertyName && objectPath ? [...objectPath, propertyName] : null;
|
|
74001
|
-
};
|
|
74002
|
-
const freshResultFromAllocatingCall = (expression, analysis, visitedSymbolIds) => {
|
|
74003
|
-
const callee = stripParenExpression(expression.callee);
|
|
74004
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
74005
|
-
const methodName = getStaticPropertyName(callee);
|
|
74006
|
-
if (!methodName) return null;
|
|
74007
|
-
const receiver = stripParenExpression(callee.object);
|
|
74008
|
-
if (isNodeOfType(receiver, "Identifier") && analysis.scopes.isGlobalReference(receiver)) {
|
|
74009
|
-
const resultKind = ALLOCATING_NAMESPACE_METHOD_KINDS.get(receiver.name)?.get(methodName);
|
|
74010
|
-
if (resultKind) return {
|
|
74011
|
-
kind: resultKind,
|
|
74012
|
-
node: expression
|
|
74013
|
-
};
|
|
74014
|
-
if (receiver.name === "Object" && methodName === "assign") {
|
|
74015
|
-
const target = expression.arguments[0];
|
|
74016
|
-
if (!target || isNodeOfType(target, "SpreadElement")) return null;
|
|
74017
|
-
const freshTarget = resolveFreshSelectorResult(target, analysis, new Set(visitedSymbolIds));
|
|
74018
|
-
return freshTarget ? {
|
|
74019
|
-
kind: freshTarget.kind,
|
|
74020
|
-
node: expression
|
|
74021
|
-
} : null;
|
|
74022
|
-
}
|
|
74023
|
-
}
|
|
74024
|
-
if (ALLOCATING_ARRAY_METHODS.has(methodName)) {
|
|
74025
|
-
if (resolveFreshSelectorResult(receiver, analysis, new Set(visitedSymbolIds))?.kind === "array") return {
|
|
74026
|
-
kind: "array",
|
|
74027
|
-
node: expression
|
|
74028
|
-
};
|
|
74029
|
-
const receiverPath = selectorStatePropertyPath(receiver, analysis);
|
|
74030
|
-
if (receiverPath && analysis.storeCreatorFunction && functionReturnsCollectionAtPath({
|
|
74031
|
-
collectionKind: "array",
|
|
74032
|
-
functionNode: analysis.storeCreatorFunction,
|
|
74033
|
-
propertyPath: receiverPath,
|
|
74034
|
-
scopes: analysis.scopes
|
|
74035
|
-
})) return {
|
|
74036
|
-
kind: "array",
|
|
74037
|
-
node: expression
|
|
74038
|
-
};
|
|
74039
|
-
return null;
|
|
74040
|
-
}
|
|
74041
|
-
if (!SAME_REFERENCE_ARRAY_METHODS.has(methodName)) return null;
|
|
74042
|
-
return resolveFreshSelectorResult(receiver, analysis, new Set(visitedSymbolIds)) ? {
|
|
74043
|
-
kind: "array",
|
|
74044
|
-
node: expression
|
|
74045
|
-
} : null;
|
|
74046
|
-
};
|
|
74047
|
-
const resolveFreshSelectorResult = (expression, analysis, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
74048
|
-
const freshRenderValue = resolveFreshRenderValue(expression, analysis.scopes);
|
|
74049
|
-
if (freshRenderValue?.kind === "array" || freshRenderValue?.kind === "function" || freshRenderValue?.kind === "instance" || freshRenderValue?.kind === "object") return {
|
|
74050
|
-
kind: freshRenderValue.kind,
|
|
74051
|
-
node: expression
|
|
74052
|
-
};
|
|
74053
|
-
const candidate = stripParenExpression(expression);
|
|
74054
|
-
if (isNodeOfType(candidate, "CallExpression")) return freshResultFromAllocatingCall(candidate, analysis, visitedSymbolIds);
|
|
74055
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return resolveFreshSelectorResult(candidate.consequent, analysis, new Set(visitedSymbolIds)) ?? resolveFreshSelectorResult(candidate.alternate, analysis, new Set(visitedSymbolIds));
|
|
74056
|
-
if (isNodeOfType(candidate, "LogicalExpression")) {
|
|
74057
|
-
if (candidate.operator === "&&") return resolveFreshSelectorResult(candidate.right, analysis, visitedSymbolIds);
|
|
74058
|
-
return resolveFreshSelectorResult(candidate.left, analysis, new Set(visitedSymbolIds)) ?? resolveFreshSelectorResult(candidate.right, analysis, new Set(visitedSymbolIds));
|
|
74059
|
-
}
|
|
74060
|
-
if (isNodeOfType(candidate, "SequenceExpression")) {
|
|
74061
|
-
const returnedExpression = candidate.expressions[candidate.expressions.length - 1];
|
|
74062
|
-
return returnedExpression ? resolveFreshSelectorResult(returnedExpression, analysis, visitedSymbolIds) : null;
|
|
74063
|
-
}
|
|
74064
|
-
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
74065
|
-
const symbol = analysis.scopes.symbolFor(candidate);
|
|
74066
|
-
if (symbol?.kind !== "const" || symbol.scope.kind === "module" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
74067
|
-
visitedSymbolIds.add(symbol.id);
|
|
74068
|
-
return resolveFreshSelectorResult(symbol.initializer, analysis, visitedSymbolIds);
|
|
74069
|
-
};
|
|
74070
|
-
const findFreshSelectorReturn = (selectorFunction, scopes, storeCreatorFunction) => {
|
|
74071
|
-
if (!isFunctionLike$1(selectorFunction) || !selectorFunction.body) return null;
|
|
74072
|
-
const analysis = {
|
|
74073
|
-
scopes,
|
|
74074
|
-
selectorFunction,
|
|
74075
|
-
storeCreatorFunction
|
|
74076
|
-
};
|
|
74077
|
-
if (!isNodeOfType(selectorFunction.body, "BlockStatement")) return resolveFreshSelectorResult(selectorFunction.body, analysis);
|
|
74078
|
-
let freshResult = null;
|
|
74079
|
-
walkAst(selectorFunction.body, (candidate) => {
|
|
74080
|
-
if (freshResult) return false;
|
|
74081
|
-
if (candidate !== selectorFunction.body && isFunctionLike$1(candidate)) return false;
|
|
74082
|
-
if (!isNodeOfType(candidate, "ReturnStatement") || !candidate.argument) return;
|
|
74083
|
-
freshResult = resolveFreshSelectorResult(candidate.argument, analysis);
|
|
74084
|
-
return freshResult ? false : void 0;
|
|
74085
|
-
});
|
|
74086
|
-
return freshResult;
|
|
74087
|
-
};
|
|
74088
|
-
const zustandNoFreshSelectorResult = defineRule({
|
|
74089
|
-
id: "zustand-no-fresh-selector-result",
|
|
74090
|
-
title: "Zustand selector returns a fresh value",
|
|
74091
|
-
severity: "error",
|
|
74092
|
-
category: "Performance",
|
|
74093
|
-
requires: ["zustand", "zustand:5"],
|
|
74094
|
-
recommendation: "Select a stable store field, split the selector, or wrap a collection selector with `useShallow`.",
|
|
74095
|
-
create: (context) => ({ CallExpression(node) {
|
|
74096
|
-
const selectorCall = getZustandSelectorCall(node, context.scopes);
|
|
74097
|
-
if (!selectorCall) return;
|
|
74098
|
-
const selectorFunction = resolveSelectorFunction(selectorCall.selector, context.scopes);
|
|
74099
|
-
if (!selectorFunction) return;
|
|
74100
|
-
const freshResult = findFreshSelectorReturn(selectorFunction, context.scopes, selectorCall.storeCreatorFunction);
|
|
74101
|
-
if (!freshResult) return;
|
|
74102
|
-
context.report({
|
|
74103
|
-
node: freshResult.node,
|
|
74104
|
-
message: "This Zustand selector creates a new reference whenever the store is read, so Object.is never sees a stable snapshot and Zustand v5 can repeatedly render or hit maximum update depth. Select a stable field or use `useShallow`."
|
|
74105
|
-
});
|
|
74106
|
-
} })
|
|
74107
|
-
});
|
|
74108
|
-
//#endregion
|
|
74109
|
-
//#region src/plugin/rules/state-and-effects/zustand-no-get-during-initialization.ts
|
|
74110
|
-
const findGetParameterIdentifier = (parameter) => {
|
|
74111
|
-
if (!parameter) return null;
|
|
74112
|
-
if (isNodeOfType(parameter, "Identifier")) return parameter;
|
|
74113
|
-
if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
|
|
74114
|
-
return null;
|
|
74115
|
-
};
|
|
74116
|
-
const isGetParameterCall = (callExpression, getParameterSymbol, context) => {
|
|
74117
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
74118
|
-
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
74119
|
-
return resolveConstIdentifierAlias(callee, context.scopes)?.id === getParameterSymbol.id;
|
|
74120
|
-
};
|
|
74121
|
-
const reportEagerGetCalls = (creatorFunction, getParameterSymbol, context) => {
|
|
74122
|
-
const visitedFunctions = /* @__PURE__ */ new Set();
|
|
74123
|
-
const reportFunctionReads = (functionNode) => {
|
|
74124
|
-
if (!isFunctionLike$1(functionNode) || functionNode.generator || visitedFunctions.has(functionNode)) return;
|
|
74125
|
-
visitedFunctions.add(functionNode);
|
|
74126
|
-
walkAst(functionNode, (node) => {
|
|
74127
|
-
if (node !== functionNode && isFunctionLike$1(node)) return false;
|
|
74128
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
74129
|
-
if (isGetParameterCall(node, getParameterSymbol, context)) {
|
|
74130
|
-
if (!canExecuteBeforeAsyncSuspension(node, functionNode, context)) return;
|
|
74131
|
-
context.report({
|
|
74132
|
-
node,
|
|
74133
|
-
message: "`get()` runs before Zustand installs the initial state, so it returns `undefined` here."
|
|
74134
|
-
});
|
|
74135
|
-
return;
|
|
74136
|
-
}
|
|
74137
|
-
if (!canExecuteBeforeAsyncSuspension(node, functionNode, context)) return;
|
|
74138
|
-
const calledFunction = resolveExactLocalFunction(node.callee, context.scopes);
|
|
74139
|
-
if (!calledFunction || calledFunction === functionNode) return;
|
|
74140
|
-
reportFunctionReads(calledFunction);
|
|
74141
|
-
});
|
|
74142
|
-
};
|
|
74143
|
-
reportFunctionReads(creatorFunction);
|
|
74144
|
-
};
|
|
74145
|
-
const zustandNoGetDuringInitialization = defineRule({
|
|
74146
|
-
id: "zustand-no-get-during-initialization",
|
|
74147
|
-
title: "Zustand get() called during store initialization",
|
|
74148
|
-
severity: "error",
|
|
74149
|
-
category: "Correctness",
|
|
74150
|
-
recommendation: "Move the read into a deferred store action or derive the initial value without calling `get()`.",
|
|
74151
|
-
requires: ["zustand", "zustand:1"],
|
|
74152
|
-
create: (context) => {
|
|
74153
|
-
const creatorFunctions = /* @__PURE__ */ new Set();
|
|
74154
|
-
return {
|
|
74155
|
-
CallExpression(node) {
|
|
74156
|
-
const creator = resolveZustandStoreCreator(node, context.scopes);
|
|
74157
|
-
if (creator) creatorFunctions.add(creator.creatorFunction);
|
|
74158
|
-
},
|
|
74159
|
-
"Program:exit"() {
|
|
74160
|
-
for (const creatorFunction of creatorFunctions) {
|
|
74161
|
-
if (creatorFunction.generator) continue;
|
|
74162
|
-
const parameterIdentifier = findGetParameterIdentifier(creatorFunction.params[1]);
|
|
74163
|
-
const getParameterSymbol = parameterIdentifier ? context.scopes.symbolFor(parameterIdentifier) : null;
|
|
74164
|
-
if (!getParameterSymbol) continue;
|
|
74165
|
-
reportEagerGetCalls(creatorFunction, getParameterSymbol, context);
|
|
74166
|
-
}
|
|
74167
|
-
}
|
|
74168
|
-
};
|
|
74169
|
-
}
|
|
74170
|
-
});
|
|
74171
|
-
//#endregion
|
|
74172
|
-
//#region src/plugin/rules/state-and-effects/zustand-no-mutating-state.ts
|
|
74173
|
-
const MESSAGE = "This Zustand state reference is mutated and reused, so subscribers can miss the update.";
|
|
74174
|
-
const FRESH_ARRAY_METHOD_NAMES = new Set([
|
|
74175
|
-
"concat",
|
|
74176
|
-
"filter",
|
|
74177
|
-
"flat",
|
|
74178
|
-
"flatMap",
|
|
74179
|
-
"map",
|
|
74180
|
-
"slice",
|
|
74181
|
-
"toReversed",
|
|
74182
|
-
"toSorted",
|
|
74183
|
-
"toSpliced",
|
|
74184
|
-
"with"
|
|
74185
|
-
]);
|
|
74186
|
-
const UNSUPPORTED_CONTROL_FLOW_TYPES = new Set([
|
|
74187
|
-
"DoWhileStatement",
|
|
74188
|
-
"ForInStatement",
|
|
74189
|
-
"ForOfStatement",
|
|
74190
|
-
"ForStatement",
|
|
74191
|
-
"IfStatement",
|
|
74192
|
-
"LabeledStatement",
|
|
74193
|
-
"BlockStatement",
|
|
74194
|
-
"SwitchStatement",
|
|
74195
|
-
"TryStatement",
|
|
74196
|
-
"WhileStatement",
|
|
74197
|
-
"WithStatement"
|
|
74198
|
-
]);
|
|
74199
|
-
const UNSUPPORTED_SNAPSHOT_EXPRESSION_CONTROL_FLOW_TYPES = new Set(["ConditionalExpression", "LogicalExpression"]);
|
|
74200
|
-
const findIdentifierParameter = (parameter) => {
|
|
74201
|
-
if (!parameter) return null;
|
|
74202
|
-
if (isNodeOfType(parameter, "Identifier")) return parameter;
|
|
74203
|
-
if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
|
|
74204
|
-
return null;
|
|
74205
|
-
};
|
|
74206
|
-
const symbolForParameter = (creatorFunction, parameterIndex, context) => {
|
|
74207
|
-
const parameter = findIdentifierParameter(creatorFunction.params[parameterIndex]);
|
|
74208
|
-
return parameter ? context.scopes.symbolFor(parameter) ?? null : null;
|
|
74209
|
-
};
|
|
74210
|
-
const isCallToSymbol = (callExpression, symbolIds, context) => {
|
|
74211
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
74212
|
-
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
74213
|
-
const symbol = resolveConstIdentifierAlias(callee, context.scopes);
|
|
74214
|
-
return Boolean(symbol && symbolIds.has(symbol.id));
|
|
74215
|
-
};
|
|
74216
|
-
const rootCallForExpression = (expression) => {
|
|
74217
|
-
let current = stripParenExpression(expression);
|
|
74218
|
-
while (isNodeOfType(current, "MemberExpression")) current = stripParenExpression(current.object);
|
|
74219
|
-
return isNodeOfType(current, "CallExpression") ? current : null;
|
|
74220
|
-
};
|
|
74221
|
-
const storeSymbolIdForMethodCall = (callExpression, methodName, context) => {
|
|
74222
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
74223
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
74224
|
-
if (getStaticPropertyName(callee) !== methodName) return null;
|
|
74225
|
-
const receiver = stripParenExpression(callee.object);
|
|
74226
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
74227
|
-
return resolveConstIdentifierAlias(receiver, context.scopes)?.id ?? null;
|
|
74228
|
-
};
|
|
74229
|
-
const isStoreMethodCall = (callExpression, methodName, storeSymbolIds, context) => {
|
|
74230
|
-
const storeSymbolId = storeSymbolIdForMethodCall(callExpression, methodName, context);
|
|
74231
|
-
return storeSymbolId !== null && storeSymbolIds.has(storeSymbolId);
|
|
74232
|
-
};
|
|
74233
|
-
const isSnapshotExpression = (expression, getSymbolIds, storeSymbolIds, context) => {
|
|
74234
|
-
if (!expression) return false;
|
|
74235
|
-
const rootCall = rootCallForExpression(expression);
|
|
74236
|
-
return Boolean(rootCall && (isCallToSymbol(rootCall, getSymbolIds, context) || isStoreMethodCall(rootCall, "getState", storeSymbolIds, context)));
|
|
74237
|
-
};
|
|
74238
|
-
const expressionKeyPreservesTarget = (expression, targetKey, context) => {
|
|
74239
|
-
const expressionKey = resolveExpressionKey$1(expression, context);
|
|
74240
|
-
return Boolean(expressionKey && (expressionKey === targetKey || targetKey.startsWith(`${expressionKey}.`)));
|
|
74241
|
-
};
|
|
74242
|
-
const expressionPreservesTarget = (expression, targetKey, mutationNode, context) => {
|
|
74243
|
-
if (!expression) return false;
|
|
74244
|
-
const candidate = stripParenExpression(expression);
|
|
74245
|
-
if (candidate === mutationNode) return true;
|
|
74246
|
-
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "MemberExpression")) return expressionKeyPreservesTarget(candidate, targetKey, context);
|
|
74247
|
-
if (isNodeOfType(candidate, "ObjectExpression")) return candidate.properties.some((property) => {
|
|
74248
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
74249
|
-
const spreadKey = resolveExpressionKey$1(property.argument, context);
|
|
74250
|
-
return Boolean(spreadKey && spreadKey !== targetKey && targetKey.startsWith(`${spreadKey}.`));
|
|
74251
|
-
}
|
|
74252
|
-
return isNodeOfType(property, "Property") && expressionPreservesTarget(property.value, targetKey, mutationNode, context);
|
|
74253
|
-
});
|
|
74254
|
-
if (isNodeOfType(candidate, "ArrayExpression")) return candidate.elements.some((element) => Boolean(element) && !isNodeOfType(element, "SpreadElement") && expressionPreservesTarget(element, targetKey, mutationNode, context));
|
|
74255
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return expressionPreservesTarget(candidate.consequent, targetKey, mutationNode, context) || expressionPreservesTarget(candidate.alternate, targetKey, mutationNode, context);
|
|
74256
|
-
if (isNodeOfType(candidate, "LogicalExpression")) return expressionPreservesTarget(candidate.left, targetKey, mutationNode, context) || expressionPreservesTarget(candidate.right, targetKey, mutationNode, context);
|
|
74257
|
-
if (isNodeOfType(candidate, "SequenceExpression")) return expressionPreservesTarget(candidate.expressions[candidate.expressions.length - 1], targetKey, mutationNode, context);
|
|
74258
|
-
return false;
|
|
74259
|
-
};
|
|
74260
|
-
const expressionContainsFreshCloneOfTarget = (expression, targetKey, context) => {
|
|
74261
|
-
if (!expression) return false;
|
|
74262
|
-
let didFindFreshClone = false;
|
|
74263
|
-
walkAst(expression, (node) => {
|
|
74264
|
-
const candidate = stripParenExpression(node);
|
|
74265
|
-
if (isFunctionLike$1(candidate)) return false;
|
|
74266
|
-
if (isNodeOfType(candidate, "SpreadElement")) {
|
|
74267
|
-
if (resolveExpressionKey$1(candidate.argument, context) === targetKey) didFindFreshClone = true;
|
|
74268
|
-
return false;
|
|
74269
|
-
}
|
|
74270
|
-
if (isNodeOfType(candidate, "NewExpression")) {
|
|
74271
|
-
const callee = stripParenExpression(candidate.callee);
|
|
74272
|
-
const firstArgument = candidate.arguments[0];
|
|
74273
|
-
if (isNodeOfType(callee, "Identifier") && (callee.name === "Map" || callee.name === "Set") && firstArgument && !isNodeOfType(firstArgument, "SpreadElement") && resolveExpressionKey$1(firstArgument, context) === targetKey) didFindFreshClone = true;
|
|
74274
|
-
return;
|
|
74275
|
-
}
|
|
74276
|
-
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
74277
|
-
const callee = stripParenExpression(candidate.callee);
|
|
74278
|
-
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
74279
|
-
const methodName = getStaticPropertyName(callee);
|
|
74280
|
-
if (methodName && FRESH_ARRAY_METHOD_NAMES.has(methodName) && resolveExpressionKey$1(callee.object, context) === targetKey) didFindFreshClone = true;
|
|
74281
|
-
});
|
|
74282
|
-
return didFindFreshClone;
|
|
74283
|
-
};
|
|
74284
|
-
const isDefinitelyNoUpdateExpression = (expression, context) => {
|
|
74285
|
-
const candidate = stripParenExpression(expression);
|
|
74286
|
-
return isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "void" || isNodeOfType(candidate, "Identifier") && candidate.name === "undefined" && context.scopes.isGlobalReference(candidate);
|
|
74287
|
-
};
|
|
74288
|
-
const returnedExpressionsForFunction = (functionNode) => {
|
|
74289
|
-
if (!isFunctionLike$1(functionNode)) return [];
|
|
74290
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return [functionNode.body];
|
|
74291
|
-
const returnedExpressions = [];
|
|
74292
|
-
for (const statement of functionNode.body.body) if (isNodeOfType(statement, "ReturnStatement") && statement.argument) returnedExpressions.push(statement.argument);
|
|
74293
|
-
return returnedExpressions;
|
|
74294
|
-
};
|
|
74295
|
-
const hasUnsupportedControlFlow = (statements) => statements.some((statement) => UNSUPPORTED_CONTROL_FLOW_TYPES.has(statement.type));
|
|
74296
|
-
const hasAbruptCompletion = (node) => {
|
|
74297
|
-
let didFindAbruptCompletion = false;
|
|
74298
|
-
walkAst(node, (child) => {
|
|
74299
|
-
if (child !== node && isFunctionLike$1(child)) return false;
|
|
74300
|
-
if (isNodeOfType(child, "ReturnStatement") || isNodeOfType(child, "ThrowStatement")) {
|
|
74301
|
-
didFindAbruptCompletion = true;
|
|
74302
|
-
return false;
|
|
74303
|
-
}
|
|
74304
|
-
});
|
|
74305
|
-
return didFindAbruptCompletion;
|
|
74306
|
-
};
|
|
74307
|
-
const hasUnsupportedSnapshotStatement = (statement) => {
|
|
74308
|
-
if (!isNodeOfType(statement, "IfStatement")) return UNSUPPORTED_CONTROL_FLOW_TYPES.has(statement.type);
|
|
74309
|
-
if (hasAbruptCompletion(statement)) return true;
|
|
74310
|
-
const branchStatements = [];
|
|
74311
|
-
for (const branch of [statement.consequent, statement.alternate]) {
|
|
74312
|
-
if (!branch) continue;
|
|
74313
|
-
branchStatements.push(...isNodeOfType(branch, "BlockStatement") ? branch.body : [branch]);
|
|
74314
|
-
}
|
|
74315
|
-
return branchStatements.some(hasUnsupportedSnapshotStatement);
|
|
74316
|
-
};
|
|
74317
|
-
const hasUnsupportedSnapshotControlFlow = (statements) => {
|
|
74318
|
-
if (statements.some(hasUnsupportedSnapshotStatement)) return true;
|
|
74319
|
-
let didFindUnsupportedExpressionControlFlow = false;
|
|
74320
|
-
for (const statement of statements) walkAst(statement, (node) => {
|
|
74321
|
-
if (node !== statement && isFunctionLike$1(node)) return false;
|
|
74322
|
-
if (UNSUPPORTED_SNAPSHOT_EXPRESSION_CONTROL_FLOW_TYPES.has(node.type)) {
|
|
74323
|
-
didFindUnsupportedExpressionControlFlow = true;
|
|
74324
|
-
return false;
|
|
74325
|
-
}
|
|
74326
|
-
});
|
|
74327
|
-
return didFindUnsupportedExpressionControlFlow;
|
|
74328
|
-
};
|
|
74329
|
-
const analyzeSetUpdater = (updaterFunction, getSymbolIds, storeSymbolIds, creatorFunction, context, reportedNodes) => {
|
|
74330
|
-
if (!isFunctionLike$1(updaterFunction) || updaterFunction.async || updaterFunction.generator) return;
|
|
74331
|
-
const stateParameter = findIdentifierParameter(updaterFunction.params[0]);
|
|
74332
|
-
if (!stateParameter) return;
|
|
74333
|
-
const state = { mutableStateSourceNames: new Set([stateParameter.name]) };
|
|
74334
|
-
const returnedExpressions = returnedExpressionsForFunction(updaterFunction);
|
|
74335
|
-
const mutations = [];
|
|
74336
|
-
const mutationOptions = { isProvenMutatingMethodCall: (callExpression) => isProvenZustandMutatingMethodCall(callExpression, creatorFunction, context) };
|
|
74337
|
-
if (isNodeOfType(updaterFunction.body, "BlockStatement")) {
|
|
74338
|
-
if (hasUnsupportedControlFlow(updaterFunction.body.body)) return;
|
|
74339
|
-
for (const statement of updaterFunction.body.body) {
|
|
74340
|
-
mutations.push(...collectMutableStateReferenceMutations(statement, state, mutationOptions));
|
|
74341
|
-
if (isNodeOfType(statement, "VariableDeclaration")) updateMutableStateReferencesForVariableDeclaration(statement, state);
|
|
74342
|
-
else if (isNodeOfType(statement, "ExpressionStatement")) {
|
|
74343
|
-
const assignment = stripParenExpression(statement.expression);
|
|
74344
|
-
if (isNodeOfType(assignment, "AssignmentExpression")) updateMutableStateReferencesForIdentifierAssignment(assignment, state);
|
|
74345
|
-
}
|
|
74346
|
-
if (isNodeOfType(statement, "ReturnStatement")) break;
|
|
74347
|
-
}
|
|
74348
|
-
} else mutations.push(...collectMutableStateReferenceMutations(updaterFunction.body, state, mutationOptions));
|
|
74349
|
-
for (const mutation of mutations) {
|
|
74350
|
-
const mutationPath = staticPropertyPathForExpression(mutation.receiver, context);
|
|
74351
|
-
const hasNoUpdateReturn = returnedExpressions.some((expression) => isDefinitelyNoUpdateExpression(expression, context));
|
|
74352
|
-
const doesPreserveTarget = returnedExpressions.some((expression) => updateTargetReplacementDisposition(expression, mutation, context) === false || isSnapshotExpression(expression, getSymbolIds, storeSymbolIds, context) && mutationPath !== null && staticPathPreservesTarget(staticPropertyPathForExpression(expression, context), mutationPath));
|
|
74353
|
-
if (returnedExpressions.length > 0 && !doesPreserveTarget && !hasNoUpdateReturn) continue;
|
|
74354
|
-
if (reportedNodes.has(mutation.node)) continue;
|
|
74355
|
-
reportedNodes.add(mutation.node);
|
|
74356
|
-
context.report({
|
|
74357
|
-
node: mutation.node,
|
|
74358
|
-
message: MESSAGE
|
|
74359
|
-
});
|
|
74360
|
-
}
|
|
74361
|
-
};
|
|
74362
|
-
const collectNotifierCalls = (statement, setSymbolIds, storeSymbolIds, context) => {
|
|
74363
|
-
const notifierCalls = [];
|
|
74364
|
-
walkAst(statement, (node) => {
|
|
74365
|
-
if (isFunctionLike$1(node)) return false;
|
|
74366
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
74367
|
-
if (isCallToSymbol(node, setSymbolIds, context) || isStoreMethodCall(node, "setState", storeSymbolIds, context)) notifierCalls.push(node);
|
|
74368
|
-
});
|
|
74369
|
-
return notifierCalls;
|
|
74370
|
-
};
|
|
74371
|
-
const staticPropertyPathForExpression = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set(), followMutableInitializer = false) => {
|
|
74372
|
-
const candidate = stripParenExpression(expression);
|
|
74373
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
74374
|
-
const symbol = context.scopes.symbolFor(candidate);
|
|
74375
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
|
|
74376
|
-
visitedSymbolIds.add(symbol.id);
|
|
74377
|
-
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
74378
|
-
const bindingPattern = bindingProperty?.parent;
|
|
74379
|
-
const variableDeclarator = bindingPattern?.parent;
|
|
74380
|
-
if (isNodeOfType(bindingProperty, "Property") && isNodeOfType(bindingPattern, "ObjectPattern") && isNodeOfType(variableDeclarator, "VariableDeclarator")) {
|
|
74381
|
-
const propertyName = getStaticPropertyKeyName(bindingProperty);
|
|
74382
|
-
const objectPath = variableDeclarator.init ? staticPropertyPathForExpression(variableDeclarator.init, context, visitedSymbolIds, followMutableInitializer) : null;
|
|
74383
|
-
return propertyName && objectPath ? [...objectPath, propertyName] : null;
|
|
74384
|
-
}
|
|
74385
|
-
if (!followMutableInitializer && symbol.kind !== "const" || !symbol.initializer) return [];
|
|
74386
|
-
return staticPropertyPathForExpression(symbol.initializer, context, visitedSymbolIds, followMutableInitializer);
|
|
74387
|
-
}
|
|
74388
|
-
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
74389
|
-
const propertyName = getStaticPropertyName(candidate);
|
|
74390
|
-
const objectPath = staticPropertyPathForExpression(candidate.object, context, visitedSymbolIds, followMutableInitializer);
|
|
74391
|
-
return propertyName && objectPath ? [...objectPath, propertyName] : null;
|
|
74392
|
-
}
|
|
74393
|
-
if (isNodeOfType(candidate, "CallExpression")) return [];
|
|
74394
|
-
return null;
|
|
74395
|
-
};
|
|
74396
|
-
const isProvenZustandMutatingMethodCall = (callExpression, creatorFunction, context) => {
|
|
74397
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
74398
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
74399
|
-
const methodName = getStaticPropertyName(callee);
|
|
74400
|
-
const receiverPath = staticPropertyPathForExpression(callee.object, context, /* @__PURE__ */ new Set(), true);
|
|
74401
|
-
if (!methodName || !receiverPath) return false;
|
|
74402
|
-
if (MUTATING_ARRAY_METHODS.has(methodName) && functionReturnsCollectionAtPath({
|
|
74403
|
-
collectionKind: "array",
|
|
74404
|
-
functionNode: creatorFunction,
|
|
74405
|
-
propertyPath: receiverPath,
|
|
74406
|
-
scopes: context.scopes
|
|
74407
|
-
})) return true;
|
|
74408
|
-
return MUTATING_COLLECTION_METHODS.has(methodName) && functionReturnsCollectionAtPath({
|
|
74409
|
-
collectionKind: "map-or-set",
|
|
74410
|
-
functionNode: creatorFunction,
|
|
74411
|
-
propertyPath: receiverPath,
|
|
74412
|
-
scopes: context.scopes
|
|
74413
|
-
});
|
|
74414
|
-
};
|
|
74415
|
-
const isProvenFreshReplacementExpression = (expression, targetKey, context) => {
|
|
74416
|
-
const candidate = stripParenExpression(expression);
|
|
74417
|
-
return isNodeOfType(candidate, "ObjectExpression") || isNodeOfType(candidate, "ArrayExpression") || isNodeOfType(candidate, "NewExpression") || isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "TemplateLiteral") || expressionContainsFreshCloneOfTarget(candidate, targetKey, context);
|
|
74418
|
-
};
|
|
74419
|
-
const objectTargetReplacementDisposition = (objectExpression, targetPath, targetKey, ancestorKey, mutationNode, isPartialUpdateRoot, context, ancestorPath = []) => {
|
|
74420
|
-
const propertyName = targetPath[0];
|
|
74421
|
-
if (!propertyName) return true;
|
|
74422
|
-
let disposition = isPartialUpdateRoot ? false : true;
|
|
74423
|
-
for (const property of objectExpression.properties) {
|
|
74424
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
74425
|
-
const spreadKey = resolveExpressionKey$1(property.argument, context);
|
|
74426
|
-
const spreadPath = staticPropertyPathForExpression(property.argument, context);
|
|
74427
|
-
disposition = spreadKey === ancestorKey || staticPathPreservesTarget(spreadPath, ancestorPath) ? false : null;
|
|
74428
|
-
continue;
|
|
74429
|
-
}
|
|
74430
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
74431
|
-
if (getStaticPropertyKeyName(property) !== propertyName) continue;
|
|
74432
|
-
if (targetPath.length === 1) {
|
|
74433
|
-
if (expressionPreservesTarget(property.value, targetKey, mutationNode, context)) disposition = false;
|
|
74434
|
-
else disposition = isProvenFreshReplacementExpression(property.value, targetKey, context) ? true : null;
|
|
74435
|
-
continue;
|
|
74436
|
-
}
|
|
74437
|
-
const propertyValue = stripParenExpression(property.value);
|
|
74438
|
-
if (isNodeOfType(propertyValue, "ObjectExpression")) disposition = objectTargetReplacementDisposition(propertyValue, targetPath.slice(1), targetKey, `${ancestorKey}.${propertyName}`, mutationNode, false, context, [...ancestorPath, propertyName]);
|
|
74439
|
-
else if (expressionKeyPreservesTarget(propertyValue, targetKey, context)) disposition = false;
|
|
74440
|
-
else disposition = isProvenFreshReplacementExpression(propertyValue, targetKey, context) ? true : null;
|
|
74441
|
-
}
|
|
74442
|
-
return disposition;
|
|
74443
|
-
};
|
|
74444
|
-
const staticPathPreservesTarget = (candidatePath, targetPath) => Boolean(candidatePath && candidatePath.length <= targetPath.length && candidatePath.every((propertyName, index) => propertyName === targetPath[index]));
|
|
74445
|
-
const objectTargetPathReplacementDisposition = (objectExpression, targetPath, isPartialUpdateRoot, context, ancestorPath = []) => {
|
|
74446
|
-
const propertyName = targetPath[0];
|
|
74447
|
-
if (!propertyName) return true;
|
|
74448
|
-
let disposition = isPartialUpdateRoot ? false : true;
|
|
74449
|
-
for (const property of objectExpression.properties) {
|
|
74450
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
74451
|
-
disposition = null;
|
|
74452
|
-
continue;
|
|
74453
|
-
}
|
|
74454
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
74455
|
-
if (getStaticPropertyKeyName(property) !== propertyName) continue;
|
|
74456
|
-
const propertyValue = stripParenExpression(property.value);
|
|
74457
|
-
if (targetPath.length > 1 && isNodeOfType(propertyValue, "ObjectExpression")) {
|
|
74458
|
-
disposition = objectTargetPathReplacementDisposition(propertyValue, targetPath.slice(1), false, context, [...ancestorPath, propertyName]);
|
|
74459
|
-
continue;
|
|
74460
|
-
}
|
|
74461
|
-
if (staticPathPreservesTarget(staticPropertyPathForExpression(propertyValue, context), [...ancestorPath, ...targetPath])) {
|
|
74462
|
-
disposition = false;
|
|
74463
|
-
continue;
|
|
74464
|
-
}
|
|
74465
|
-
disposition = isProvenFreshReplacementExpression(propertyValue, "", context) ? true : null;
|
|
74466
|
-
}
|
|
74467
|
-
return disposition;
|
|
74468
|
-
};
|
|
74469
|
-
const objectExpressionPublishesSymbolAtPath = (objectExpression, targetPath, symbolId, context) => {
|
|
74470
|
-
const propertyName = targetPath[0];
|
|
74471
|
-
if (!propertyName) return false;
|
|
74472
|
-
for (const property of objectExpression.properties) {
|
|
74473
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
74474
|
-
if (getStaticPropertyKeyName(property) !== propertyName) continue;
|
|
74475
|
-
const propertyValue = stripParenExpression(property.value);
|
|
74476
|
-
if (targetPath.length > 1) return isNodeOfType(propertyValue, "ObjectExpression") && objectExpressionPublishesSymbolAtPath(propertyValue, targetPath.slice(1), symbolId, context);
|
|
74477
|
-
return isNodeOfType(propertyValue, "Identifier") && context.scopes.symbolFor(propertyValue)?.id === symbolId;
|
|
74478
|
-
}
|
|
74479
|
-
return false;
|
|
74480
|
-
};
|
|
74481
|
-
const branchPathCompatibility = (candidate, mutation) => {
|
|
74482
|
-
let current = candidate;
|
|
74483
|
-
const mutationFunction = findEnclosingFunction$1(mutation.node);
|
|
74484
|
-
while (current.parent && current.parent !== mutationFunction) {
|
|
74485
|
-
const parent = current.parent;
|
|
74486
|
-
if (isNodeOfType(parent, "IfStatement") && (parent.consequent === current || parent.alternate === current)) {
|
|
74487
|
-
if (isAstDescendant(mutation.node, current)) {
|
|
74488
|
-
current = parent;
|
|
74489
|
-
continue;
|
|
74490
|
-
}
|
|
74491
|
-
const otherBranch = parent.consequent === current ? parent.alternate : parent.consequent;
|
|
74492
|
-
return otherBranch && isAstDescendant(mutation.node, otherBranch) ? false : null;
|
|
74493
|
-
}
|
|
74494
|
-
current = parent;
|
|
74495
|
-
}
|
|
74496
|
-
return true;
|
|
74497
|
-
};
|
|
74498
|
-
const rebindPathCompatibility = (assignment, mutation) => {
|
|
74499
|
-
if (!isNodeOfType(assignment.parent, "ExpressionStatement")) return null;
|
|
74500
|
-
return branchPathCompatibility(assignment.parent, mutation);
|
|
74501
|
-
};
|
|
74502
|
-
const targetRebindReplacementDisposition = (updateExpression, mutation, targetPath, targetKey, context) => {
|
|
74503
|
-
const targetIdentifier = getRootIdentifier$1(mutation.receiver);
|
|
74504
|
-
if (!targetIdentifier) return void 0;
|
|
74505
|
-
const targetSymbol = context.scopes.symbolFor(targetIdentifier);
|
|
74506
|
-
if (!targetSymbol || targetSymbol.kind === "const") return void 0;
|
|
74507
|
-
const candidate = stripParenExpression(updateExpression);
|
|
74508
|
-
if (!(targetPath.length === 0 ? isNodeOfType(candidate, "Identifier") && context.scopes.symbolFor(candidate)?.id === targetSymbol.id : isNodeOfType(candidate, "ObjectExpression") && objectExpressionPublishesSymbolAtPath(candidate, targetPath, targetSymbol.id, context))) return void 0;
|
|
74509
|
-
const mutationStart = getRangeStart(mutation.node);
|
|
74510
|
-
const updateStart = getRangeStart(updateExpression);
|
|
74511
|
-
const mutationFunction = findEnclosingFunction$1(mutation.node);
|
|
74512
|
-
if (mutationStart === null || updateStart === null) return void 0;
|
|
74513
|
-
let latestAssignment = null;
|
|
74514
|
-
let latestAssignmentStart = null;
|
|
74515
|
-
let latestConditionalAssignmentStart = null;
|
|
74516
|
-
for (const reference of targetSymbol.references) {
|
|
74517
|
-
if (reference.flag === "read") continue;
|
|
74518
|
-
const assignment = reference.identifier.parent;
|
|
74519
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier || findEnclosingFunction$1(assignment) !== mutationFunction) continue;
|
|
74520
|
-
const assignmentStart = getRangeStart(assignment);
|
|
74521
|
-
if (assignmentStart === null || assignmentStart <= mutationStart || assignmentStart >= updateStart) continue;
|
|
74522
|
-
const pathCompatibility = rebindPathCompatibility(assignment, mutation);
|
|
74523
|
-
if (pathCompatibility === false) continue;
|
|
74524
|
-
if (pathCompatibility === null) {
|
|
74525
|
-
if (latestConditionalAssignmentStart === null || assignmentStart > latestConditionalAssignmentStart) latestConditionalAssignmentStart = assignmentStart;
|
|
74526
|
-
continue;
|
|
74527
|
-
}
|
|
74528
|
-
if (latestAssignmentStart !== null && assignmentStart <= latestAssignmentStart) continue;
|
|
74529
|
-
latestAssignment = assignment;
|
|
74530
|
-
latestAssignmentStart = assignmentStart;
|
|
74531
|
-
}
|
|
74532
|
-
if (latestConditionalAssignmentStart !== null && (latestAssignmentStart === null || latestConditionalAssignmentStart > latestAssignmentStart)) return null;
|
|
74533
|
-
if (!latestAssignment) return void 0;
|
|
74534
|
-
if (expressionKeyPreservesTarget(latestAssignment.right, targetKey, context)) return false;
|
|
74535
|
-
return isProvenFreshReplacementExpression(latestAssignment.right, targetKey, context) ? true : null;
|
|
74536
|
-
};
|
|
74537
|
-
const updateTargetReplacementDisposition = (updateExpression, mutation, context) => {
|
|
74538
|
-
const targetKey = resolveExpressionKey$1(mutation.receiver, context);
|
|
74539
|
-
const targetPath = staticPropertyPathForExpression(mutation.receiver, context, /* @__PURE__ */ new Set(), true);
|
|
74540
|
-
if (!targetPath) return null;
|
|
74541
|
-
const candidate = stripParenExpression(updateExpression);
|
|
74542
|
-
if (!targetKey) {
|
|
74543
|
-
if (targetPath.length === 0) return null;
|
|
74544
|
-
if (!isNodeOfType(candidate, "ObjectExpression")) return staticPathPreservesTarget(staticPropertyPathForExpression(candidate, context), targetPath) ? false : null;
|
|
74545
|
-
return objectTargetPathReplacementDisposition(candidate, targetPath, true, context);
|
|
74546
|
-
}
|
|
74547
|
-
if (targetPath.length === 0) {
|
|
74548
|
-
if (expressionPreservesTarget(candidate, targetKey, mutation.node, context)) {
|
|
74549
|
-
const rebindDisposition = targetRebindReplacementDisposition(candidate, mutation, targetPath, targetKey, context);
|
|
74550
|
-
return rebindDisposition === void 0 ? false : rebindDisposition;
|
|
74551
|
-
}
|
|
74552
|
-
return isProvenFreshReplacementExpression(candidate, targetKey, context) ? true : null;
|
|
74553
|
-
}
|
|
74554
|
-
if (!isNodeOfType(candidate, "ObjectExpression")) return expressionPreservesTarget(candidate, targetKey, mutation.node, context) ? false : null;
|
|
74555
|
-
const ancestorKeySuffix = `.${targetPath.join(".")}`;
|
|
74556
|
-
const disposition = objectTargetReplacementDisposition(candidate, targetPath, targetKey, targetKey.endsWith(ancestorKeySuffix) ? targetKey.slice(0, -ancestorKeySuffix.length) : targetKey, mutation.node, true, context);
|
|
74557
|
-
if (disposition !== false) return disposition;
|
|
74558
|
-
const rebindDisposition = targetRebindReplacementDisposition(candidate, mutation, targetPath, targetKey, context);
|
|
74559
|
-
return rebindDisposition === void 0 ? false : rebindDisposition;
|
|
74560
|
-
};
|
|
74561
|
-
const notifierTargetReplacementDisposition = (notifierCall, mutation, context) => {
|
|
74562
|
-
const updateArgument = notifierCall.arguments[0];
|
|
74563
|
-
if (!updateArgument) return false;
|
|
74564
|
-
if (isNodeOfType(updateArgument, "SpreadElement")) return null;
|
|
74565
|
-
const updateFunction = resolveExactLocalFunction(updateArgument, context.scopes);
|
|
74566
|
-
const dispositions = (updateFunction ? returnedExpressionsForFunction(updateFunction) : [updateArgument]).map((expression) => updateTargetReplacementDisposition(expression, mutation, context));
|
|
74567
|
-
if (dispositions.some((disposition) => disposition === true)) return true;
|
|
74568
|
-
if (dispositions.some((disposition) => disposition === null)) return null;
|
|
74569
|
-
return false;
|
|
74570
|
-
};
|
|
74571
|
-
const sequentialNotifierTargetReplacementDisposition = (previousDisposition, nextDisposition) => {
|
|
74572
|
-
if (previousDisposition === true || nextDisposition === true) return true;
|
|
74573
|
-
if (previousDisposition === null || nextDisposition === null) return null;
|
|
74574
|
-
return false;
|
|
74575
|
-
};
|
|
74576
|
-
const notifierFlowTargetReplacementDisposition = (statement, mutation, setSymbolIds, storeSymbolIds, context) => {
|
|
74577
|
-
if (isNodeOfType(statement, "BlockStatement")) return statement.body.reduce((disposition, childStatement) => sequentialNotifierTargetReplacementDisposition(disposition, notifierFlowTargetReplacementDisposition(childStatement, mutation, setSymbolIds, storeSymbolIds, context)), false);
|
|
74578
|
-
if (isNodeOfType(statement, "IfStatement")) {
|
|
74579
|
-
const consequentDisposition = notifierFlowTargetReplacementDisposition(statement.consequent, mutation, setSymbolIds, storeSymbolIds, context);
|
|
74580
|
-
const alternateDisposition = statement.alternate ? notifierFlowTargetReplacementDisposition(statement.alternate, mutation, setSymbolIds, storeSymbolIds, context) : false;
|
|
74581
|
-
if (consequentDisposition === false || alternateDisposition === false) return false;
|
|
74582
|
-
return consequentDisposition === true && alternateDisposition === true ? true : null;
|
|
74583
|
-
}
|
|
74584
|
-
return collectNotifierCalls(statement, setSymbolIds, storeSymbolIds, context).reduce((disposition, callExpression) => sequentialNotifierTargetReplacementDisposition(disposition, notifierTargetReplacementDisposition(callExpression, mutation, context)), false);
|
|
74585
|
-
};
|
|
74586
|
-
const collectConditionalNotifierGroups = (statement, statementIndex) => {
|
|
74587
|
-
const groups = [];
|
|
74588
|
-
walkAst(statement, (node) => {
|
|
74589
|
-
if (isFunctionLike$1(node)) return false;
|
|
74590
|
-
if (isNodeOfType(node, "IfStatement")) groups.push({
|
|
74591
|
-
statement: node,
|
|
74592
|
-
statementIndex
|
|
74593
|
-
});
|
|
74594
|
-
});
|
|
74595
|
-
return groups;
|
|
74596
|
-
};
|
|
74597
|
-
const updateSnapshotStateForStatement = (statement, state) => {
|
|
74598
|
-
if (isNodeOfType(statement, "VariableDeclaration")) {
|
|
74599
|
-
updateMutableStateReferencesForVariableDeclaration(statement, state);
|
|
74600
|
-
return;
|
|
74601
|
-
}
|
|
74602
|
-
if (!isNodeOfType(statement, "ExpressionStatement")) return;
|
|
74603
|
-
const assignment = stripParenExpression(statement.expression);
|
|
74604
|
-
if (!isNodeOfType(assignment, "AssignmentExpression")) return;
|
|
74605
|
-
updateMutableStateReferencesForIdentifierAssignment(assignment, state);
|
|
74606
|
-
};
|
|
74607
|
-
const collectSequentialSnapshotMutations = (branchRoot, state, creatorFunction, context) => {
|
|
74608
|
-
const branchState = { mutableStateSourceNames: new Set(state.mutableStateSourceNames) };
|
|
74609
|
-
if (state.isAdditionalMutableStateSource) branchState.isAdditionalMutableStateSource = state.isAdditionalMutableStateSource;
|
|
74610
|
-
const statements = isNodeOfType(branchRoot, "BlockStatement") ? branchRoot.body : [branchRoot];
|
|
74611
|
-
const mutations = [];
|
|
74612
|
-
const mutationOptions = { isProvenMutatingMethodCall: (callExpression) => isProvenZustandMutatingMethodCall(callExpression, creatorFunction, context) };
|
|
74613
|
-
for (const statement of statements) {
|
|
74614
|
-
if (isNodeOfType(statement, "IfStatement")) {
|
|
74615
|
-
mutations.push(...collectSequentialSnapshotMutations(statement.consequent, branchState, creatorFunction, context));
|
|
74616
|
-
if (statement.alternate) mutations.push(...collectSequentialSnapshotMutations(statement.alternate, branchState, creatorFunction, context));
|
|
74617
|
-
continue;
|
|
74618
|
-
}
|
|
74619
|
-
mutations.push(...collectMutableStateReferenceMutations(statement, branchState, mutationOptions));
|
|
74620
|
-
updateSnapshotStateForStatement(statement, branchState);
|
|
74621
|
-
}
|
|
74622
|
-
return mutations;
|
|
74623
|
-
};
|
|
74624
|
-
const analyzeSnapshotContainer = (statements, getSymbolIds, setSymbolIds, snapshotStoreSymbolIds, notifierStoreSymbolIds, creatorFunction, context, reportedNodes, returnedUpdateExpressions = []) => {
|
|
74625
|
-
if (hasUnsupportedSnapshotControlFlow(statements)) return;
|
|
74626
|
-
const state = {
|
|
74627
|
-
isAdditionalMutableStateSource: (expression) => isSnapshotExpression(expression, getSymbolIds, snapshotStoreSymbolIds, context),
|
|
74628
|
-
mutableStateSourceNames: /* @__PURE__ */ new Set()
|
|
74629
|
-
};
|
|
74630
|
-
const mutations = [];
|
|
74631
|
-
const notifierCalls = [];
|
|
74632
|
-
const conditionalNotifierGroups = [];
|
|
74633
|
-
const mutationOptions = { isProvenMutatingMethodCall: (callExpression) => isProvenZustandMutatingMethodCall(callExpression, creatorFunction, context) };
|
|
74634
|
-
for (let statementIndex = 0; statementIndex < statements.length; statementIndex += 1) {
|
|
74635
|
-
const statement = statements[statementIndex];
|
|
74636
|
-
if (isNodeOfType(statement, "IfStatement")) for (const branchRoot of [statement.consequent, statement.alternate]) {
|
|
74637
|
-
if (!branchRoot) continue;
|
|
74638
|
-
for (const mutation of collectSequentialSnapshotMutations(branchRoot, state, creatorFunction, context)) mutations.push({
|
|
74639
|
-
branchRoot,
|
|
74640
|
-
mutation,
|
|
74641
|
-
statementIndex
|
|
74642
|
-
});
|
|
74643
|
-
const calls = collectNotifierCalls(branchRoot, setSymbolIds, notifierStoreSymbolIds, context);
|
|
74644
|
-
for (const callExpression of calls) notifierCalls.push({
|
|
74645
|
-
branchRoot,
|
|
74646
|
-
callExpression,
|
|
74647
|
-
statementIndex
|
|
74648
|
-
});
|
|
74649
|
-
}
|
|
74650
|
-
else {
|
|
74651
|
-
for (const mutation of collectMutableStateReferenceMutations(statement, state, mutationOptions)) mutations.push({
|
|
74652
|
-
branchRoot: null,
|
|
74653
|
-
mutation,
|
|
74654
|
-
statementIndex
|
|
74655
|
-
});
|
|
74656
|
-
for (const callExpression of collectNotifierCalls(statement, setSymbolIds, notifierStoreSymbolIds, context)) notifierCalls.push({
|
|
74657
|
-
branchRoot: null,
|
|
74658
|
-
callExpression,
|
|
74659
|
-
statementIndex
|
|
74660
|
-
});
|
|
74661
|
-
}
|
|
74662
|
-
conditionalNotifierGroups.push(...collectConditionalNotifierGroups(statement, statementIndex));
|
|
74663
|
-
updateSnapshotStateForStatement(statement, state);
|
|
74664
|
-
if (isNodeOfType(statement, "ReturnStatement")) break;
|
|
74665
|
-
}
|
|
74666
|
-
for (const { branchRoot, mutation, statementIndex } of mutations) {
|
|
74667
|
-
const replacementDispositions = [...notifierCalls.filter((notifier) => {
|
|
74668
|
-
if (!notifier.branchRoot) {
|
|
74669
|
-
if (notifier.statementIndex !== statementIndex) return notifier.statementIndex > statementIndex;
|
|
74670
|
-
const mutationStart = getRangeStart(mutation.node);
|
|
74671
|
-
const notifierStart = getRangeStart(notifier.callExpression);
|
|
74672
|
-
return isAstDescendant(mutation.node, notifier.callExpression) || mutationStart !== null && notifierStart !== null && notifierStart >= mutationStart;
|
|
74673
|
-
}
|
|
74674
|
-
if (notifier.statementIndex !== statementIndex || notifier.branchRoot !== branchRoot) return false;
|
|
74675
|
-
if (branchPathCompatibility(notifier.callExpression, mutation) !== true) return false;
|
|
74676
|
-
const mutationStart = getRangeStart(mutation.node);
|
|
74677
|
-
const notifierStart = getRangeStart(notifier.callExpression);
|
|
74678
|
-
return isAstDescendant(mutation.node, notifier.callExpression) || mutationStart !== null && notifierStart !== null && notifierStart >= mutationStart;
|
|
74679
|
-
}).map((notifier) => notifierTargetReplacementDisposition(notifier.callExpression, mutation, context)), ...returnedUpdateExpressions.map((expression) => updateTargetReplacementDisposition(expression, mutation, context))];
|
|
74680
|
-
for (const group of conditionalNotifierGroups) {
|
|
74681
|
-
if (group.statementIndex < statementIndex) continue;
|
|
74682
|
-
const mutationStart = getRangeStart(mutation.node);
|
|
74683
|
-
const groupStart = getRangeStart(group.statement);
|
|
74684
|
-
if (mutationStart === null || groupStart === null || groupStart <= mutationStart || branchPathCompatibility(group.statement, mutation) !== true) continue;
|
|
74685
|
-
replacementDispositions.push(notifierFlowTargetReplacementDisposition(group.statement, mutation, setSymbolIds, notifierStoreSymbolIds, context));
|
|
74686
|
-
}
|
|
74687
|
-
if (replacementDispositions.some((disposition) => disposition !== false)) continue;
|
|
74688
|
-
if (reportedNodes.has(mutation.node)) continue;
|
|
74689
|
-
reportedNodes.add(mutation.node);
|
|
74690
|
-
context.report({
|
|
74691
|
-
node: mutation.node,
|
|
74692
|
-
message: MESSAGE
|
|
74693
|
-
});
|
|
74694
|
-
}
|
|
74695
|
-
};
|
|
74696
|
-
const zustandNoMutatingState = defineRule({
|
|
74697
|
-
id: "zustand-no-mutating-state",
|
|
74698
|
-
title: "Zustand state mutated in place",
|
|
74699
|
-
severity: "error",
|
|
74700
|
-
category: "Correctness",
|
|
74701
|
-
recommendation: "Create a new object, array, Map, or Set before passing the updated value to Zustand.",
|
|
74702
|
-
requires: ["zustand", "zustand:1"],
|
|
74703
|
-
create: (context) => {
|
|
74704
|
-
const creatorBindings = /* @__PURE__ */ new Map();
|
|
74705
|
-
const bindingsWithBoundStoreNotifier = /* @__PURE__ */ new Set();
|
|
74706
|
-
const functionContainers = /* @__PURE__ */ new Set();
|
|
74707
|
-
const updaterFunctionNotifierSymbolIds = /* @__PURE__ */ new Map();
|
|
74708
|
-
const recordUpdaterFunctionNotifier = (updaterFunction, notifierSymbolId) => {
|
|
74709
|
-
const notifierSymbolIds = updaterFunctionNotifierSymbolIds.get(updaterFunction);
|
|
74710
|
-
if (notifierSymbolIds) notifierSymbolIds.add(notifierSymbolId);
|
|
74711
|
-
else updaterFunctionNotifierSymbolIds.set(updaterFunction, new Set([notifierSymbolId]));
|
|
74712
|
-
};
|
|
74713
|
-
const reportedNodes = /* @__PURE__ */ new WeakSet();
|
|
74714
|
-
let programNode = null;
|
|
74715
|
-
return {
|
|
74716
|
-
Program(node) {
|
|
74717
|
-
programNode = node;
|
|
74718
|
-
},
|
|
74719
|
-
CallExpression(node) {
|
|
74720
|
-
const creator = resolveZustandStoreCreator(node, context.scopes);
|
|
74721
|
-
if (!creator) return;
|
|
74722
|
-
const hasNonImmerUsage = !creator.middlewareNames.has("immer");
|
|
74723
|
-
let binding = creatorBindings.get(creator.creatorFunction);
|
|
74724
|
-
if (binding) {
|
|
74725
|
-
if (hasNonImmerUsage) binding.hasNonImmerUsage = true;
|
|
74726
|
-
} else {
|
|
74727
|
-
binding = {
|
|
74728
|
-
creatorFunction: creator.creatorFunction,
|
|
74729
|
-
getSymbol: symbolForParameter(creator.creatorFunction, 1, context),
|
|
74730
|
-
hasNonImmerUsage,
|
|
74731
|
-
nonImmerStoreSymbolIds: /* @__PURE__ */ new Set(),
|
|
74732
|
-
setSymbol: symbolForParameter(creator.creatorFunction, 0, context),
|
|
74733
|
-
storeSymbolIds: /* @__PURE__ */ new Set()
|
|
74734
|
-
};
|
|
74735
|
-
creatorBindings.set(creator.creatorFunction, binding);
|
|
74736
|
-
}
|
|
74737
|
-
const parent = node.parent;
|
|
74738
|
-
if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) {
|
|
74739
|
-
const storeSymbol = context.scopes.symbolFor(parent.id);
|
|
74740
|
-
if (storeSymbol) {
|
|
74741
|
-
binding.storeSymbolIds.add(storeSymbol.id);
|
|
74742
|
-
if (hasNonImmerUsage) binding.nonImmerStoreSymbolIds.add(storeSymbol.id);
|
|
74743
|
-
}
|
|
74744
|
-
}
|
|
74745
|
-
},
|
|
74746
|
-
ArrowFunctionExpression(node) {
|
|
74747
|
-
functionContainers.add(node);
|
|
74748
|
-
},
|
|
74749
|
-
FunctionDeclaration(node) {
|
|
74750
|
-
functionContainers.add(node);
|
|
74751
|
-
},
|
|
74752
|
-
FunctionExpression(node) {
|
|
74753
|
-
functionContainers.add(node);
|
|
74754
|
-
},
|
|
74755
|
-
"Program:exit"() {
|
|
74756
|
-
for (const binding of creatorBindings.values()) {
|
|
74757
|
-
if (binding.hasNonImmerUsage && binding.setSymbol) {
|
|
74758
|
-
const setSymbolId = binding.setSymbol.id;
|
|
74759
|
-
const creatorSetSymbolIds = new Set([setSymbolId]);
|
|
74760
|
-
walkAst(binding.creatorFunction.body, (node) => {
|
|
74761
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
74762
|
-
if (!isCallToSymbol(node, creatorSetSymbolIds, context)) return;
|
|
74763
|
-
const updaterArgument = node.arguments[0];
|
|
74764
|
-
if (!updaterArgument || isNodeOfType(updaterArgument, "SpreadElement")) return;
|
|
74765
|
-
const updaterFunction = resolveExactLocalFunction(updaterArgument, context.scopes);
|
|
74766
|
-
if (!updaterFunction) return;
|
|
74767
|
-
recordUpdaterFunctionNotifier(updaterFunction, setSymbolId);
|
|
74768
|
-
analyzeSetUpdater(updaterFunction, binding.getSymbol ? new Set([binding.getSymbol.id]) : /* @__PURE__ */ new Set(), binding.storeSymbolIds, binding.creatorFunction, context, reportedNodes);
|
|
74769
|
-
});
|
|
74770
|
-
}
|
|
74771
|
-
if (!programNode || binding.storeSymbolIds.size === 0) continue;
|
|
74772
|
-
walkAst(programNode, (node) => {
|
|
74773
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
74774
|
-
const storeSymbolId = storeSymbolIdForMethodCall(node, "setState", context);
|
|
74775
|
-
if (storeSymbolId === null || !binding.storeSymbolIds.has(storeSymbolId)) return;
|
|
74776
|
-
bindingsWithBoundStoreNotifier.add(binding);
|
|
74777
|
-
const updaterArgument = node.arguments[0];
|
|
74778
|
-
if (!updaterArgument || isNodeOfType(updaterArgument, "SpreadElement")) return;
|
|
74779
|
-
const updaterFunction = resolveExactLocalFunction(updaterArgument, context.scopes);
|
|
74780
|
-
if (!updaterFunction) return;
|
|
74781
|
-
recordUpdaterFunctionNotifier(updaterFunction, storeSymbolId);
|
|
74782
|
-
if (!binding.nonImmerStoreSymbolIds.has(storeSymbolId)) return;
|
|
74783
|
-
analyzeSetUpdater(updaterFunction, /* @__PURE__ */ new Set(), new Set([storeSymbolId]), binding.creatorFunction, context, reportedNodes);
|
|
74784
|
-
});
|
|
74785
|
-
}
|
|
74786
|
-
const analyzeProvenance = (getSymbolIds, setSymbolIds, snapshotStoreSymbolIds, notifierStoreSymbolIds, creatorFunction) => {
|
|
74787
|
-
if (programNode) analyzeSnapshotContainer(programNode.body, getSymbolIds, setSymbolIds, snapshotStoreSymbolIds, notifierStoreSymbolIds, creatorFunction, context, reportedNodes);
|
|
74788
|
-
for (const functionContainer of functionContainers) {
|
|
74789
|
-
if (!isFunctionLike$1(functionContainer)) continue;
|
|
74790
|
-
if (!isNodeOfType(functionContainer.body, "BlockStatement")) continue;
|
|
74791
|
-
const updaterNotifierSymbolIds = updaterFunctionNotifierSymbolIds.get(functionContainer);
|
|
74792
|
-
const isUpdaterNotifierForProvenance = Boolean(updaterNotifierSymbolIds && Array.from(updaterNotifierSymbolIds).some((notifierSymbolId) => setSymbolIds.has(notifierSymbolId) || notifierStoreSymbolIds.has(notifierSymbolId)));
|
|
74793
|
-
analyzeSnapshotContainer(functionContainer.body.body, getSymbolIds, setSymbolIds, snapshotStoreSymbolIds, notifierStoreSymbolIds, creatorFunction, context, reportedNodes, isUpdaterNotifierForProvenance ? returnedExpressionsForFunction(functionContainer) : []);
|
|
74794
|
-
}
|
|
74795
|
-
};
|
|
74796
|
-
for (const binding of creatorBindings.values()) {
|
|
74797
|
-
const getSymbolIds = /* @__PURE__ */ new Set();
|
|
74798
|
-
const setSymbolIds = /* @__PURE__ */ new Set();
|
|
74799
|
-
if (binding.getSymbol && binding.setSymbol && (binding.setSymbol.references.length > 0 || bindingsWithBoundStoreNotifier.has(binding))) getSymbolIds.add(binding.getSymbol.id);
|
|
74800
|
-
if (binding.setSymbol) setSymbolIds.add(binding.setSymbol.id);
|
|
74801
|
-
analyzeProvenance(getSymbolIds, setSymbolIds, /* @__PURE__ */ new Set(), binding.storeSymbolIds, binding.creatorFunction);
|
|
74802
|
-
for (const storeSymbolId of binding.storeSymbolIds) {
|
|
74803
|
-
const storeSymbolIds = new Set([storeSymbolId]);
|
|
74804
|
-
analyzeProvenance(/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set(), storeSymbolIds, storeSymbolIds, binding.creatorFunction);
|
|
74805
|
-
}
|
|
74806
|
-
}
|
|
74807
|
-
}
|
|
74808
|
-
};
|
|
74809
|
-
}
|
|
74810
|
-
});
|
|
74811
|
-
//#endregion
|
|
74812
|
-
//#region src/plugin/rules/state-and-effects/zustand-no-whole-store-destructure.ts
|
|
74813
|
-
const BOUND_STORE_FACTORY_APIS = new Set(["create", "createWithEqualityFn"]);
|
|
74814
|
-
const VANILLA_STORE_FACTORY_APIS = new Set(["createStore"]);
|
|
74815
|
-
const isStoreFactoryValue = (rawValue, factoryApiNames, scopes) => {
|
|
74816
|
-
const value = stripParenExpression(rawValue);
|
|
74817
|
-
if (!isNodeOfType(value, "CallExpression")) return false;
|
|
74818
|
-
const factoryCall = resolveZustandStoreFactoryCall(value, scopes);
|
|
74819
|
-
return Boolean(factoryCall && factoryApiNames.has(factoryCall.factoryApiName));
|
|
74820
|
-
};
|
|
74821
|
-
const isStoreValue = (rawValue, factoryApiNames, scopes) => {
|
|
74822
|
-
const value = stripParenExpression(rawValue);
|
|
74823
|
-
if (isStoreFactoryValue(value, factoryApiNames, scopes)) return true;
|
|
74824
|
-
if (!isNodeOfType(value, "Identifier")) return false;
|
|
74825
|
-
const symbol = resolveConstIdentifierAlias(value, scopes);
|
|
74826
|
-
return Boolean(symbol?.kind === "const" && symbol.initializer && isStoreFactoryValue(symbol.initializer, factoryApiNames, scopes));
|
|
74827
|
-
};
|
|
74828
|
-
const isBoundStoreCall = (node, scopes) => {
|
|
74829
|
-
const callee = stripParenExpression(node.callee);
|
|
74830
|
-
return isNodeOfType(callee, "Identifier") && isStoreValue(callee, BOUND_STORE_FACTORY_APIS, scopes);
|
|
74831
|
-
};
|
|
74832
|
-
const isVanillaStoreHookCall = (node, scopes) => {
|
|
74833
|
-
if (node.arguments.length !== 1) return false;
|
|
74834
|
-
const hook = resolveZustandApiBinding(node.callee, scopes);
|
|
74835
|
-
if (hook?.apiName !== "useStore" && hook?.apiName !== "useStoreWithEqualityFn") return false;
|
|
74836
|
-
return isStoreValue(node.arguments[0], VANILLA_STORE_FACTORY_APIS, scopes);
|
|
74837
|
-
};
|
|
74838
|
-
const isDirectReactRenderCall = (node, context) => {
|
|
74839
|
-
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
74840
|
-
if (!renderFunction || findEnclosingFunction$1(node) !== renderFunction) return false;
|
|
74841
|
-
const displayName = componentOrHookDisplayNameForFunction(renderFunction);
|
|
74842
|
-
return Boolean(displayName && (isReactHookName(displayName) || functionHasReactComponentEvidence(renderFunction, context.scopes, context.cfg)));
|
|
74843
|
-
};
|
|
74844
|
-
const zustandNoWholeStoreDestructure = defineRule({
|
|
74845
|
-
id: "zustand-no-whole-store-destructure",
|
|
74846
|
-
title: "Whole Zustand store subscribed during render",
|
|
74847
|
-
severity: "warn",
|
|
74848
|
-
requires: ["zustand:1"],
|
|
74849
|
-
recommendation: "Pass a selector to the Zustand hook so this component rerenders only when the state it reads changes.",
|
|
74850
|
-
create: (context) => ({ CallExpression(node) {
|
|
74851
|
-
if (!isDirectReactRenderCall(node, context)) return;
|
|
74852
|
-
if (!(node.arguments.length === 0 && isBoundStoreCall(node, context.scopes)) && !isVanillaStoreHookCall(node, context.scopes)) return;
|
|
74853
|
-
context.report({
|
|
74854
|
-
node,
|
|
74855
|
-
message: "This hook subscribes to the whole Zustand store, so every store update rerenders this component. Pass a selector for the state it reads."
|
|
74856
|
-
});
|
|
74857
|
-
} })
|
|
74858
|
-
});
|
|
74859
|
-
//#endregion
|
|
74860
73157
|
//#region src/plugin/rule-registry.ts
|
|
74861
73158
|
const reactDoctorRules = [
|
|
74862
73159
|
{
|
|
@@ -76171,39 +74468,6 @@ const reactDoctorRules = [
|
|
|
76171
74468
|
requires: [...new Set(["react", ...mediaHasCaption.requires ?? []])]
|
|
76172
74469
|
}
|
|
76173
74470
|
},
|
|
76174
|
-
{
|
|
76175
|
-
key: "react-doctor/mobx-no-make-auto-observable-in-inheritance",
|
|
76176
|
-
id: "mobx-no-make-auto-observable-in-inheritance",
|
|
76177
|
-
source: "react-doctor",
|
|
76178
|
-
originallyExternal: false,
|
|
76179
|
-
rule: {
|
|
76180
|
-
...mobxNoMakeAutoObservableInInheritance,
|
|
76181
|
-
framework: "global",
|
|
76182
|
-
category: "Bugs"
|
|
76183
|
-
}
|
|
76184
|
-
},
|
|
76185
|
-
{
|
|
76186
|
-
key: "react-doctor/mobx-no-observer-wrapped-memo",
|
|
76187
|
-
id: "mobx-no-observer-wrapped-memo",
|
|
76188
|
-
source: "react-doctor",
|
|
76189
|
-
originallyExternal: false,
|
|
76190
|
-
rule: {
|
|
76191
|
-
...mobxNoObserverWrappedMemo,
|
|
76192
|
-
framework: "global",
|
|
76193
|
-
category: "Bugs"
|
|
76194
|
-
}
|
|
76195
|
-
},
|
|
76196
|
-
{
|
|
76197
|
-
key: "react-doctor/mobx-reaction-disposer-discarded",
|
|
76198
|
-
id: "mobx-reaction-disposer-discarded",
|
|
76199
|
-
source: "react-doctor",
|
|
76200
|
-
originallyExternal: false,
|
|
76201
|
-
rule: {
|
|
76202
|
-
...mobxReactionDisposerDiscarded,
|
|
76203
|
-
framework: "global",
|
|
76204
|
-
category: "Bugs"
|
|
76205
|
-
}
|
|
76206
|
-
},
|
|
76207
74471
|
{
|
|
76208
74472
|
key: "react-doctor/mouse-events-have-key-events",
|
|
76209
74473
|
id: "mouse-events-have-key-events",
|
|
@@ -79699,54 +77963,6 @@ const reactDoctorRules = [
|
|
|
79699
77963
|
framework: "global",
|
|
79700
77964
|
category: "Maintainability"
|
|
79701
77965
|
}
|
|
79702
|
-
},
|
|
79703
|
-
{
|
|
79704
|
-
key: "react-doctor/zustand-no-fresh-selector-result",
|
|
79705
|
-
id: "zustand-no-fresh-selector-result",
|
|
79706
|
-
source: "react-doctor",
|
|
79707
|
-
originallyExternal: false,
|
|
79708
|
-
rule: {
|
|
79709
|
-
...zustandNoFreshSelectorResult,
|
|
79710
|
-
framework: "global",
|
|
79711
|
-
category: "Performance",
|
|
79712
|
-
requires: [...new Set(["react", ...zustandNoFreshSelectorResult.requires ?? []])]
|
|
79713
|
-
}
|
|
79714
|
-
},
|
|
79715
|
-
{
|
|
79716
|
-
key: "react-doctor/zustand-no-get-during-initialization",
|
|
79717
|
-
id: "zustand-no-get-during-initialization",
|
|
79718
|
-
source: "react-doctor",
|
|
79719
|
-
originallyExternal: false,
|
|
79720
|
-
rule: {
|
|
79721
|
-
...zustandNoGetDuringInitialization,
|
|
79722
|
-
framework: "global",
|
|
79723
|
-
category: "Bugs",
|
|
79724
|
-
requires: [...new Set(["react", ...zustandNoGetDuringInitialization.requires ?? []])]
|
|
79725
|
-
}
|
|
79726
|
-
},
|
|
79727
|
-
{
|
|
79728
|
-
key: "react-doctor/zustand-no-mutating-state",
|
|
79729
|
-
id: "zustand-no-mutating-state",
|
|
79730
|
-
source: "react-doctor",
|
|
79731
|
-
originallyExternal: false,
|
|
79732
|
-
rule: {
|
|
79733
|
-
...zustandNoMutatingState,
|
|
79734
|
-
framework: "global",
|
|
79735
|
-
category: "Bugs",
|
|
79736
|
-
requires: [...new Set(["react", ...zustandNoMutatingState.requires ?? []])]
|
|
79737
|
-
}
|
|
79738
|
-
},
|
|
79739
|
-
{
|
|
79740
|
-
key: "react-doctor/zustand-no-whole-store-destructure",
|
|
79741
|
-
id: "zustand-no-whole-store-destructure",
|
|
79742
|
-
source: "react-doctor",
|
|
79743
|
-
originallyExternal: false,
|
|
79744
|
-
rule: {
|
|
79745
|
-
...zustandNoWholeStoreDestructure,
|
|
79746
|
-
framework: "global",
|
|
79747
|
-
category: "Bugs",
|
|
79748
|
-
requires: [...new Set(["react", ...zustandNoWholeStoreDestructure.requires ?? []])]
|
|
79749
|
-
}
|
|
79750
77966
|
}
|
|
79751
77967
|
];
|
|
79752
77968
|
const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id, rule.rule]));
|