oxlint-plugin-react-doctor 0.8.1-dev.90e794f → 0.8.1-dev.a4eaedd
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 +47 -1
- package/dist/index.js +406 -152
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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
|
}
|
|
@@ -10257,7 +10257,7 @@ const getClassNameLiteral = (classAttribute) => {
|
|
|
10257
10257
|
};
|
|
10258
10258
|
//#endregion
|
|
10259
10259
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
10260
|
-
const MESSAGE$
|
|
10260
|
+
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`.";
|
|
10261
10261
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
10262
10262
|
"td",
|
|
10263
10263
|
"th",
|
|
@@ -10787,7 +10787,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
10787
10787
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
10788
10788
|
context.report({
|
|
10789
10789
|
node: candidate.opening,
|
|
10790
|
-
message: MESSAGE$
|
|
10790
|
+
message: MESSAGE$58
|
|
10791
10791
|
});
|
|
10792
10792
|
}
|
|
10793
10793
|
}
|
|
@@ -12594,7 +12594,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
12594
12594
|
});
|
|
12595
12595
|
//#endregion
|
|
12596
12596
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
12597
|
-
const MESSAGE$
|
|
12597
|
+
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.";
|
|
12598
12598
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
12599
12599
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
12600
12600
|
"aria-label",
|
|
@@ -12619,7 +12619,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
12619
12619
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
12620
12620
|
context.report({
|
|
12621
12621
|
node: node.name,
|
|
12622
|
-
message: MESSAGE$
|
|
12622
|
+
message: MESSAGE$57
|
|
12623
12623
|
});
|
|
12624
12624
|
} };
|
|
12625
12625
|
}
|
|
@@ -12659,7 +12659,7 @@ const isEs6Component = (node) => {
|
|
|
12659
12659
|
};
|
|
12660
12660
|
//#endregion
|
|
12661
12661
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
12662
|
-
const MESSAGE$
|
|
12662
|
+
const MESSAGE$56 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
12663
12663
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
12664
12664
|
"observer",
|
|
12665
12665
|
"lazy",
|
|
@@ -12852,7 +12852,7 @@ const displayName = defineRule({
|
|
|
12852
12852
|
const reportAt = (node) => {
|
|
12853
12853
|
context.report({
|
|
12854
12854
|
node,
|
|
12855
|
-
message: MESSAGE$
|
|
12855
|
+
message: MESSAGE$56
|
|
12856
12856
|
});
|
|
12857
12857
|
};
|
|
12858
12858
|
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
|
}
|
|
@@ -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,7 +28178,7 @@ 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
|
}
|
|
@@ -30231,7 +30231,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
30231
30231
|
});
|
|
30232
30232
|
//#endregion
|
|
30233
30233
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
30234
|
-
const MESSAGE$
|
|
30234
|
+
const MESSAGE$40 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
30235
30235
|
const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
30236
30236
|
const noAccessKey = defineRule({
|
|
30237
30237
|
id: "no-access-key",
|
|
@@ -30250,7 +30250,7 @@ const noAccessKey = defineRule({
|
|
|
30250
30250
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
30251
30251
|
context.report({
|
|
30252
30252
|
node: accessKey,
|
|
30253
|
-
message: MESSAGE$
|
|
30253
|
+
message: MESSAGE$40
|
|
30254
30254
|
});
|
|
30255
30255
|
return;
|
|
30256
30256
|
}
|
|
@@ -30260,7 +30260,7 @@ const noAccessKey = defineRule({
|
|
|
30260
30260
|
if (isUndefinedIdentifier$1(expression)) return;
|
|
30261
30261
|
context.report({
|
|
30262
30262
|
node: accessKey,
|
|
30263
|
-
message: MESSAGE$
|
|
30263
|
+
message: MESSAGE$40
|
|
30264
30264
|
});
|
|
30265
30265
|
}
|
|
30266
30266
|
} };
|
|
@@ -32603,7 +32603,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
32603
32603
|
});
|
|
32604
32604
|
//#endregion
|
|
32605
32605
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
32606
|
-
const MESSAGE$
|
|
32606
|
+
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.";
|
|
32607
32607
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
32608
32608
|
"button",
|
|
32609
32609
|
"embed",
|
|
@@ -32715,7 +32715,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
32715
32715
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
32716
32716
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
32717
32717
|
node: ariaHidden,
|
|
32718
|
-
message: MESSAGE$
|
|
32718
|
+
message: MESSAGE$39
|
|
32719
32719
|
});
|
|
32720
32720
|
} })
|
|
32721
32721
|
});
|
|
@@ -33762,7 +33762,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
33762
33762
|
};
|
|
33763
33763
|
//#endregion
|
|
33764
33764
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
33765
|
-
const MESSAGE$
|
|
33765
|
+
const MESSAGE$38 = "Your users can see & submit the wrong data when this list reorders.";
|
|
33766
33766
|
const SECOND_INDEX_METHODS = new Set([
|
|
33767
33767
|
"every",
|
|
33768
33768
|
"filter",
|
|
@@ -33881,14 +33881,14 @@ const noArrayIndexKey = defineRule({
|
|
|
33881
33881
|
if (propName !== "key") continue;
|
|
33882
33882
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
33883
33883
|
node: property,
|
|
33884
|
-
message: MESSAGE$
|
|
33884
|
+
message: MESSAGE$38
|
|
33885
33885
|
});
|
|
33886
33886
|
}
|
|
33887
33887
|
} })
|
|
33888
33888
|
});
|
|
33889
33889
|
//#endregion
|
|
33890
33890
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
33891
|
-
const MESSAGE$
|
|
33891
|
+
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.";
|
|
33892
33892
|
const noAsyncEffectCallback = defineRule({
|
|
33893
33893
|
id: "no-async-effect-callback",
|
|
33894
33894
|
title: "Async effect callback",
|
|
@@ -33906,13 +33906,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
33906
33906
|
if (!callback.async) return;
|
|
33907
33907
|
context.report({
|
|
33908
33908
|
node: callback,
|
|
33909
|
-
message: MESSAGE$
|
|
33909
|
+
message: MESSAGE$37
|
|
33910
33910
|
});
|
|
33911
33911
|
} })
|
|
33912
33912
|
});
|
|
33913
33913
|
//#endregion
|
|
33914
33914
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
33915
|
-
const MESSAGE$
|
|
33915
|
+
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.";
|
|
33916
33916
|
const resolveSettings$21 = (settings) => {
|
|
33917
33917
|
const reactDoctor = settings?.["react-doctor"];
|
|
33918
33918
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -34009,7 +34009,7 @@ const noAutofocus = defineRule({
|
|
|
34009
34009
|
if (isConditionallyRendered(node)) return;
|
|
34010
34010
|
context.report({
|
|
34011
34011
|
node: autoFocusAttribute,
|
|
34012
|
-
message: MESSAGE$
|
|
34012
|
+
message: MESSAGE$36
|
|
34013
34013
|
});
|
|
34014
34014
|
} };
|
|
34015
34015
|
}
|
|
@@ -35338,7 +35338,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35338
35338
|
});
|
|
35339
35339
|
//#endregion
|
|
35340
35340
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
35341
|
-
const MESSAGE$
|
|
35341
|
+
const MESSAGE$35 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
35342
35342
|
const noChildrenProp = defineRule({
|
|
35343
35343
|
id: "no-children-prop",
|
|
35344
35344
|
title: "Children passed as a prop",
|
|
@@ -35350,7 +35350,7 @@ const noChildrenProp = defineRule({
|
|
|
35350
35350
|
if (node.name.name !== "children") return;
|
|
35351
35351
|
context.report({
|
|
35352
35352
|
node: node.name,
|
|
35353
|
-
message: MESSAGE$
|
|
35353
|
+
message: MESSAGE$35
|
|
35354
35354
|
});
|
|
35355
35355
|
},
|
|
35356
35356
|
CallExpression(node) {
|
|
@@ -35363,7 +35363,7 @@ const noChildrenProp = defineRule({
|
|
|
35363
35363
|
const propertyKey = property.key;
|
|
35364
35364
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
35365
35365
|
node: propertyKey,
|
|
35366
|
-
message: MESSAGE$
|
|
35366
|
+
message: MESSAGE$35
|
|
35367
35367
|
});
|
|
35368
35368
|
}
|
|
35369
35369
|
}
|
|
@@ -35371,7 +35371,7 @@ const noChildrenProp = defineRule({
|
|
|
35371
35371
|
});
|
|
35372
35372
|
//#endregion
|
|
35373
35373
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
35374
|
-
const MESSAGE$
|
|
35374
|
+
const MESSAGE$34 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
35375
35375
|
const noCloneElement = defineRule({
|
|
35376
35376
|
id: "no-clone-element",
|
|
35377
35377
|
title: "cloneElement makes child props fragile",
|
|
@@ -35384,7 +35384,7 @@ const noCloneElement = defineRule({
|
|
|
35384
35384
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
35385
35385
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
35386
35386
|
node: callee,
|
|
35387
|
-
message: MESSAGE$
|
|
35387
|
+
message: MESSAGE$34
|
|
35388
35388
|
});
|
|
35389
35389
|
return;
|
|
35390
35390
|
}
|
|
@@ -35397,7 +35397,7 @@ const noCloneElement = defineRule({
|
|
|
35397
35397
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
35398
35398
|
context.report({
|
|
35399
35399
|
node: callee,
|
|
35400
|
-
message: MESSAGE$
|
|
35400
|
+
message: MESSAGE$34
|
|
35401
35401
|
});
|
|
35402
35402
|
}
|
|
35403
35403
|
} })
|
|
@@ -35417,7 +35417,7 @@ const getCallMethodName = (callee) => {
|
|
|
35417
35417
|
};
|
|
35418
35418
|
//#endregion
|
|
35419
35419
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
35420
|
-
const MESSAGE$
|
|
35420
|
+
const MESSAGE$33 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
35421
35421
|
const CONTEXT_MODULES = [
|
|
35422
35422
|
"react",
|
|
35423
35423
|
"use-context-selector",
|
|
@@ -35465,7 +35465,7 @@ const noCreateContextInRender = defineRule({
|
|
|
35465
35465
|
if (!componentOrHookName) return;
|
|
35466
35466
|
context.report({
|
|
35467
35467
|
node,
|
|
35468
|
-
message: `${MESSAGE$
|
|
35468
|
+
message: `${MESSAGE$33} (called inside "${componentOrHookName}")`
|
|
35469
35469
|
});
|
|
35470
35470
|
} })
|
|
35471
35471
|
});
|
|
@@ -36212,7 +36212,7 @@ const isProvenOneShotTestingLibraryComponent = (functionNode, filename, scopes)
|
|
|
36212
36212
|
};
|
|
36213
36213
|
//#endregion
|
|
36214
36214
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
36215
|
-
const MESSAGE$
|
|
36215
|
+
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.";
|
|
36216
36216
|
const isUseMemoCallbackArgument = (functionNode, scopes) => {
|
|
36217
36217
|
const parent = functionNode.parent;
|
|
36218
36218
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -36266,7 +36266,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
36266
36266
|
if (isCreateRefResultWriteOnly(node, context.filename, context.scopes)) return;
|
|
36267
36267
|
context.report({
|
|
36268
36268
|
node,
|
|
36269
|
-
message: MESSAGE$
|
|
36269
|
+
message: MESSAGE$32
|
|
36270
36270
|
});
|
|
36271
36271
|
} })
|
|
36272
36272
|
});
|
|
@@ -36406,7 +36406,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
36406
36406
|
});
|
|
36407
36407
|
//#endregion
|
|
36408
36408
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
36409
|
-
const MESSAGE$
|
|
36409
|
+
const MESSAGE$31 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
36410
36410
|
const noDanger = defineRule({
|
|
36411
36411
|
id: "no-danger",
|
|
36412
36412
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -36420,7 +36420,7 @@ const noDanger = defineRule({
|
|
|
36420
36420
|
if (!propAttribute) return;
|
|
36421
36421
|
context.report({
|
|
36422
36422
|
node: propAttribute.name,
|
|
36423
|
-
message: MESSAGE$
|
|
36423
|
+
message: MESSAGE$31
|
|
36424
36424
|
});
|
|
36425
36425
|
},
|
|
36426
36426
|
CallExpression(node) {
|
|
@@ -36432,7 +36432,7 @@ const noDanger = defineRule({
|
|
|
36432
36432
|
const propertyKey = property.key;
|
|
36433
36433
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
36434
36434
|
node: propertyKey,
|
|
36435
|
-
message: MESSAGE$
|
|
36435
|
+
message: MESSAGE$31
|
|
36436
36436
|
});
|
|
36437
36437
|
}
|
|
36438
36438
|
}
|
|
@@ -36455,7 +36455,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
36455
36455
|
};
|
|
36456
36456
|
//#endregion
|
|
36457
36457
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
36458
|
-
const MESSAGE$
|
|
36458
|
+
const MESSAGE$30 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
36459
36459
|
const mergePropsShape = (target, source) => {
|
|
36460
36460
|
target.hasDangerously ||= source.hasDangerously;
|
|
36461
36461
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -36530,7 +36530,7 @@ const noDangerWithChildren = defineRule({
|
|
|
36530
36530
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
36531
36531
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
36532
36532
|
node: opening,
|
|
36533
|
-
message: MESSAGE$
|
|
36533
|
+
message: MESSAGE$30
|
|
36534
36534
|
});
|
|
36535
36535
|
},
|
|
36536
36536
|
CallExpression(node) {
|
|
@@ -36543,7 +36543,7 @@ const noDangerWithChildren = defineRule({
|
|
|
36543
36543
|
const positionalChildren = node.arguments.slice(2);
|
|
36544
36544
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
36545
36545
|
node,
|
|
36546
|
-
message: MESSAGE$
|
|
36546
|
+
message: MESSAGE$30
|
|
36547
36547
|
});
|
|
36548
36548
|
}
|
|
36549
36549
|
})
|
|
@@ -37518,7 +37518,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
37518
37518
|
//#endregion
|
|
37519
37519
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
37520
37520
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
37521
|
-
const MESSAGE$
|
|
37521
|
+
const MESSAGE$29 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
37522
37522
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
37523
37523
|
let ancestor = setStateCall.parent;
|
|
37524
37524
|
while (ancestor) {
|
|
@@ -37645,7 +37645,7 @@ const noDidMountSetState = defineRule({
|
|
|
37645
37645
|
}
|
|
37646
37646
|
context.report({
|
|
37647
37647
|
node: node.callee,
|
|
37648
|
-
message: MESSAGE$
|
|
37648
|
+
message: MESSAGE$29
|
|
37649
37649
|
});
|
|
37650
37650
|
} };
|
|
37651
37651
|
}
|
|
@@ -37663,7 +37663,7 @@ const findEnclosingClass = (node) => {
|
|
|
37663
37663
|
//#endregion
|
|
37664
37664
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
37665
37665
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
37666
|
-
const MESSAGE$
|
|
37666
|
+
const MESSAGE$28 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
37667
37667
|
const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
|
|
37668
37668
|
const EQUALITY_OPERATORS = new Set([
|
|
37669
37669
|
"==",
|
|
@@ -38057,7 +38057,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
38057
38057
|
if (isInsideDiffGuard(node, context.scopes)) return;
|
|
38058
38058
|
context.report({
|
|
38059
38059
|
node: node.callee,
|
|
38060
|
-
message: MESSAGE$
|
|
38060
|
+
message: MESSAGE$28
|
|
38061
38061
|
});
|
|
38062
38062
|
} };
|
|
38063
38063
|
}
|
|
@@ -38080,7 +38080,7 @@ const isStateMemberExpression = (node) => {
|
|
|
38080
38080
|
};
|
|
38081
38081
|
//#endregion
|
|
38082
38082
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
38083
|
-
const MESSAGE$
|
|
38083
|
+
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.";
|
|
38084
38084
|
const shouldIgnoreMutation = (node) => {
|
|
38085
38085
|
let isConstructor = false;
|
|
38086
38086
|
let isInsideCallExpression = false;
|
|
@@ -38102,7 +38102,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
38102
38102
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
38103
38103
|
context.report({
|
|
38104
38104
|
node: reportNode,
|
|
38105
|
-
message: MESSAGE$
|
|
38105
|
+
message: MESSAGE$27
|
|
38106
38106
|
});
|
|
38107
38107
|
};
|
|
38108
38108
|
const noDirectMutationState = defineRule({
|
|
@@ -38453,7 +38453,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
38453
38453
|
});
|
|
38454
38454
|
//#endregion
|
|
38455
38455
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
38456
|
-
const MESSAGE$
|
|
38456
|
+
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.";
|
|
38457
38457
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
38458
38458
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
38459
38459
|
const noDocumentWrite = defineRule({
|
|
@@ -38470,7 +38470,7 @@ const noDocumentWrite = defineRule({
|
|
|
38470
38470
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
38471
38471
|
context.report({
|
|
38472
38472
|
node,
|
|
38473
|
-
message: MESSAGE$
|
|
38473
|
+
message: MESSAGE$26
|
|
38474
38474
|
});
|
|
38475
38475
|
} })
|
|
38476
38476
|
});
|
|
@@ -41810,7 +41810,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
41810
41810
|
"ReactDOM",
|
|
41811
41811
|
"ReactDom"
|
|
41812
41812
|
]);
|
|
41813
|
-
const MESSAGE$
|
|
41813
|
+
const MESSAGE$25 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
41814
41814
|
const noFindDomNode = defineRule({
|
|
41815
41815
|
id: "no-find-dom-node",
|
|
41816
41816
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -41821,7 +41821,7 @@ const noFindDomNode = defineRule({
|
|
|
41821
41821
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
41822
41822
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
41823
41823
|
node: callee,
|
|
41824
|
-
message: MESSAGE$
|
|
41824
|
+
message: MESSAGE$25
|
|
41825
41825
|
});
|
|
41826
41826
|
return;
|
|
41827
41827
|
}
|
|
@@ -41832,7 +41832,7 @@ const noFindDomNode = defineRule({
|
|
|
41832
41832
|
if (callee.property.name !== "findDOMNode") return;
|
|
41833
41833
|
context.report({
|
|
41834
41834
|
node: callee.property,
|
|
41835
|
-
message: MESSAGE$
|
|
41835
|
+
message: MESSAGE$25
|
|
41836
41836
|
});
|
|
41837
41837
|
}
|
|
41838
41838
|
} })
|
|
@@ -42970,7 +42970,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
42970
42970
|
});
|
|
42971
42971
|
//#endregion
|
|
42972
42972
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
42973
|
-
const MESSAGE$
|
|
42973
|
+
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.";
|
|
42974
42974
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
42975
42975
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
42976
42976
|
title: "Lazy image with high fetchPriority",
|
|
@@ -42984,7 +42984,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
42984
42984
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
42985
42985
|
context.report({
|
|
42986
42986
|
node: node.name,
|
|
42987
|
-
message: MESSAGE$
|
|
42987
|
+
message: MESSAGE$24
|
|
42988
42988
|
});
|
|
42989
42989
|
} })
|
|
42990
42990
|
});
|
|
@@ -43173,7 +43173,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
43173
43173
|
});
|
|
43174
43174
|
//#endregion
|
|
43175
43175
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
43176
|
-
const MESSAGE$
|
|
43176
|
+
const MESSAGE$23 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
43177
43177
|
const REACT_USE_REF_OPTIONS = {
|
|
43178
43178
|
allowGlobalReactNamespace: false,
|
|
43179
43179
|
allowUnboundBareCalls: false
|
|
@@ -43274,7 +43274,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
43274
43274
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
43275
43275
|
context.report({
|
|
43276
43276
|
node: indeterminateAttribute,
|
|
43277
|
-
message: MESSAGE$
|
|
43277
|
+
message: MESSAGE$23
|
|
43278
43278
|
});
|
|
43279
43279
|
},
|
|
43280
43280
|
CallExpression(node) {
|
|
@@ -43283,7 +43283,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
43283
43283
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
43284
43284
|
context.report({
|
|
43285
43285
|
node,
|
|
43286
|
-
message: MESSAGE$
|
|
43286
|
+
message: MESSAGE$23
|
|
43287
43287
|
});
|
|
43288
43288
|
}
|
|
43289
43289
|
};
|
|
@@ -43571,7 +43571,7 @@ const noIsMounted = defineRule({
|
|
|
43571
43571
|
});
|
|
43572
43572
|
//#endregion
|
|
43573
43573
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
43574
|
-
const MESSAGE$
|
|
43574
|
+
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)`.";
|
|
43575
43575
|
const isJsonMethodCall = (node, method) => {
|
|
43576
43576
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
43577
43577
|
const callee = node.callee;
|
|
@@ -43635,13 +43635,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
43635
43635
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
43636
43636
|
context.report({
|
|
43637
43637
|
node,
|
|
43638
|
-
message: MESSAGE$
|
|
43638
|
+
message: MESSAGE$22
|
|
43639
43639
|
});
|
|
43640
43640
|
} })
|
|
43641
43641
|
});
|
|
43642
43642
|
//#endregion
|
|
43643
43643
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
43644
|
-
const MESSAGE$
|
|
43644
|
+
const MESSAGE$21 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
43645
43645
|
const isJsxElementTypeReference = (node) => {
|
|
43646
43646
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
43647
43647
|
const typeName = node.typeName;
|
|
@@ -43693,7 +43693,7 @@ const noJsxElementType = defineRule({
|
|
|
43693
43693
|
if (isJsxImported) return;
|
|
43694
43694
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
43695
43695
|
node: typeAnnotation,
|
|
43696
|
-
message: MESSAGE$
|
|
43696
|
+
message: MESSAGE$21
|
|
43697
43697
|
});
|
|
43698
43698
|
}
|
|
43699
43699
|
};
|
|
@@ -44974,7 +44974,7 @@ const noMoment = defineRule({
|
|
|
44974
44974
|
});
|
|
44975
44975
|
//#endregion
|
|
44976
44976
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
44977
|
-
const MESSAGE$
|
|
44977
|
+
const MESSAGE$20 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
44978
44978
|
const resolveSettings$16 = (settings) => {
|
|
44979
44979
|
const reactDoctor = settings?.["react-doctor"];
|
|
44980
44980
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -45326,7 +45326,7 @@ const noMultiComp = defineRule({
|
|
|
45326
45326
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
45327
45327
|
for (const component of flagged.slice(1)) context.report({
|
|
45328
45328
|
node: component.reportNode,
|
|
45329
|
-
message: MESSAGE$
|
|
45329
|
+
message: MESSAGE$20
|
|
45330
45330
|
});
|
|
45331
45331
|
} };
|
|
45332
45332
|
}
|
|
@@ -45539,7 +45539,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
45539
45539
|
};
|
|
45540
45540
|
//#endregion
|
|
45541
45541
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
45542
|
-
const MESSAGE$
|
|
45542
|
+
const MESSAGE$19 = "This reducer changes state in place, so your update is silently skipped.";
|
|
45543
45543
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
45544
45544
|
"copyWithin",
|
|
45545
45545
|
"fill",
|
|
@@ -45743,7 +45743,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
45743
45743
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
45744
45744
|
context.report({
|
|
45745
45745
|
node: options.crossFileConsumerCallSite,
|
|
45746
|
-
message: `${MESSAGE$
|
|
45746
|
+
message: `${MESSAGE$19} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
45747
45747
|
});
|
|
45748
45748
|
return;
|
|
45749
45749
|
}
|
|
@@ -45752,7 +45752,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
45752
45752
|
reportedNodes.add(mutation.node);
|
|
45753
45753
|
context.report({
|
|
45754
45754
|
node: mutation.node,
|
|
45755
|
-
message: MESSAGE$
|
|
45755
|
+
message: MESSAGE$19
|
|
45756
45756
|
});
|
|
45757
45757
|
}
|
|
45758
45758
|
};
|
|
@@ -46159,7 +46159,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
46159
46159
|
});
|
|
46160
46160
|
//#endregion
|
|
46161
46161
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
46162
|
-
const MESSAGE$
|
|
46162
|
+
const MESSAGE$18 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
46163
46163
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
46164
46164
|
"onKeyDown",
|
|
46165
46165
|
"onKeyUp",
|
|
@@ -46278,7 +46278,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
46278
46278
|
if (numeric === null) {
|
|
46279
46279
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
46280
46280
|
node: tabIndex,
|
|
46281
|
-
message: MESSAGE$
|
|
46281
|
+
message: MESSAGE$18
|
|
46282
46282
|
});
|
|
46283
46283
|
return;
|
|
46284
46284
|
}
|
|
@@ -46300,7 +46300,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
46300
46300
|
if (!roleAttribute) {
|
|
46301
46301
|
context.report({
|
|
46302
46302
|
node: tabIndex,
|
|
46303
|
-
message: MESSAGE$
|
|
46303
|
+
message: MESSAGE$18
|
|
46304
46304
|
});
|
|
46305
46305
|
return;
|
|
46306
46306
|
}
|
|
@@ -46314,7 +46314,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
46314
46314
|
}
|
|
46315
46315
|
context.report({
|
|
46316
46316
|
node: tabIndex,
|
|
46317
|
-
message: MESSAGE$
|
|
46317
|
+
message: MESSAGE$18
|
|
46318
46318
|
});
|
|
46319
46319
|
} };
|
|
46320
46320
|
}
|
|
@@ -48520,7 +48520,7 @@ const noRandomKey = defineRule({
|
|
|
48520
48520
|
});
|
|
48521
48521
|
//#endregion
|
|
48522
48522
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
48523
|
-
const MESSAGE$
|
|
48523
|
+
const MESSAGE$17 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
48524
48524
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
48525
48525
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
48526
48526
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -48546,13 +48546,13 @@ const noReactChildren = defineRule({
|
|
|
48546
48546
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
48547
48547
|
context.report({
|
|
48548
48548
|
node: calleeOuter,
|
|
48549
|
-
message: MESSAGE$
|
|
48549
|
+
message: MESSAGE$17
|
|
48550
48550
|
});
|
|
48551
48551
|
return;
|
|
48552
48552
|
}
|
|
48553
48553
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
48554
48554
|
node: calleeOuter,
|
|
48555
|
-
message: MESSAGE$
|
|
48555
|
+
message: MESSAGE$17
|
|
48556
48556
|
});
|
|
48557
48557
|
} })
|
|
48558
48558
|
});
|
|
@@ -49434,7 +49434,7 @@ const noRenderPropChildren = defineRule({
|
|
|
49434
49434
|
});
|
|
49435
49435
|
//#endregion
|
|
49436
49436
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
49437
|
-
const MESSAGE$
|
|
49437
|
+
const MESSAGE$16 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
49438
49438
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
49439
49439
|
const isUsedAsReturnValue = (parent) => {
|
|
49440
49440
|
if (!parent) return false;
|
|
@@ -49452,7 +49452,7 @@ const noRenderReturnValue = defineRule({
|
|
|
49452
49452
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
49453
49453
|
context.report({
|
|
49454
49454
|
node: node.callee,
|
|
49455
|
-
message: MESSAGE$
|
|
49455
|
+
message: MESSAGE$16
|
|
49456
49456
|
});
|
|
49457
49457
|
} })
|
|
49458
49458
|
});
|
|
@@ -50879,7 +50879,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
50879
50879
|
});
|
|
50880
50880
|
//#endregion
|
|
50881
50881
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
50882
|
-
const MESSAGE$
|
|
50882
|
+
const MESSAGE$15 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
50883
50883
|
const noSetState = defineRule({
|
|
50884
50884
|
id: "no-set-state",
|
|
50885
50885
|
title: "Local class state forbidden",
|
|
@@ -50894,7 +50894,7 @@ const noSetState = defineRule({
|
|
|
50894
50894
|
if (!getParentComponent(node)) return;
|
|
50895
50895
|
context.report({
|
|
50896
50896
|
node: node.callee,
|
|
50897
|
-
message: MESSAGE$
|
|
50897
|
+
message: MESSAGE$15
|
|
50898
50898
|
});
|
|
50899
50899
|
} })
|
|
50900
50900
|
});
|
|
@@ -51264,7 +51264,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
51264
51264
|
};
|
|
51265
51265
|
//#endregion
|
|
51266
51266
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
51267
|
-
const MESSAGE$
|
|
51267
|
+
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.";
|
|
51268
51268
|
const DEFAULT_HANDLERS = [
|
|
51269
51269
|
"onClick",
|
|
51270
51270
|
"onMouseDown",
|
|
@@ -51379,7 +51379,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51379
51379
|
if (!roleAttribute || !roleAttribute.value) {
|
|
51380
51380
|
context.report({
|
|
51381
51381
|
node: node.name,
|
|
51382
|
-
message: MESSAGE$
|
|
51382
|
+
message: MESSAGE$14
|
|
51383
51383
|
});
|
|
51384
51384
|
return;
|
|
51385
51385
|
}
|
|
@@ -51389,7 +51389,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51389
51389
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
51390
51390
|
context.report({
|
|
51391
51391
|
node: node.name,
|
|
51392
|
-
message: MESSAGE$
|
|
51392
|
+
message: MESSAGE$14
|
|
51393
51393
|
});
|
|
51394
51394
|
return;
|
|
51395
51395
|
}
|
|
@@ -51397,7 +51397,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51397
51397
|
if (!settings.allowExpressionValues) {
|
|
51398
51398
|
context.report({
|
|
51399
51399
|
node: node.name,
|
|
51400
|
-
message: MESSAGE$
|
|
51400
|
+
message: MESSAGE$14
|
|
51401
51401
|
});
|
|
51402
51402
|
return;
|
|
51403
51403
|
}
|
|
@@ -51405,7 +51405,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51405
51405
|
if (isStaticNullishExpression(expression)) {
|
|
51406
51406
|
context.report({
|
|
51407
51407
|
node: node.name,
|
|
51408
|
-
message: MESSAGE$
|
|
51408
|
+
message: MESSAGE$14
|
|
51409
51409
|
});
|
|
51410
51410
|
return;
|
|
51411
51411
|
}
|
|
@@ -51415,7 +51415,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51415
51415
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
51416
51416
|
context.report({
|
|
51417
51417
|
node: node.name,
|
|
51418
|
-
message: MESSAGE$
|
|
51418
|
+
message: MESSAGE$14
|
|
51419
51419
|
});
|
|
51420
51420
|
return;
|
|
51421
51421
|
}
|
|
@@ -51423,7 +51423,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51423
51423
|
}
|
|
51424
51424
|
context.report({
|
|
51425
51425
|
node: node.name,
|
|
51426
|
-
message: MESSAGE$
|
|
51426
|
+
message: MESSAGE$14
|
|
51427
51427
|
});
|
|
51428
51428
|
} };
|
|
51429
51429
|
}
|
|
@@ -51529,7 +51529,7 @@ const noStringRefs = defineRule({
|
|
|
51529
51529
|
});
|
|
51530
51530
|
//#endregion
|
|
51531
51531
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
51532
|
-
const MESSAGE$
|
|
51532
|
+
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)`).";
|
|
51533
51533
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
51534
51534
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
51535
51535
|
const noSyncXhr = defineRule({
|
|
@@ -51550,7 +51550,7 @@ const noSyncXhr = defineRule({
|
|
|
51550
51550
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
51551
51551
|
context.report({
|
|
51552
51552
|
node,
|
|
51553
|
-
message: MESSAGE$
|
|
51553
|
+
message: MESSAGE$13
|
|
51554
51554
|
});
|
|
51555
51555
|
};
|
|
51556
51556
|
return {
|
|
@@ -51575,7 +51575,7 @@ const noSyncXhr = defineRule({
|
|
|
51575
51575
|
});
|
|
51576
51576
|
//#endregion
|
|
51577
51577
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
51578
|
-
const MESSAGE$
|
|
51578
|
+
const MESSAGE$12 = "This value is `undefined` because function components have no `this`.";
|
|
51579
51579
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
51580
51580
|
let ancestor = node.parent;
|
|
51581
51581
|
while (ancestor) {
|
|
@@ -51663,7 +51663,7 @@ const noThisInSfc = defineRule({
|
|
|
51663
51663
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
51664
51664
|
context.report({
|
|
51665
51665
|
node,
|
|
51666
|
-
message: MESSAGE$
|
|
51666
|
+
message: MESSAGE$12
|
|
51667
51667
|
});
|
|
51668
51668
|
} };
|
|
51669
51669
|
}
|
|
@@ -54033,7 +54033,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
54033
54033
|
//#endregion
|
|
54034
54034
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
54035
54035
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
54036
|
-
const MESSAGE$
|
|
54036
|
+
const MESSAGE$11 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
54037
54037
|
const resolveSettings$7 = (settings) => {
|
|
54038
54038
|
const reactDoctor = settings?.["react-doctor"];
|
|
54039
54039
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -54067,7 +54067,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
54067
54067
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
54068
54068
|
context.report({
|
|
54069
54069
|
node: node.callee,
|
|
54070
|
-
message: MESSAGE$
|
|
54070
|
+
message: MESSAGE$11
|
|
54071
54071
|
});
|
|
54072
54072
|
} };
|
|
54073
54073
|
}
|
|
@@ -55867,7 +55867,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
55867
55867
|
});
|
|
55868
55868
|
//#endregion
|
|
55869
55869
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
55870
|
-
const MESSAGE$
|
|
55870
|
+
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.";
|
|
55871
55871
|
const preactPreferOndblclick = defineRule({
|
|
55872
55872
|
id: "preact-prefer-ondblclick",
|
|
55873
55873
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -55882,7 +55882,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
55882
55882
|
if (!onDoubleClickAttribute) return;
|
|
55883
55883
|
context.report({
|
|
55884
55884
|
node: onDoubleClickAttribute,
|
|
55885
|
-
message: MESSAGE$
|
|
55885
|
+
message: MESSAGE$10
|
|
55886
55886
|
});
|
|
55887
55887
|
} })
|
|
55888
55888
|
});
|
|
@@ -56122,7 +56122,7 @@ const preferExplicitVariants = defineRule({
|
|
|
56122
56122
|
});
|
|
56123
56123
|
//#endregion
|
|
56124
56124
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
56125
|
-
const MESSAGE$
|
|
56125
|
+
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.";
|
|
56126
56126
|
const resolveSettings$4 = (settings) => {
|
|
56127
56127
|
const reactDoctor = settings?.["react-doctor"];
|
|
56128
56128
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -56161,7 +56161,7 @@ const preferFunctionComponent = defineRule({
|
|
|
56161
56161
|
const reportNode = node.id ?? node;
|
|
56162
56162
|
context.report({
|
|
56163
56163
|
node: reportNode,
|
|
56164
|
-
message: MESSAGE$
|
|
56164
|
+
message: MESSAGE$9
|
|
56165
56165
|
});
|
|
56166
56166
|
};
|
|
56167
56167
|
return {
|
|
@@ -58362,7 +58362,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
58362
58362
|
});
|
|
58363
58363
|
//#endregion
|
|
58364
58364
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
58365
|
-
const MESSAGE$
|
|
58365
|
+
const MESSAGE$8 = "This JSX crashes because `React` isn't in scope.";
|
|
58366
58366
|
const reactInJsxScope = defineRule({
|
|
58367
58367
|
id: "react-in-jsx-scope",
|
|
58368
58368
|
title: "React not in scope for JSX",
|
|
@@ -58374,21 +58374,21 @@ const reactInJsxScope = defineRule({
|
|
|
58374
58374
|
if (findVariableInitializer(node, "React")) return;
|
|
58375
58375
|
context.report({
|
|
58376
58376
|
node: node.name,
|
|
58377
|
-
message: MESSAGE$
|
|
58377
|
+
message: MESSAGE$8
|
|
58378
58378
|
});
|
|
58379
58379
|
},
|
|
58380
58380
|
JSXFragment(node) {
|
|
58381
58381
|
if (findVariableInitializer(node, "React")) return;
|
|
58382
58382
|
context.report({
|
|
58383
58383
|
node: node.openingFragment,
|
|
58384
|
-
message: MESSAGE$
|
|
58384
|
+
message: MESSAGE$8
|
|
58385
58385
|
});
|
|
58386
58386
|
}
|
|
58387
58387
|
})
|
|
58388
58388
|
});
|
|
58389
58389
|
//#endregion
|
|
58390
58390
|
//#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
|
|
58391
|
-
const MESSAGE$
|
|
58391
|
+
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.";
|
|
58392
58392
|
const REACT_MARKDOWN_MODULE = "react-markdown";
|
|
58393
58393
|
const REHYPE_RAW_MODULE = "rehype-raw";
|
|
58394
58394
|
const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
|
|
@@ -58540,7 +58540,7 @@ const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
|
58540
58540
|
if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
|
|
58541
58541
|
context.report({
|
|
58542
58542
|
node,
|
|
58543
|
-
message: MESSAGE$
|
|
58543
|
+
message: MESSAGE$7
|
|
58544
58544
|
});
|
|
58545
58545
|
} }))
|
|
58546
58546
|
});
|
|
@@ -58695,7 +58695,7 @@ const reduxUseselectorInlineDerivation = defineRule({
|
|
|
58695
58695
|
});
|
|
58696
58696
|
//#endregion
|
|
58697
58697
|
//#region src/plugin/rules/state-and-effects/redux-useselector-returns-new-collection.ts
|
|
58698
|
-
const MESSAGE$
|
|
58698
|
+
const MESSAGE$6 = "Your component re-renders on every dispatched action when useSelector returns a new object or array.";
|
|
58699
58699
|
const isConciseBodyReturningCollection = (functionNode) => {
|
|
58700
58700
|
if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression")) return false;
|
|
58701
58701
|
const rawBody = functionNode.body;
|
|
@@ -58730,7 +58730,7 @@ const reduxUseselectorReturnsNewCollection = defineRule({
|
|
|
58730
58730
|
if (!isConciseBodyReturningCollection(selectorArgument)) return;
|
|
58731
58731
|
context.report({
|
|
58732
58732
|
node,
|
|
58733
|
-
message: MESSAGE$
|
|
58733
|
+
message: MESSAGE$6
|
|
58734
58734
|
});
|
|
58735
58735
|
}
|
|
58736
58736
|
};
|
|
@@ -59857,7 +59857,7 @@ const functionBodyHasReturnWithValue = (functionNode) => {
|
|
|
59857
59857
|
//#endregion
|
|
59858
59858
|
//#region src/plugin/rules/react-builtins/require-render-return.ts
|
|
59859
59859
|
const RENDER_METHOD_NAME = "render";
|
|
59860
|
-
const MESSAGE$
|
|
59860
|
+
const MESSAGE$5 = "Your users see nothing because this `render` method returns nothing.";
|
|
59861
59861
|
const isStaticRenderKey = (key) => {
|
|
59862
59862
|
if (isNodeOfType(key, "Identifier")) return key.name === RENDER_METHOD_NAME;
|
|
59863
59863
|
if (isNodeOfType(key, "Literal")) return key.value === RENDER_METHOD_NAME;
|
|
@@ -59917,7 +59917,7 @@ const requireRenderReturn = defineRule({
|
|
|
59917
59917
|
if (functionBodyHasReturnWithValue(functionNode)) return;
|
|
59918
59918
|
context.report({
|
|
59919
59919
|
node: host.reportNode,
|
|
59920
|
-
message: MESSAGE$
|
|
59920
|
+
message: MESSAGE$5
|
|
59921
59921
|
});
|
|
59922
59922
|
};
|
|
59923
59923
|
return {
|
|
@@ -68759,7 +68759,7 @@ const rulesOfHooks = defineRule({
|
|
|
68759
68759
|
});
|
|
68760
68760
|
//#endregion
|
|
68761
68761
|
//#region src/plugin/rules/a11y/scope.ts
|
|
68762
|
-
const MESSAGE$
|
|
68762
|
+
const MESSAGE$4 = "The `scope` attribute only works on `<th>` cells, so screen readers get no table-header help from it here.";
|
|
68763
68763
|
const scope = defineRule({
|
|
68764
68764
|
id: "scope",
|
|
68765
68765
|
title: "scope attribute on non-th element",
|
|
@@ -68774,7 +68774,7 @@ const scope = defineRule({
|
|
|
68774
68774
|
if (!HTML_TAGS.has(tag)) return;
|
|
68775
68775
|
if (tag !== "th") context.report({
|
|
68776
68776
|
node: scopeAttribute,
|
|
68777
|
-
message: MESSAGE$
|
|
68777
|
+
message: MESSAGE$4
|
|
68778
68778
|
});
|
|
68779
68779
|
} })
|
|
68780
68780
|
});
|
|
@@ -68791,7 +68791,7 @@ const secretInFallback = defineRule({
|
|
|
68791
68791
|
});
|
|
68792
68792
|
//#endregion
|
|
68793
68793
|
//#region src/plugin/rules/react-builtins/self-closing-comp.ts
|
|
68794
|
-
const MESSAGE$
|
|
68794
|
+
const MESSAGE$3 = "This tag has no children, so the closing tag adds noise without changing output.";
|
|
68795
68795
|
const resolveSettings$2 = (settings) => {
|
|
68796
68796
|
const reactDoctor = settings?.["react-doctor"];
|
|
68797
68797
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.selfClosingComp ?? {} : {};
|
|
@@ -68830,7 +68830,7 @@ const selfClosingComp = defineRule({
|
|
|
68830
68830
|
if (isHtmlTag && !settings.html) return;
|
|
68831
68831
|
context.report({
|
|
68832
68832
|
node: node.openingElement,
|
|
68833
|
-
message: MESSAGE$
|
|
68833
|
+
message: MESSAGE$3
|
|
68834
68834
|
});
|
|
68835
68835
|
} };
|
|
68836
68836
|
}
|
|
@@ -70214,7 +70214,7 @@ const collectJsxRuntimeImports = (program) => {
|
|
|
70214
70214
|
};
|
|
70215
70215
|
//#endregion
|
|
70216
70216
|
//#region src/plugin/rules/react-builtins/style-prop-object.ts
|
|
70217
|
-
const MESSAGE$
|
|
70217
|
+
const MESSAGE$2 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
|
|
70218
70218
|
const resolveSettings = (settings) => {
|
|
70219
70219
|
const reactDoctor = settings?.["react-doctor"];
|
|
70220
70220
|
return { allow: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.stylePropObject ?? {} : {}).allow ?? [] };
|
|
@@ -70321,7 +70321,7 @@ const stylePropObject = defineRule({
|
|
|
70321
70321
|
if (isNodeOfType(value, "Literal") && typeof value.value === "string") {
|
|
70322
70322
|
context.report({
|
|
70323
70323
|
node: attribute,
|
|
70324
|
-
message: MESSAGE$
|
|
70324
|
+
message: MESSAGE$2
|
|
70325
70325
|
});
|
|
70326
70326
|
return;
|
|
70327
70327
|
}
|
|
@@ -70329,7 +70329,7 @@ const stylePropObject = defineRule({
|
|
|
70329
70329
|
const innerExpression = value.expression;
|
|
70330
70330
|
if (innerExpression && innerExpression.type !== "JSXEmptyExpression" && isInvalidStyleExpression(innerExpression)) context.report({
|
|
70331
70331
|
node: attribute,
|
|
70332
|
-
message: MESSAGE$
|
|
70332
|
+
message: MESSAGE$2
|
|
70333
70333
|
});
|
|
70334
70334
|
}
|
|
70335
70335
|
}
|
|
@@ -70352,7 +70352,7 @@ const stylePropObject = defineRule({
|
|
|
70352
70352
|
if (!(isNodeOfType(key, "Identifier") && key.name === "style" || isNodeOfType(key, "Literal") && key.value === "style")) continue;
|
|
70353
70353
|
if (isInvalidStyleExpression(property.value)) context.report({
|
|
70354
70354
|
node: property,
|
|
70355
|
-
message: MESSAGE$
|
|
70355
|
+
message: MESSAGE$2
|
|
70356
70356
|
});
|
|
70357
70357
|
}
|
|
70358
70358
|
}
|
|
@@ -70688,7 +70688,7 @@ const svgFilterClickjackingRisk = defineRule({
|
|
|
70688
70688
|
});
|
|
70689
70689
|
//#endregion
|
|
70690
70690
|
//#region src/plugin/rules/a11y/tabindex-no-positive.ts
|
|
70691
|
-
const MESSAGE = "Keyboard users get jumped out of the normal order by a positive `tabIndex`, so use `0` or `-1`.";
|
|
70691
|
+
const MESSAGE$1 = "Keyboard users get jumped out of the normal order by a positive `tabIndex`, so use `0` or `-1`.";
|
|
70692
70692
|
const tabindexNoPositive = defineRule({
|
|
70693
70693
|
id: "tabindex-no-positive",
|
|
70694
70694
|
title: "Positive tabindex value",
|
|
@@ -70708,7 +70708,7 @@ const tabindexNoPositive = defineRule({
|
|
|
70708
70708
|
} else if (tabIndex.value && isNodeOfType(tabIndex.value, "JSXExpressionContainer")) numericValue = parseJsxValue(tabIndex.value);
|
|
70709
70709
|
if (numericValue !== null && numericValue > 0) context.report({
|
|
70710
70710
|
node: tabIndex,
|
|
70711
|
-
message: MESSAGE
|
|
70711
|
+
message: MESSAGE$1
|
|
70712
70712
|
});
|
|
70713
70713
|
} })
|
|
70714
70714
|
});
|
|
@@ -71614,6 +71614,248 @@ const useLazyMotion = defineRule({
|
|
|
71614
71614
|
} })
|
|
71615
71615
|
});
|
|
71616
71616
|
//#endregion
|
|
71617
|
+
//#region src/plugin/rules/valtio/valtio-no-proxy-read-in-render.ts
|
|
71618
|
+
const VALTIO_REACT_MODULE_SOURCES = new Set(["valtio", "valtio/react"]);
|
|
71619
|
+
const REACT_DEPENDENCY_ARRAY_HOOK_NAMES = new Set([
|
|
71620
|
+
"useCallback",
|
|
71621
|
+
"useEffect",
|
|
71622
|
+
"useInsertionEffect",
|
|
71623
|
+
"useLayoutEffect",
|
|
71624
|
+
"useMemo"
|
|
71625
|
+
]);
|
|
71626
|
+
const MESSAGE = "Read this Valtio value from the useSnapshot result during render. Keep proxy reads for callbacks so render uses the tracked, consistent snapshot.";
|
|
71627
|
+
const isValtioImportSymbol = (symbol) => {
|
|
71628
|
+
if (symbol.kind !== "import") return false;
|
|
71629
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
71630
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && VALTIO_REACT_MODULE_SOURCES.has(importDeclaration.source.value));
|
|
71631
|
+
};
|
|
71632
|
+
const isValtioUseSnapshotCall = (node, scopes) => {
|
|
71633
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
71634
|
+
const callee = stripParenExpression(node.callee);
|
|
71635
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
71636
|
+
const symbol = resolveConstIdentifierAlias(callee, scopes);
|
|
71637
|
+
return Boolean(symbol && isValtioImportSymbol(symbol) && getImportedName(symbol.declarationNode) === "useSnapshot");
|
|
71638
|
+
}
|
|
71639
|
+
if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyName(callee) !== "useSnapshot") return false;
|
|
71640
|
+
const receiver = stripParenExpression(callee.object);
|
|
71641
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
71642
|
+
const namespaceSymbol = resolveConstIdentifierAlias(receiver, scopes);
|
|
71643
|
+
return Boolean(namespaceSymbol && isValtioImportSymbol(namespaceSymbol) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
|
|
71644
|
+
};
|
|
71645
|
+
const collectPatternBindingIdentifiers = (pattern) => {
|
|
71646
|
+
if (isNodeOfType(pattern, "Identifier")) return [pattern];
|
|
71647
|
+
if (isNodeOfType(pattern, "AssignmentPattern")) return collectPatternBindingIdentifiers(pattern.left);
|
|
71648
|
+
if (isNodeOfType(pattern, "RestElement")) return collectPatternBindingIdentifiers(pattern.argument);
|
|
71649
|
+
if (isNodeOfType(pattern, "ArrayPattern")) return pattern.elements.flatMap((element) => element ? collectPatternBindingIdentifiers(element) : []);
|
|
71650
|
+
if (isNodeOfType(pattern, "ObjectPattern")) return pattern.properties.flatMap((property) => {
|
|
71651
|
+
if (isNodeOfType(property, "Property")) return collectPatternBindingIdentifiers(property.value);
|
|
71652
|
+
return isNodeOfType(property, "RestElement") ? collectPatternBindingIdentifiers(property.argument) : [];
|
|
71653
|
+
});
|
|
71654
|
+
return [];
|
|
71655
|
+
};
|
|
71656
|
+
const collectAssignmentWriteTargets = (target) => {
|
|
71657
|
+
const candidate = stripParenExpression(target);
|
|
71658
|
+
if (isNodeOfType(candidate, "AssignmentPattern")) return collectAssignmentWriteTargets(candidate.left);
|
|
71659
|
+
if (isNodeOfType(candidate, "RestElement")) return collectAssignmentWriteTargets(candidate.argument);
|
|
71660
|
+
if (isNodeOfType(candidate, "ArrayPattern")) return candidate.elements.flatMap((element) => element ? collectAssignmentWriteTargets(element) : []);
|
|
71661
|
+
if (isNodeOfType(candidate, "ObjectPattern")) return candidate.properties.flatMap((property) => isNodeOfType(property, "Property") ? collectAssignmentWriteTargets(property.value) : collectAssignmentWriteTargets(property.argument));
|
|
71662
|
+
return [candidate];
|
|
71663
|
+
};
|
|
71664
|
+
const findPatternPathToBinding = (pattern, bindingIdentifier) => {
|
|
71665
|
+
if (pattern === bindingIdentifier) return [];
|
|
71666
|
+
if (isNodeOfType(pattern, "AssignmentPattern")) return findPatternPathToBinding(pattern.left, bindingIdentifier);
|
|
71667
|
+
if (isNodeOfType(pattern, "RestElement")) return null;
|
|
71668
|
+
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
71669
|
+
for (const [elementIndex, element] of pattern.elements.entries()) {
|
|
71670
|
+
if (!element) continue;
|
|
71671
|
+
const nestedPath = findPatternPathToBinding(element, bindingIdentifier);
|
|
71672
|
+
if (nestedPath) return [String(elementIndex), ...nestedPath];
|
|
71673
|
+
}
|
|
71674
|
+
return null;
|
|
71675
|
+
}
|
|
71676
|
+
if (!isNodeOfType(pattern, "ObjectPattern")) return null;
|
|
71677
|
+
for (const property of pattern.properties) {
|
|
71678
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
71679
|
+
const nestedPath = findPatternPathToBinding(property.value, bindingIdentifier);
|
|
71680
|
+
if (!nestedPath) continue;
|
|
71681
|
+
const propertyName = getStaticPropertyKeyName(property);
|
|
71682
|
+
return propertyName === null ? null : [propertyName, ...nestedPath];
|
|
71683
|
+
}
|
|
71684
|
+
return null;
|
|
71685
|
+
};
|
|
71686
|
+
const resolveProxyPath = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), allowedAliasSymbolIds = null, collectedAliasCaptures = null) => {
|
|
71687
|
+
const candidate = stripParenExpression(expression);
|
|
71688
|
+
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
71689
|
+
const receiverPath = resolveProxyPath(candidate.object, scopes, visitedSymbolIds, allowedAliasSymbolIds, collectedAliasCaptures);
|
|
71690
|
+
if (!receiverPath) return null;
|
|
71691
|
+
return {
|
|
71692
|
+
...receiverPath,
|
|
71693
|
+
properties: [...receiverPath.properties, getStaticPropertyName(candidate)]
|
|
71694
|
+
};
|
|
71695
|
+
}
|
|
71696
|
+
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
71697
|
+
const symbol = scopes.symbolFor(candidate);
|
|
71698
|
+
if (!symbol) return scopes.isGlobalReference(candidate) ? {
|
|
71699
|
+
rootKey: `global:${candidate.name}`,
|
|
71700
|
+
properties: []
|
|
71701
|
+
} : null;
|
|
71702
|
+
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
71703
|
+
if (symbol.kind === "const" && symbol.initializer && isNodeOfType(symbol.declarationNode, "VariableDeclarator")) {
|
|
71704
|
+
const directAlias = symbol.declarationNode.id === symbol.bindingIdentifier;
|
|
71705
|
+
const initializerNode = directAlias ? symbol.initializer : symbol.declarationNode.init;
|
|
71706
|
+
if (!initializerNode) return {
|
|
71707
|
+
rootKey: `symbol:${symbol.id}`,
|
|
71708
|
+
properties: []
|
|
71709
|
+
};
|
|
71710
|
+
const initializer = stripParenExpression(initializerNode);
|
|
71711
|
+
const canResolveDirectAlias = directAlias && (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression"));
|
|
71712
|
+
const patternPath = directAlias ? null : findPatternPathToBinding(symbol.declarationNode.id, symbol.bindingIdentifier);
|
|
71713
|
+
if ((canResolveDirectAlias || patternPath) && (allowedAliasSymbolIds === null || allowedAliasSymbolIds.has(symbol.id))) {
|
|
71714
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
71715
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
71716
|
+
const initializerPath = resolveProxyPath(initializer, scopes, nextVisitedSymbolIds, allowedAliasSymbolIds, collectedAliasCaptures);
|
|
71717
|
+
if (initializerPath) {
|
|
71718
|
+
const aliasPath = {
|
|
71719
|
+
...initializerPath,
|
|
71720
|
+
properties: [...initializerPath.properties, ...patternPath ?? []]
|
|
71721
|
+
};
|
|
71722
|
+
const declarationEnd = symbol.declarationNode.range?.[1];
|
|
71723
|
+
if (typeof declarationEnd === "number") collectedAliasCaptures?.set(symbol.id, {
|
|
71724
|
+
declarationEnd,
|
|
71725
|
+
path: aliasPath
|
|
71726
|
+
});
|
|
71727
|
+
return aliasPath;
|
|
71728
|
+
}
|
|
71729
|
+
}
|
|
71730
|
+
}
|
|
71731
|
+
return {
|
|
71732
|
+
rootKey: `symbol:${symbol.id}`,
|
|
71733
|
+
properties: []
|
|
71734
|
+
};
|
|
71735
|
+
};
|
|
71736
|
+
const isScopeWithin = (candidateScope, ancestorScope) => {
|
|
71737
|
+
let currentScope = candidateScope;
|
|
71738
|
+
while (currentScope) {
|
|
71739
|
+
if (currentScope === ancestorScope) return true;
|
|
71740
|
+
currentScope = currentScope.parent;
|
|
71741
|
+
}
|
|
71742
|
+
return false;
|
|
71743
|
+
};
|
|
71744
|
+
const isPathPrefix = (prefix, candidate) => {
|
|
71745
|
+
if (prefix.rootKey !== candidate.rootKey || prefix.properties.length > candidate.properties.length) return false;
|
|
71746
|
+
return prefix.properties.every((propertyName, propertyIndex) => propertyName !== null && propertyName === candidate.properties[propertyIndex]);
|
|
71747
|
+
};
|
|
71748
|
+
const findOutermostMemberRead = (identifier) => {
|
|
71749
|
+
let currentExpression = findTransparentExpressionRoot(identifier);
|
|
71750
|
+
for (;;) {
|
|
71751
|
+
const parent = currentExpression.parent;
|
|
71752
|
+
if (!isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== stripParenExpression(currentExpression)) return currentExpression;
|
|
71753
|
+
currentExpression = findTransparentExpressionRoot(parent);
|
|
71754
|
+
}
|
|
71755
|
+
};
|
|
71756
|
+
const isReadPositionWithinAssignmentTarget = (expression) => {
|
|
71757
|
+
let currentNode = expression;
|
|
71758
|
+
let parentNode = currentNode.parent;
|
|
71759
|
+
while (parentNode) {
|
|
71760
|
+
if (isNodeOfType(parentNode, "MemberExpression") && parentNode.computed && parentNode.property === currentNode || isNodeOfType(parentNode, "Property") && parentNode.computed && parentNode.key === currentNode || isNodeOfType(parentNode, "AssignmentPattern") && parentNode.right === currentNode) return true;
|
|
71761
|
+
if (isNodeOfType(parentNode, "AssignmentExpression") || isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete" || isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return false;
|
|
71762
|
+
currentNode = parentNode;
|
|
71763
|
+
parentNode = currentNode.parent;
|
|
71764
|
+
}
|
|
71765
|
+
return false;
|
|
71766
|
+
};
|
|
71767
|
+
const isSnapshotArgument = (expression, scopes) => {
|
|
71768
|
+
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
71769
|
+
const parent = expressionRoot.parent;
|
|
71770
|
+
return Boolean(parent && isNodeOfType(parent, "CallExpression") && isValtioUseSnapshotCall(parent, scopes) && parent.arguments[0] === expressionRoot);
|
|
71771
|
+
};
|
|
71772
|
+
const isStableProxyDependency = (expression, readPath, matchingTarget, scopes) => {
|
|
71773
|
+
if (readPath.properties.length !== matchingTarget.path.properties.length || !isPathPrefix(matchingTarget.path, readPath)) return false;
|
|
71774
|
+
const dependencyArray = findTransparentExpressionRoot(expression).parent;
|
|
71775
|
+
if (!isNodeOfType(dependencyArray, "ArrayExpression")) return false;
|
|
71776
|
+
const hookCall = dependencyArray.parent;
|
|
71777
|
+
return Boolean(isNodeOfType(hookCall, "CallExpression") && hookCall.arguments[1] === dependencyArray && isReactApiCall(hookCall, REACT_DEPENDENCY_ARRAY_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }));
|
|
71778
|
+
};
|
|
71779
|
+
const getSnapshotTarget = (callExpression, context) => {
|
|
71780
|
+
if (!isValtioUseSnapshotCall(callExpression, context.scopes)) return null;
|
|
71781
|
+
const proxyArgument = callExpression.arguments[0];
|
|
71782
|
+
if (!proxyArgument || isNodeOfType(proxyArgument, "SpreadElement")) return null;
|
|
71783
|
+
const path = resolveProxyPath(proxyArgument, context.scopes);
|
|
71784
|
+
if (!path || path.properties.some((propertyName) => propertyName === null)) return null;
|
|
71785
|
+
const callExpressionRoot = findTransparentExpressionRoot(callExpression);
|
|
71786
|
+
const declarator = callExpressionRoot.parent;
|
|
71787
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== callExpressionRoot || !isNodeOfType(declarator.parent, "VariableDeclaration")) return null;
|
|
71788
|
+
const ownerFunction = findRenderPhaseComponentOrHook(callExpression, context.scopes);
|
|
71789
|
+
if (!ownerFunction) return null;
|
|
71790
|
+
const snapshotBindingScopes = collectPatternBindingIdentifiers(declarator.id).flatMap((bindingIdentifier) => {
|
|
71791
|
+
const bindingSymbol = context.scopes.symbolFor(bindingIdentifier);
|
|
71792
|
+
return bindingSymbol ? [bindingSymbol.scope] : [];
|
|
71793
|
+
});
|
|
71794
|
+
const declarationEnd = declarator.range?.[1];
|
|
71795
|
+
if (snapshotBindingScopes.length === 0 || typeof declarationEnd !== "number") return null;
|
|
71796
|
+
return {
|
|
71797
|
+
declarationEnd,
|
|
71798
|
+
ownerFunction,
|
|
71799
|
+
path,
|
|
71800
|
+
snapshotBindingScopes
|
|
71801
|
+
};
|
|
71802
|
+
};
|
|
71803
|
+
const wasTargetReplacedBeforeRead = (target, readPosition, writeTargets, readAliasCaptures, context) => writeTargets.some((writeTarget) => {
|
|
71804
|
+
const writeNode = findTransparentExpressionRoot(writeTarget).parent;
|
|
71805
|
+
const writePosition = writeNode?.range?.[0];
|
|
71806
|
+
if (!writeNode || typeof writePosition !== "number" || writePosition <= target.declarationEnd || writePosition >= readPosition || findRenderPhaseComponentOrHook(writeNode, context.scopes) !== target.ownerFunction) return false;
|
|
71807
|
+
const writePath = resolveProxyPath(writeTarget, context.scopes, /* @__PURE__ */ new Set(), null);
|
|
71808
|
+
if (!writePath || !isPathPrefix(writePath, target.path)) return false;
|
|
71809
|
+
return ![...readAliasCaptures.values()].some((aliasCapture) => aliasCapture.declarationEnd < writePosition && isPathPrefix(writePath, aliasCapture.path));
|
|
71810
|
+
});
|
|
71811
|
+
const valtioNoProxyReadInRender = defineRule({
|
|
71812
|
+
id: "valtio-no-proxy-read-in-render",
|
|
71813
|
+
title: "Valtio proxy read during render",
|
|
71814
|
+
severity: "warn",
|
|
71815
|
+
recommendation: "Read reactive render values from useSnapshot. Reserve the mutable proxy for event handlers, effects, and other callbacks that need the latest value.",
|
|
71816
|
+
requires: ["valtio:1"],
|
|
71817
|
+
create: (context) => {
|
|
71818
|
+
const snapshotTargets = [];
|
|
71819
|
+
const identifierCandidates = [];
|
|
71820
|
+
const writeTargets = [];
|
|
71821
|
+
return {
|
|
71822
|
+
CallExpression(node) {
|
|
71823
|
+
const snapshotTarget = getSnapshotTarget(node, context);
|
|
71824
|
+
if (snapshotTarget) snapshotTargets.push(snapshotTarget);
|
|
71825
|
+
},
|
|
71826
|
+
Identifier(node) {
|
|
71827
|
+
if (context.scopes.referenceFor(node)) identifierCandidates.push(node);
|
|
71828
|
+
},
|
|
71829
|
+
AssignmentExpression(node) {
|
|
71830
|
+
writeTargets.push(...collectAssignmentWriteTargets(node.left));
|
|
71831
|
+
},
|
|
71832
|
+
UpdateExpression(node) {
|
|
71833
|
+
writeTargets.push(node.argument);
|
|
71834
|
+
},
|
|
71835
|
+
"Program:exit"() {
|
|
71836
|
+
const reportedExpressions = /* @__PURE__ */ new Set();
|
|
71837
|
+
for (const identifier of identifierCandidates) {
|
|
71838
|
+
const readExpression = findOutermostMemberRead(identifier);
|
|
71839
|
+
if (reportedExpressions.has(readExpression) || isWithinAssignmentTarget(readExpression) && !isReadPositionWithinAssignmentTarget(readExpression) || isSnapshotArgument(readExpression, context.scopes)) continue;
|
|
71840
|
+
const readAliasCaptures = /* @__PURE__ */ new Map();
|
|
71841
|
+
const readPath = resolveProxyPath(readExpression, context.scopes, /* @__PURE__ */ new Set(), null, readAliasCaptures);
|
|
71842
|
+
const readPosition = readExpression.range?.[0];
|
|
71843
|
+
const ownerFunction = findRenderPhaseComponentOrHook(readExpression, context.scopes);
|
|
71844
|
+
if (!readPath || typeof readPosition !== "number" || !ownerFunction) continue;
|
|
71845
|
+
const readScope = context.scopes.scopeFor(readExpression);
|
|
71846
|
+
const matchingTarget = snapshotTargets.find((target) => target.ownerFunction === ownerFunction && target.declarationEnd < readPosition && target.snapshotBindingScopes.some((bindingScope) => isScopeWithin(readScope, bindingScope)) && isPathPrefix(target.path, readPath) && (readAliasCaptures.size === 0 || [...readAliasCaptures.values()].some((aliasCapture) => isPathPrefix(aliasCapture.path, target.path) || isPathPrefix(target.path, aliasCapture.path) && readPath.properties.length > aliasCapture.path.properties.length)) && !wasTargetReplacedBeforeRead(target, readPosition, writeTargets, readAliasCaptures, context));
|
|
71847
|
+
if (!matchingTarget || isStableProxyDependency(readExpression, readPath, matchingTarget, context.scopes)) continue;
|
|
71848
|
+
reportedExpressions.add(readExpression);
|
|
71849
|
+
context.report({
|
|
71850
|
+
node: readExpression,
|
|
71851
|
+
message: MESSAGE
|
|
71852
|
+
});
|
|
71853
|
+
}
|
|
71854
|
+
}
|
|
71855
|
+
};
|
|
71856
|
+
}
|
|
71857
|
+
});
|
|
71858
|
+
//#endregion
|
|
71617
71859
|
//#region src/plugin/rules/react-builtins/void-dom-elements-no-children.ts
|
|
71618
71860
|
const VOID_DOM_ELEMENTS = new Set([
|
|
71619
71861
|
"area",
|
|
@@ -76766,6 +77008,18 @@ const reactDoctorRules = [
|
|
|
76766
77008
|
category: "Performance"
|
|
76767
77009
|
}
|
|
76768
77010
|
},
|
|
77011
|
+
{
|
|
77012
|
+
key: "react-doctor/valtio-no-proxy-read-in-render",
|
|
77013
|
+
id: "valtio-no-proxy-read-in-render",
|
|
77014
|
+
source: "react-doctor",
|
|
77015
|
+
originallyExternal: false,
|
|
77016
|
+
rule: {
|
|
77017
|
+
...valtioNoProxyReadInRender,
|
|
77018
|
+
framework: "global",
|
|
77019
|
+
category: "Bugs",
|
|
77020
|
+
requires: [...new Set(["react", ...valtioNoProxyReadInRender.requires ?? []])]
|
|
77021
|
+
}
|
|
77022
|
+
},
|
|
76769
77023
|
{
|
|
76770
77024
|
key: "react-doctor/void-dom-elements-no-children",
|
|
76771
77025
|
id: "void-dom-elements-no-children",
|