oxlint-plugin-react-doctor 0.8.1-dev.b4556e3 → 0.8.1-dev.b852961
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 +46 -0
- package/dist/index.js +412 -299
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2421,6 +2421,29 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
2421
2421
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
2422
2422
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
2423
2423
|
};
|
|
2424
|
+
}, {
|
|
2425
|
+
readonly key: "react-doctor/jsx-no-target-blank";
|
|
2426
|
+
readonly id: "jsx-no-target-blank";
|
|
2427
|
+
readonly source: "react-doctor";
|
|
2428
|
+
readonly originallyExternal: true;
|
|
2429
|
+
readonly rule: {
|
|
2430
|
+
readonly framework: "global";
|
|
2431
|
+
readonly category: "Security";
|
|
2432
|
+
readonly requires: readonly Capability[];
|
|
2433
|
+
readonly id: string;
|
|
2434
|
+
readonly title?: string;
|
|
2435
|
+
readonly severity: RuleSeverity;
|
|
2436
|
+
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
2437
|
+
readonly tags?: ReadonlyArray<string>;
|
|
2438
|
+
readonly matchByOccurrence?: boolean;
|
|
2439
|
+
readonly defaultEnabled?: boolean;
|
|
2440
|
+
readonly lifecycle?: "retired";
|
|
2441
|
+
readonly scan?: FileScan;
|
|
2442
|
+
readonly committedFilesOnly?: boolean;
|
|
2443
|
+
readonly recommendation?: string;
|
|
2444
|
+
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
2445
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
2446
|
+
};
|
|
2424
2447
|
}, {
|
|
2425
2448
|
readonly key: "react-doctor/jsx-no-undef";
|
|
2426
2449
|
readonly id: "jsx-no-undef";
|
|
@@ -11710,6 +11733,29 @@ declare const RULES: readonly [{
|
|
|
11710
11733
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
11711
11734
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11712
11735
|
};
|
|
11736
|
+
}, {
|
|
11737
|
+
readonly key: "react-doctor/jsx-no-target-blank";
|
|
11738
|
+
readonly id: "jsx-no-target-blank";
|
|
11739
|
+
readonly source: "react-doctor";
|
|
11740
|
+
readonly originallyExternal: true;
|
|
11741
|
+
readonly rule: {
|
|
11742
|
+
readonly framework: "global";
|
|
11743
|
+
readonly category: "Security";
|
|
11744
|
+
readonly requires: readonly Capability[];
|
|
11745
|
+
readonly id: string;
|
|
11746
|
+
readonly title?: string;
|
|
11747
|
+
readonly severity: RuleSeverity;
|
|
11748
|
+
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
11749
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11750
|
+
readonly matchByOccurrence?: boolean;
|
|
11751
|
+
readonly defaultEnabled?: boolean;
|
|
11752
|
+
readonly lifecycle?: "retired";
|
|
11753
|
+
readonly scan?: FileScan;
|
|
11754
|
+
readonly committedFilesOnly?: boolean;
|
|
11755
|
+
readonly recommendation?: string;
|
|
11756
|
+
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
11757
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11758
|
+
};
|
|
11713
11759
|
}, {
|
|
11714
11760
|
readonly key: "react-doctor/jsx-no-undef";
|
|
11715
11761
|
readonly id: "jsx-no-undef";
|
package/dist/index.js
CHANGED
|
@@ -2622,7 +2622,7 @@ const PREFER_ALT = "Screen readers skip a decorative image more reliably with `a
|
|
|
2622
2622
|
const MESSAGE_OBJECT = "Screen reader users cannot use this `<object>` because assistive tech cannot describe it, so add `alt`, `aria-label`, `aria-labelledby`, `title`, or inner fallback text.";
|
|
2623
2623
|
const MESSAGE_AREA = "Blind users can't use this `<area>` of the image map because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
|
|
2624
2624
|
const MESSAGE_INPUT_IMAGE = "Blind users can't use this image button because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
|
|
2625
|
-
const resolveSettings$
|
|
2625
|
+
const resolveSettings$53 = (settings) => {
|
|
2626
2626
|
const reactDoctor = settings?.["react-doctor"];
|
|
2627
2627
|
return typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.altText ?? {} : {};
|
|
2628
2628
|
};
|
|
@@ -2745,7 +2745,7 @@ const altText = defineRule({
|
|
|
2745
2745
|
category: "Accessibility",
|
|
2746
2746
|
create: (context) => {
|
|
2747
2747
|
if (isGeneratedImageRenderContext(context)) return {};
|
|
2748
|
-
const settings = resolveSettings$
|
|
2748
|
+
const settings = resolveSettings$53(context.settings);
|
|
2749
2749
|
const checkImg = !settings.elements || settings.elements.includes("img");
|
|
2750
2750
|
const checkObject = !settings.elements || settings.elements.includes("object");
|
|
2751
2751
|
const checkArea = !settings.elements || settings.elements.includes("area");
|
|
@@ -2807,7 +2807,7 @@ const DEFAULT_AMBIGUOUS = [
|
|
|
2807
2807
|
"a link",
|
|
2808
2808
|
"learn more"
|
|
2809
2809
|
];
|
|
2810
|
-
const resolveSettings$
|
|
2810
|
+
const resolveSettings$52 = (settings) => {
|
|
2811
2811
|
const reactDoctor = settings?.["react-doctor"];
|
|
2812
2812
|
return { words: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorAmbiguousText ?? {} : {}).words ?? DEFAULT_AMBIGUOUS };
|
|
2813
2813
|
};
|
|
@@ -2857,7 +2857,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2857
2857
|
category: "Accessibility",
|
|
2858
2858
|
create: (context) => {
|
|
2859
2859
|
if (isTestlikeFilename(context.filename)) return {};
|
|
2860
|
-
const settings = resolveSettings$
|
|
2860
|
+
const settings = resolveSettings$52(context.settings);
|
|
2861
2861
|
const ambiguousSet = new Set(settings.words.map((word) => word.toLowerCase()));
|
|
2862
2862
|
return { JSXElement(node) {
|
|
2863
2863
|
if (getElementType(node.openingElement, context.settings) !== "a") return;
|
|
@@ -3225,7 +3225,7 @@ const anchorHasContent = defineRule({
|
|
|
3225
3225
|
const MESSAGE_MISSING_HREF = "Keyboard users can't reach this link because it has no `href`, so add a real `href` (or use `<button>` for actions).";
|
|
3226
3226
|
const MESSAGE_INCORRECT_HREF = "Keyboard users can't reach this link because its `href` goes nowhere (`#`, `javascript:`, or empty), so point it at a real destination.";
|
|
3227
3227
|
const MESSAGE_CANT_BE_ANCHOR = "Keyboard users can't trigger this link because it's a click handler with no real `href`, so use `<button>` instead.";
|
|
3228
|
-
const resolveSettings$
|
|
3228
|
+
const resolveSettings$51 = (settings) => {
|
|
3229
3229
|
const reactDoctor = settings?.["react-doctor"];
|
|
3230
3230
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorIsValid ?? {} : {};
|
|
3231
3231
|
const jsxA11y = settings?.["jsx-a11y"];
|
|
@@ -3268,7 +3268,7 @@ const anchorIsValid = defineRule({
|
|
|
3268
3268
|
recommendation: "Give links a real destination. Use `<button>` for in-page actions.",
|
|
3269
3269
|
category: "Accessibility",
|
|
3270
3270
|
create: (context) => {
|
|
3271
|
-
const settings = resolveSettings$
|
|
3271
|
+
const settings = resolveSettings$51(context.settings);
|
|
3272
3272
|
const fileHasJsxA11ySettings = hasJsxA11ySettings(context.settings);
|
|
3273
3273
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
3274
3274
|
return { JSXOpeningElement(node) {
|
|
@@ -4181,7 +4181,7 @@ const ariaProptypes = defineRule({
|
|
|
4181
4181
|
//#endregion
|
|
4182
4182
|
//#region src/plugin/rules/a11y/aria-role.ts
|
|
4183
4183
|
const buildBaseMessage = (suffix) => `This \`role\` is not a valid ARIA role, so assistive tech cannot expose it correctly. Use a real, non-abstract role.${suffix}`;
|
|
4184
|
-
const resolveSettings$
|
|
4184
|
+
const resolveSettings$50 = (settings) => {
|
|
4185
4185
|
const reactDoctor = settings?.["react-doctor"];
|
|
4186
4186
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.ariaRole ?? {} : {};
|
|
4187
4187
|
return {
|
|
@@ -4197,7 +4197,7 @@ const ariaRole = defineRule({
|
|
|
4197
4197
|
recommendation: "Use a real, non-abstract ARIA role so assistive tech can expose the element correctly.",
|
|
4198
4198
|
category: "Accessibility",
|
|
4199
4199
|
create: (context) => {
|
|
4200
|
-
const settings = resolveSettings$
|
|
4200
|
+
const settings = resolveSettings$50(context.settings);
|
|
4201
4201
|
return { JSXOpeningElement(node) {
|
|
4202
4202
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
4203
4203
|
if (!roleAttribute) return;
|
|
@@ -7115,7 +7115,7 @@ const FORM_CONTROL_TAGS = new Set([
|
|
|
7115
7115
|
"select",
|
|
7116
7116
|
"form"
|
|
7117
7117
|
]);
|
|
7118
|
-
const resolveSettings$
|
|
7118
|
+
const resolveSettings$49 = (settings) => {
|
|
7119
7119
|
const reactDoctor = settings?.["react-doctor"];
|
|
7120
7120
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.autocompleteValid ?? {} : {};
|
|
7121
7121
|
return { inputComponents: new Set(ruleSettings.inputComponents ?? []) };
|
|
@@ -7128,7 +7128,7 @@ const autocompleteValid = defineRule({
|
|
|
7128
7128
|
recommendation: "Use a valid autofill token in `autoComplete` so browsers can fill the right field reliably.",
|
|
7129
7129
|
category: "Accessibility",
|
|
7130
7130
|
create: skipNonProductionFiles((context) => {
|
|
7131
|
-
const settings = resolveSettings$
|
|
7131
|
+
const settings = resolveSettings$49(context.settings);
|
|
7132
7132
|
return { JSXOpeningElement: (node) => {
|
|
7133
7133
|
const tag = getElementType(node, context.settings);
|
|
7134
7134
|
if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.has(tag)) return;
|
|
@@ -7247,7 +7247,7 @@ const isCreateElementCall = (node) => {
|
|
|
7247
7247
|
//#region src/plugin/rules/react-builtins/button-has-type.ts
|
|
7248
7248
|
const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
|
|
7249
7249
|
const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
|
|
7250
|
-
const resolveSettings$
|
|
7250
|
+
const resolveSettings$48 = (settings) => {
|
|
7251
7251
|
const reactDoctor = settings?.["react-doctor"];
|
|
7252
7252
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.buttonHasType ?? {} : {};
|
|
7253
7253
|
return {
|
|
@@ -7485,7 +7485,7 @@ const buttonHasType = defineRule({
|
|
|
7485
7485
|
severity: "warn",
|
|
7486
7486
|
recommendation: "Set an explicit button `type` so plain buttons do not submit forms by accident: `type=\"button\"`, `\"submit\"`, or `\"reset\"`.",
|
|
7487
7487
|
create: (context) => {
|
|
7488
|
-
const settings = resolveSettings$
|
|
7488
|
+
const settings = resolveSettings$48(context.settings);
|
|
7489
7489
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
7490
7490
|
return {
|
|
7491
7491
|
JSXOpeningElement(node) {
|
|
@@ -7566,7 +7566,7 @@ const buttonHasType = defineRule({
|
|
|
7566
7566
|
//#region src/plugin/rules/react-builtins/checked-requires-onchange-or-readonly.ts
|
|
7567
7567
|
const MISSING_MESSAGE$1 = "Your users can't toggle this input because `checked` has no `onChange`.";
|
|
7568
7568
|
const EXCLUSIVE_MESSAGE = "This input mixes `checked` with `defaultChecked`, so React can't tell whether it is controlled or uncontrolled.";
|
|
7569
|
-
const resolveSettings$
|
|
7569
|
+
const resolveSettings$47 = (settings) => {
|
|
7570
7570
|
const reactDoctor = settings?.["react-doctor"];
|
|
7571
7571
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.checkedRequiresOnchangeOrReadonly ?? {} : {};
|
|
7572
7572
|
return {
|
|
@@ -7667,7 +7667,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
7667
7667
|
recommendation: "Add `onChange`, `readOnly`, or `defaultChecked` so React knows whether the checkbox is editable, display-only, or uncontrolled.",
|
|
7668
7668
|
category: "Correctness",
|
|
7669
7669
|
create: (context) => {
|
|
7670
|
-
const settings = resolveSettings$
|
|
7670
|
+
const settings = resolveSettings$47(context.settings);
|
|
7671
7671
|
const reportFromPresence = (presence) => {
|
|
7672
7672
|
if (presence.checkedNode && presence.defaultCheckedNode && !presence.bothCheckedAndDefaultForwarded && !settings.ignoreExclusiveCheckedAttribute) context.report({
|
|
7673
7673
|
node: presence.checkedNode,
|
|
@@ -10380,7 +10380,7 @@ const WRAPPER_LABEL_PROP = "label";
|
|
|
10380
10380
|
const SELECT_ELEMENT = "select";
|
|
10381
10381
|
const DEFAULT_DEPTH = 5;
|
|
10382
10382
|
const MAX_DEPTH = 25;
|
|
10383
|
-
const resolveSettings$
|
|
10383
|
+
const resolveSettings$46 = (settings) => {
|
|
10384
10384
|
const reactDoctor = settings?.["react-doctor"];
|
|
10385
10385
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.controlHasAssociatedLabel ?? {} : {};
|
|
10386
10386
|
return {
|
|
@@ -10713,7 +10713,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
10713
10713
|
recommendation: "Give every interactive control a label screen readers can read.",
|
|
10714
10714
|
category: "Accessibility",
|
|
10715
10715
|
create: (context) => {
|
|
10716
|
-
const settings = resolveSettings$
|
|
10716
|
+
const settings = resolveSettings$46(context.settings);
|
|
10717
10717
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
10718
10718
|
const iconComponentNames = /* @__PURE__ */ new Set();
|
|
10719
10719
|
const iconNamespaceNames = /* @__PURE__ */ new Set();
|
|
@@ -12665,7 +12665,7 @@ const DEFAULT_ADDITIONAL_HOCS = [
|
|
|
12665
12665
|
"lazy",
|
|
12666
12666
|
"withTracking"
|
|
12667
12667
|
];
|
|
12668
|
-
const resolveSettings$
|
|
12668
|
+
const resolveSettings$45 = (settings) => {
|
|
12669
12669
|
const reactDoctor = settings?.["react-doctor"];
|
|
12670
12670
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
|
|
12671
12671
|
const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
|
|
@@ -12847,7 +12847,7 @@ const displayName = defineRule({
|
|
|
12847
12847
|
recommendation: "Give each component a `displayName` so DevTools shows a clear name.",
|
|
12848
12848
|
category: "Architecture",
|
|
12849
12849
|
create: (context) => {
|
|
12850
|
-
const settings = resolveSettings$
|
|
12850
|
+
const settings = resolveSettings$45(context.settings);
|
|
12851
12851
|
const ignoreNamed = !settings.ignoreTranspilerName;
|
|
12852
12852
|
const reportAt = (node) => {
|
|
12853
12853
|
context.report({
|
|
@@ -19016,7 +19016,7 @@ const compileGlob = (pattern) => {
|
|
|
19016
19016
|
};
|
|
19017
19017
|
//#endregion
|
|
19018
19018
|
//#region src/plugin/rules/react-builtins/forbid-component-props.ts
|
|
19019
|
-
const resolveSettings$
|
|
19019
|
+
const resolveSettings$44 = (settings) => {
|
|
19020
19020
|
const reactDoctor = settings?.["react-doctor"];
|
|
19021
19021
|
return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
|
|
19022
19022
|
};
|
|
@@ -19068,7 +19068,7 @@ const forbidComponentProps = defineRule({
|
|
|
19068
19068
|
recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
|
|
19069
19069
|
category: "Architecture",
|
|
19070
19070
|
create: (context) => {
|
|
19071
|
-
const entries = resolveSettings$
|
|
19071
|
+
const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
|
|
19072
19072
|
return { JSXOpeningElement(node) {
|
|
19073
19073
|
if (entries.length === 0) return;
|
|
19074
19074
|
if (!isSupportedJsxName(node.name)) return;
|
|
@@ -19097,7 +19097,7 @@ const forbidComponentProps = defineRule({
|
|
|
19097
19097
|
//#endregion
|
|
19098
19098
|
//#region src/plugin/rules/react-builtins/forbid-dom-props.ts
|
|
19099
19099
|
const buildMessage$24 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags, so this bypasses the agreed DOM API contract.`;
|
|
19100
|
-
const resolveSettings$
|
|
19100
|
+
const resolveSettings$43 = (settings) => {
|
|
19101
19101
|
const reactDoctor = settings?.["react-doctor"];
|
|
19102
19102
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
|
|
19103
19103
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -19119,7 +19119,7 @@ const forbidDomProps = defineRule({
|
|
|
19119
19119
|
recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
|
|
19120
19120
|
category: "Architecture",
|
|
19121
19121
|
create: (context) => {
|
|
19122
|
-
const forbidMap = resolveSettings$
|
|
19122
|
+
const forbidMap = resolveSettings$43(context.settings);
|
|
19123
19123
|
return { JSXOpeningElement(node) {
|
|
19124
19124
|
if (forbidMap.size === 0) return;
|
|
19125
19125
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -19182,7 +19182,7 @@ const isReactFunctionCall = (node, expectedCall) => {
|
|
|
19182
19182
|
//#endregion
|
|
19183
19183
|
//#region src/plugin/rules/react-builtins/forbid-elements.ts
|
|
19184
19184
|
const buildMessage$23 = (element, customHelp) => customHelp ? `Your project blocks \`<${element}>\` here. ${customHelp}` : `Your project blocks \`<${element}>\` here, so code stays on the approved UI surface.`;
|
|
19185
|
-
const resolveSettings$
|
|
19185
|
+
const resolveSettings$42 = (settings) => {
|
|
19186
19186
|
const reactDoctor = settings?.["react-doctor"];
|
|
19187
19187
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
|
|
19188
19188
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -19198,7 +19198,7 @@ const forbidElements = defineRule({
|
|
|
19198
19198
|
recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
|
|
19199
19199
|
category: "Architecture",
|
|
19200
19200
|
create: (context) => {
|
|
19201
|
-
const forbidMap = resolveSettings$
|
|
19201
|
+
const forbidMap = resolveSettings$42(context.settings);
|
|
19202
19202
|
return {
|
|
19203
19203
|
JSXOpeningElement(node) {
|
|
19204
19204
|
if (forbidMap.size === 0) return;
|
|
@@ -19307,7 +19307,7 @@ const DEFAULT_HEADING_TAGS = [
|
|
|
19307
19307
|
"h5",
|
|
19308
19308
|
"h6"
|
|
19309
19309
|
];
|
|
19310
|
-
const resolveSettings$
|
|
19310
|
+
const resolveSettings$41 = (settings) => {
|
|
19311
19311
|
const reactDoctor = settings?.["react-doctor"];
|
|
19312
19312
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
|
|
19313
19313
|
return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
|
|
@@ -19320,7 +19320,7 @@ const headingHasContent = defineRule({
|
|
|
19320
19320
|
recommendation: "Put readable text in every heading.",
|
|
19321
19321
|
category: "Accessibility",
|
|
19322
19322
|
create: (context) => {
|
|
19323
|
-
const settings = resolveSettings$
|
|
19323
|
+
const settings = resolveSettings$41(context.settings);
|
|
19324
19324
|
return { JSXOpeningElement(node) {
|
|
19325
19325
|
const elementType = getElementType(node, context.settings);
|
|
19326
19326
|
if (!settings.headingTags.has(elementType)) return;
|
|
@@ -19341,7 +19341,7 @@ const headingHasContent = defineRule({
|
|
|
19341
19341
|
//#region src/plugin/rules/react-builtins/hook-use-state.ts
|
|
19342
19342
|
const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
|
|
19343
19343
|
const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
|
|
19344
|
-
const resolveSettings$
|
|
19344
|
+
const resolveSettings$40 = (settings) => {
|
|
19345
19345
|
const reactDoctor = settings?.["react-doctor"];
|
|
19346
19346
|
return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
|
|
19347
19347
|
};
|
|
@@ -19368,7 +19368,7 @@ const hookUseState = defineRule({
|
|
|
19368
19368
|
recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
|
|
19369
19369
|
category: "Architecture",
|
|
19370
19370
|
create: (context) => {
|
|
19371
|
-
const { allowDestructuredState } = resolveSettings$
|
|
19371
|
+
const { allowDestructuredState } = resolveSettings$40(context.settings);
|
|
19372
19372
|
return { CallExpression(node) {
|
|
19373
19373
|
const callExpressionNode = node;
|
|
19374
19374
|
if (!isReactFunctionCall(callExpressionNode, "useState")) return;
|
|
@@ -19501,7 +19501,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19501
19501
|
//#endregion
|
|
19502
19502
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
19503
19503
|
const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
19504
|
-
const resolveSettings$
|
|
19504
|
+
const resolveSettings$39 = (settings) => {
|
|
19505
19505
|
const reactDoctor = settings?.["react-doctor"];
|
|
19506
19506
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
19507
19507
|
};
|
|
@@ -19538,7 +19538,7 @@ const htmlHasLang = defineRule({
|
|
|
19538
19538
|
recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
|
|
19539
19539
|
category: "Accessibility",
|
|
19540
19540
|
create: (context) => {
|
|
19541
|
-
const settings = resolveSettings$
|
|
19541
|
+
const settings = resolveSettings$39(context.settings);
|
|
19542
19542
|
const tagSet = new Set(settings.htmlTags);
|
|
19543
19543
|
return { JSXOpeningElement(node) {
|
|
19544
19544
|
const tag = getElementType(node, context.settings);
|
|
@@ -20054,7 +20054,7 @@ const DEFAULT_REDUNDANT_WORDS = [
|
|
|
20054
20054
|
"photo",
|
|
20055
20055
|
"picture"
|
|
20056
20056
|
];
|
|
20057
|
-
const resolveSettings$
|
|
20057
|
+
const resolveSettings$38 = (settings) => {
|
|
20058
20058
|
const reactDoctor = settings?.["react-doctor"];
|
|
20059
20059
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
|
|
20060
20060
|
return {
|
|
@@ -20102,7 +20102,7 @@ const imgRedundantAlt = defineRule({
|
|
|
20102
20102
|
category: "Accessibility",
|
|
20103
20103
|
create: (context) => {
|
|
20104
20104
|
if (isGeneratedImageRenderContext(context)) return {};
|
|
20105
|
-
const settings = resolveSettings$
|
|
20105
|
+
const settings = resolveSettings$38(context.settings);
|
|
20106
20106
|
return { JSXOpeningElement(node) {
|
|
20107
20107
|
const tag = getElementType(node, context.settings);
|
|
20108
20108
|
if (!settings.components.includes(tag)) return;
|
|
@@ -20410,7 +20410,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
|
|
|
20410
20410
|
"row",
|
|
20411
20411
|
"gridcell"
|
|
20412
20412
|
]);
|
|
20413
|
-
const resolveSettings$
|
|
20413
|
+
const resolveSettings$37 = (settings) => {
|
|
20414
20414
|
const reactDoctor = settings?.["react-doctor"];
|
|
20415
20415
|
return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
|
|
20416
20416
|
};
|
|
@@ -20422,7 +20422,7 @@ const interactiveSupportsFocus = defineRule({
|
|
|
20422
20422
|
recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
|
|
20423
20423
|
category: "Accessibility",
|
|
20424
20424
|
create: (context) => {
|
|
20425
|
-
const settings = resolveSettings$
|
|
20425
|
+
const settings = resolveSettings$37(context.settings);
|
|
20426
20426
|
const tabbableSet = new Set(settings.tabbable);
|
|
20427
20427
|
return { JSXOpeningElement(node) {
|
|
20428
20428
|
if (isLocalTestScaffoldJsx(node, context)) return;
|
|
@@ -23718,7 +23718,7 @@ const jsTosortedImmutable = defineRule({
|
|
|
23718
23718
|
const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
23719
23719
|
const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
23720
23720
|
const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
|
|
23721
|
-
const resolveSettings$
|
|
23721
|
+
const resolveSettings$36 = (settings) => {
|
|
23722
23722
|
const reactDoctor = settings?.["react-doctor"];
|
|
23723
23723
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
|
|
23724
23724
|
return {
|
|
@@ -23736,7 +23736,7 @@ const jsxBooleanValue = defineRule({
|
|
|
23736
23736
|
recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
|
|
23737
23737
|
category: "Architecture",
|
|
23738
23738
|
create: (context) => {
|
|
23739
|
-
const settings = resolveSettings$
|
|
23739
|
+
const settings = resolveSettings$36(context.settings);
|
|
23740
23740
|
const alwaysSet = new Set(settings.always);
|
|
23741
23741
|
const neverSet = new Set(settings.never);
|
|
23742
23742
|
return { JSXAttribute(node) {
|
|
@@ -23790,7 +23790,7 @@ const jsxBooleanValue = defineRule({
|
|
|
23790
23790
|
const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
|
|
23791
23791
|
const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
|
|
23792
23792
|
const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
|
|
23793
|
-
const resolveSettings$
|
|
23793
|
+
const resolveSettings$35 = (settings) => {
|
|
23794
23794
|
const reactDoctor = settings?.["react-doctor"];
|
|
23795
23795
|
const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
|
|
23796
23796
|
if (isAllowedMode(ruleSettingsRaw)) return {
|
|
@@ -23886,7 +23886,7 @@ const jsxCurlyBracePresence = defineRule({
|
|
|
23886
23886
|
recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
|
|
23887
23887
|
category: "Architecture",
|
|
23888
23888
|
create: (context) => {
|
|
23889
|
-
const settings = resolveSettings$
|
|
23889
|
+
const settings = resolveSettings$35(context.settings);
|
|
23890
23890
|
return {
|
|
23891
23891
|
JSXAttribute(node) {
|
|
23892
23892
|
const value = node.value;
|
|
@@ -23969,7 +23969,7 @@ const containsJsxElement = (root) => {
|
|
|
23969
23969
|
//#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
|
|
23970
23970
|
const JSX_NOT_ALLOWED = (extension) => `This file contains JSX but uses a \`${extension}\` name, so the filename no longer signals JSX to readers or tooling conventions.`;
|
|
23971
23971
|
const EXTENSION_ONLY_FOR_JSX = (extension) => `\`${extension}\` files are reserved for JSX here, so using that extension without JSX makes file-type conventions less useful.`;
|
|
23972
|
-
const resolveSettings$
|
|
23972
|
+
const resolveSettings$34 = (settings) => {
|
|
23973
23973
|
const reactDoctor = settings?.["react-doctor"];
|
|
23974
23974
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
|
|
23975
23975
|
return {
|
|
@@ -23991,7 +23991,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
23991
23991
|
recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
|
|
23992
23992
|
category: "Architecture",
|
|
23993
23993
|
create: (context) => {
|
|
23994
|
-
const settings = resolveSettings$
|
|
23994
|
+
const settings = resolveSettings$34(context.settings);
|
|
23995
23995
|
const allowedExtensions = normalizeExtensions(settings.extensions);
|
|
23996
23996
|
const filename = normalizeFilename(context.filename ?? "fixture.tsx");
|
|
23997
23997
|
const extensionOnly = path.extname(filename).slice(1);
|
|
@@ -24032,7 +24032,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
24032
24032
|
//#region src/plugin/rules/react-builtins/jsx-fragments.ts
|
|
24033
24033
|
const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
|
|
24034
24034
|
const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
|
|
24035
|
-
const resolveSettings$
|
|
24035
|
+
const resolveSettings$33 = (settings) => {
|
|
24036
24036
|
const reactDoctor = settings?.["react-doctor"];
|
|
24037
24037
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
|
|
24038
24038
|
};
|
|
@@ -24044,7 +24044,7 @@ const jsxFragments = defineRule({
|
|
|
24044
24044
|
recommendation: "Use one fragment style so identical wrappers do not look different across files.",
|
|
24045
24045
|
category: "Architecture",
|
|
24046
24046
|
create: (context) => {
|
|
24047
|
-
const { mode } = resolveSettings$
|
|
24047
|
+
const { mode } = resolveSettings$33(context.settings);
|
|
24048
24048
|
return {
|
|
24049
24049
|
JSXElement(node) {
|
|
24050
24050
|
if (mode !== "syntax") return;
|
|
@@ -24088,7 +24088,7 @@ const buildPropRegex = (handlerPropPrefix) => {
|
|
|
24088
24088
|
const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
24089
24089
|
return new RegExp(`^(${escaped})[A-Z].*$`);
|
|
24090
24090
|
};
|
|
24091
|
-
const resolveSettings$
|
|
24091
|
+
const resolveSettings$32 = (settings) => {
|
|
24092
24092
|
const reactDoctor = settings?.["react-doctor"];
|
|
24093
24093
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
|
|
24094
24094
|
let handlerPrefix = DEFAULT_HANDLER_PREFIX;
|
|
@@ -24164,7 +24164,7 @@ const jsxHandlerNames = defineRule({
|
|
|
24164
24164
|
recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
|
|
24165
24165
|
category: "Architecture",
|
|
24166
24166
|
create: (context) => {
|
|
24167
|
-
const settings = resolveSettings$
|
|
24167
|
+
const settings = resolveSettings$32(context.settings);
|
|
24168
24168
|
return { JSXAttribute(node) {
|
|
24169
24169
|
if (settings.ignoreComponentNames.length > 0) {
|
|
24170
24170
|
const opening = node.parent;
|
|
@@ -24239,19 +24239,6 @@ const hasJsxKeyAttribute = (openingElement) => {
|
|
|
24239
24239
|
return false;
|
|
24240
24240
|
};
|
|
24241
24241
|
//#endregion
|
|
24242
|
-
//#region src/plugin/utils/is-component-function.ts
|
|
24243
|
-
const isFunctionAssignedToComponent = (functionNode) => {
|
|
24244
|
-
let cursor = functionNode.parent ?? null;
|
|
24245
|
-
while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
24246
|
-
if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
|
|
24247
|
-
return isNodeOfType(cursor, "ExportDefaultDeclaration");
|
|
24248
|
-
};
|
|
24249
|
-
const isComponentFunction$1 = (node) => {
|
|
24250
|
-
if (!isFunctionLike$1(node)) return false;
|
|
24251
|
-
if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
|
|
24252
|
-
return isFunctionAssignedToComponent(node);
|
|
24253
|
-
};
|
|
24254
|
-
//#endregion
|
|
24255
24242
|
//#region src/plugin/utils/is-const-declared-binding.ts
|
|
24256
24243
|
const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
|
|
24257
24244
|
"VariableDeclarator",
|
|
@@ -24296,17 +24283,11 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
|
|
|
24296
24283
|
"flatMap",
|
|
24297
24284
|
"from"
|
|
24298
24285
|
]);
|
|
24299
|
-
const RENDERING_CALL_NAMES = new Set([
|
|
24300
|
-
"createPortal",
|
|
24301
|
-
"hydrate",
|
|
24302
|
-
"hydrateRoot",
|
|
24303
|
-
"render"
|
|
24304
|
-
]);
|
|
24305
24286
|
const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
|
|
24306
24287
|
const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
|
|
24307
|
-
const
|
|
24288
|
+
const KEY_AFTER_SPREAD = "Place this `key` before the `{...spread}` so the JSX transform can always extract it.";
|
|
24308
24289
|
const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
|
|
24309
|
-
const resolveSettings$
|
|
24290
|
+
const resolveSettings$31 = (settings) => {
|
|
24310
24291
|
const reactDoctor = settings?.["react-doctor"];
|
|
24311
24292
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
|
|
24312
24293
|
return {
|
|
@@ -24320,33 +24301,6 @@ const findArrayVariableDeclarator = (arrayExpression) => {
|
|
|
24320
24301
|
if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
|
|
24321
24302
|
return null;
|
|
24322
24303
|
};
|
|
24323
|
-
const isArrayNestedInObjectProperty = (arrayExpression) => {
|
|
24324
|
-
let current = findTransparentExpressionRoot(arrayExpression);
|
|
24325
|
-
while (current.parent) {
|
|
24326
|
-
const parent = current.parent;
|
|
24327
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current) || isNodeOfType(parent, "LogicalExpression") && (parent.left === current || parent.right === current)) {
|
|
24328
|
-
current = findTransparentExpressionRoot(parent);
|
|
24329
|
-
continue;
|
|
24330
|
-
}
|
|
24331
|
-
return isNodeOfType(parent, "Property") && parent.value === current;
|
|
24332
|
-
}
|
|
24333
|
-
return false;
|
|
24334
|
-
};
|
|
24335
|
-
const isArrayPassedToNonRenderingCall = (arrayExpression, scopes) => {
|
|
24336
|
-
const transparentArray = findTransparentExpressionRoot(arrayExpression);
|
|
24337
|
-
const callExpression = transparentArray.parent;
|
|
24338
|
-
if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || !callExpression.arguments.some((argument) => argument === transparentArray)) return false;
|
|
24339
|
-
const argumentIndex = callExpression.arguments.findIndex((argument) => argument === transparentArray);
|
|
24340
|
-
if (isReactApiCall(callExpression, "createElement", scopes, {
|
|
24341
|
-
allowGlobalReactNamespace: true,
|
|
24342
|
-
allowUnboundBareCalls: true,
|
|
24343
|
-
resolveNamedAliases: true
|
|
24344
|
-
})) return argumentIndex < 2;
|
|
24345
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
24346
|
-
if (isNodeOfType(callee, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.name);
|
|
24347
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.property.name);
|
|
24348
|
-
return true;
|
|
24349
|
-
};
|
|
24350
24304
|
const isIdentityIteratorCallback = (callback) => {
|
|
24351
24305
|
const callbackFunction = stripParenExpression(callback);
|
|
24352
24306
|
if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
|
|
@@ -24443,7 +24397,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
|
|
|
24443
24397
|
namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
|
|
24444
24398
|
return iteratorCall;
|
|
24445
24399
|
};
|
|
24446
|
-
const findEnclosingIteratorContext = (jsxNode
|
|
24400
|
+
const findEnclosingIteratorContext = (jsxNode) => {
|
|
24447
24401
|
let current = jsxNode;
|
|
24448
24402
|
let isOutsideContainingFunction = false;
|
|
24449
24403
|
let didSeeReturnStatement = false;
|
|
@@ -24465,8 +24419,6 @@ const findEnclosingIteratorContext = (jsxNode, scopes) => {
|
|
|
24465
24419
|
isOutsideContainingFunction = true;
|
|
24466
24420
|
} else if (isNodeOfType(parent, "ArrayExpression")) {
|
|
24467
24421
|
if (isOutsideContainingFunction) return null;
|
|
24468
|
-
if (isArrayNestedInObjectProperty(parent)) return null;
|
|
24469
|
-
if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
|
|
24470
24422
|
const arrayParent = parent.parent;
|
|
24471
24423
|
if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
|
|
24472
24424
|
if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
|
|
@@ -24548,15 +24500,9 @@ const isWithinChildrenToArray = (jsxNode) => {
|
|
|
24548
24500
|
const isKeyPropertyName = (propertyKey) => {
|
|
24549
24501
|
if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
|
|
24550
24502
|
if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
|
|
24551
|
-
return
|
|
24503
|
+
return true;
|
|
24552
24504
|
};
|
|
24553
24505
|
const isThisPropsMember = (expression) => isNodeOfType(expression, "MemberExpression") && !expression.computed && isNodeOfType(expression.object, "ThisExpression") && isNodeOfType(expression.property, "Identifier") && expression.property.name === "props";
|
|
24554
|
-
const isDirectComponentPropsParameter = (bindingIdentifier, functionNode) => {
|
|
24555
|
-
if (!isFunctionLike$1(functionNode) || !isComponentFunction$1(functionNode)) return false;
|
|
24556
|
-
const firstParameter = functionNode.params?.[0];
|
|
24557
|
-
if (!firstParameter) return false;
|
|
24558
|
-
return (isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter) === bindingIdentifier;
|
|
24559
|
-
};
|
|
24560
24506
|
const isFunctionParameterPropsRest = (bindingIdentifier) => {
|
|
24561
24507
|
const restElement = bindingIdentifier.parent;
|
|
24562
24508
|
if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
|
|
@@ -24566,7 +24512,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
|
|
|
24566
24512
|
const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
|
|
24567
24513
|
const functionNode = parameterNode.parent;
|
|
24568
24514
|
if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
|
|
24569
|
-
return
|
|
24515
|
+
return functionNode.params.some((param) => param === parameterNode);
|
|
24570
24516
|
};
|
|
24571
24517
|
const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
|
|
24572
24518
|
const restElement = bindingIdentifier.parent;
|
|
@@ -24579,14 +24525,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
|
|
|
24579
24525
|
}
|
|
24580
24526
|
return false;
|
|
24581
24527
|
};
|
|
24582
|
-
const
|
|
24583
|
-
let
|
|
24528
|
+
const isBindingObjectMutated = (scopeOwner, bindingName) => {
|
|
24529
|
+
let didFindMutation = false;
|
|
24584
24530
|
walkAst(scopeOwner, (node) => {
|
|
24585
|
-
if (
|
|
24531
|
+
if (didFindMutation) return false;
|
|
24586
24532
|
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
|
|
24587
24533
|
const assignedObject = stripParenExpression(node.left.object);
|
|
24588
|
-
if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName
|
|
24589
|
-
|
|
24534
|
+
if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
|
|
24535
|
+
didFindMutation = true;
|
|
24590
24536
|
return false;
|
|
24591
24537
|
}
|
|
24592
24538
|
}
|
|
@@ -24594,63 +24540,59 @@ const isBindingAssignedKey = (scopeOwner, bindingName) => {
|
|
|
24594
24540
|
const callee = node.callee;
|
|
24595
24541
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
|
|
24596
24542
|
const firstArgument = node.arguments[0];
|
|
24597
|
-
if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName)
|
|
24598
|
-
|
|
24599
|
-
|
|
24600
|
-
if (source.properties.some((property) => isNodeOfType(property, "Property") && isKeyPropertyName(property.key))) {
|
|
24601
|
-
didFindKeyAssignment = true;
|
|
24602
|
-
return false;
|
|
24603
|
-
}
|
|
24543
|
+
if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
|
|
24544
|
+
didFindMutation = true;
|
|
24545
|
+
return false;
|
|
24604
24546
|
}
|
|
24605
24547
|
}
|
|
24606
24548
|
}
|
|
24607
24549
|
});
|
|
24608
|
-
return
|
|
24550
|
+
return didFindMutation;
|
|
24609
24551
|
};
|
|
24610
|
-
const
|
|
24552
|
+
const spreadExpressionCanCarryKey = (expression, depth) => {
|
|
24611
24553
|
const inner = stripParenExpression(expression);
|
|
24612
24554
|
if (isNodeOfType(inner, "ObjectExpression")) {
|
|
24613
24555
|
for (const property of inner.properties) {
|
|
24614
24556
|
if (isNodeOfType(property, "SpreadElement")) {
|
|
24615
|
-
if (depth >= 3)
|
|
24616
|
-
if (
|
|
24557
|
+
if (depth >= 3) return true;
|
|
24558
|
+
if (spreadExpressionCanCarryKey(property.argument, depth + 1)) return true;
|
|
24617
24559
|
continue;
|
|
24618
24560
|
}
|
|
24619
|
-
if (!isNodeOfType(property, "Property"))
|
|
24561
|
+
if (!isNodeOfType(property, "Property")) return true;
|
|
24562
|
+
if (property.computed) return true;
|
|
24620
24563
|
if (isKeyPropertyName(property.key)) return true;
|
|
24621
24564
|
}
|
|
24622
24565
|
return false;
|
|
24623
24566
|
}
|
|
24624
24567
|
if (isNodeOfType(inner, "ConditionalExpression")) {
|
|
24625
|
-
if (depth >= 3) return
|
|
24626
|
-
return
|
|
24568
|
+
if (depth >= 3) return true;
|
|
24569
|
+
return spreadExpressionCanCarryKey(inner.consequent, depth + 1) || spreadExpressionCanCarryKey(inner.alternate, depth + 1);
|
|
24627
24570
|
}
|
|
24628
24571
|
if (isNodeOfType(inner, "LogicalExpression")) {
|
|
24629
|
-
if (depth >= 3) return
|
|
24630
|
-
if (inner.operator === "&&") return
|
|
24631
|
-
return
|
|
24572
|
+
if (depth >= 3) return true;
|
|
24573
|
+
if (inner.operator === "&&") return spreadExpressionCanCarryKey(inner.right, depth + 1);
|
|
24574
|
+
return spreadExpressionCanCarryKey(inner.left, depth + 1) || spreadExpressionCanCarryKey(inner.right, depth + 1);
|
|
24632
24575
|
}
|
|
24633
24576
|
if (isNodeOfType(inner, "Literal")) return false;
|
|
24634
24577
|
if (isThisPropsMember(inner)) return false;
|
|
24635
24578
|
if (isNodeOfType(inner, "Identifier")) {
|
|
24636
24579
|
if (inner.name === "undefined") return false;
|
|
24637
|
-
if (depth >= 3) return
|
|
24580
|
+
if (depth >= 3) return true;
|
|
24638
24581
|
const binding = findVariableInitializer(inner, inner.name);
|
|
24639
|
-
if (!binding) return
|
|
24582
|
+
if (!binding) return true;
|
|
24640
24583
|
if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
|
|
24641
24584
|
if (isFunctionParameterPropsRest(binding.bindingIdentifier)) return false;
|
|
24642
|
-
if (
|
|
24643
|
-
if (
|
|
24644
|
-
|
|
24645
|
-
return spreadExpressionHasKey(binding.initializer, depth + 1);
|
|
24585
|
+
if (!isConstDeclaredBinding(binding) || !binding.initializer) return true;
|
|
24586
|
+
if (isBindingObjectMutated(binding.scopeOwner, inner.name)) return true;
|
|
24587
|
+
return spreadExpressionCanCarryKey(binding.initializer, depth + 1);
|
|
24646
24588
|
}
|
|
24647
|
-
return
|
|
24589
|
+
return true;
|
|
24648
24590
|
};
|
|
24649
|
-
const spreadCanOverwriteKey = (spreadAttribute) =>
|
|
24591
|
+
const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionCanCarryKey(spreadAttribute.argument, 0);
|
|
24650
24592
|
const checkKeyBeforeSpread = (context, openingElement) => {
|
|
24651
24593
|
let keyIndex = null;
|
|
24652
24594
|
let keyAttribute = null;
|
|
24653
|
-
let
|
|
24595
|
+
let firstKeyCarryingSpreadIndex = null;
|
|
24654
24596
|
for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
|
|
24655
24597
|
const attribute = openingElement.attributes[attributeIndex];
|
|
24656
24598
|
if (isNodeOfType(attribute, "JSXAttribute")) {
|
|
@@ -24658,11 +24600,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
|
|
|
24658
24600
|
keyIndex = attributeIndex;
|
|
24659
24601
|
keyAttribute = attribute;
|
|
24660
24602
|
}
|
|
24661
|
-
} else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute))
|
|
24603
|
+
} else if (isNodeOfType(attribute, "JSXSpreadAttribute") && firstKeyCarryingSpreadIndex === null && spreadCanOverwriteKey(attribute)) firstKeyCarryingSpreadIndex = attributeIndex;
|
|
24662
24604
|
}
|
|
24663
|
-
if (keyIndex !== null &&
|
|
24605
|
+
if (keyIndex !== null && firstKeyCarryingSpreadIndex !== null && firstKeyCarryingSpreadIndex < keyIndex && keyAttribute) context.report({
|
|
24664
24606
|
node: keyAttribute,
|
|
24665
|
-
message:
|
|
24607
|
+
message: KEY_AFTER_SPREAD
|
|
24666
24608
|
});
|
|
24667
24609
|
};
|
|
24668
24610
|
const getKeyAttributeValueString = (openingElement) => {
|
|
@@ -24706,7 +24648,7 @@ const jsxKey = defineRule({
|
|
|
24706
24648
|
severity: "error",
|
|
24707
24649
|
recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
|
|
24708
24650
|
create: (context) => {
|
|
24709
|
-
const settings = resolveSettings$
|
|
24651
|
+
const settings = resolveSettings$31(context.settings);
|
|
24710
24652
|
return {
|
|
24711
24653
|
JSXElement(node) {
|
|
24712
24654
|
const openingElement = node.openingElement;
|
|
@@ -24724,7 +24666,7 @@ const jsxKey = defineRule({
|
|
|
24724
24666
|
else seenKeys.add(keyValue.keyValue);
|
|
24725
24667
|
}
|
|
24726
24668
|
}
|
|
24727
|
-
const enclosingContext = findEnclosingIteratorContext(node
|
|
24669
|
+
const enclosingContext = findEnclosingIteratorContext(node);
|
|
24728
24670
|
if (!enclosingContext) return;
|
|
24729
24671
|
if (isWithinChildrenToArray(node)) return;
|
|
24730
24672
|
if (hasJsxKeyAttribute(openingElement)) return;
|
|
@@ -24760,7 +24702,7 @@ const jsxKey = defineRule({
|
|
|
24760
24702
|
//#region src/plugin/rules/react-builtins/jsx-max-depth.ts
|
|
24761
24703
|
const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
|
|
24762
24704
|
const DEFAULT_MAX_DEPTH = 14;
|
|
24763
|
-
const resolveSettings$
|
|
24705
|
+
const resolveSettings$30 = (settings) => {
|
|
24764
24706
|
const reactDoctor = settings?.["react-doctor"];
|
|
24765
24707
|
return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
|
|
24766
24708
|
};
|
|
@@ -24829,7 +24771,7 @@ const jsxMaxDepth = defineRule({
|
|
|
24829
24771
|
recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
|
|
24830
24772
|
category: "Architecture",
|
|
24831
24773
|
create: (context) => {
|
|
24832
|
-
const { max } = resolveSettings$
|
|
24774
|
+
const { max } = resolveSettings$30(context.settings);
|
|
24833
24775
|
const deepestLeafPerTree = /* @__PURE__ */ new Map();
|
|
24834
24776
|
const checkNode = (node) => {
|
|
24835
24777
|
if (!isLeafJsxNode(node)) return;
|
|
@@ -26801,7 +26743,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26801
26743
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
26802
26744
|
const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
26803
26745
|
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;
|
|
26804
|
-
const resolveSettings$
|
|
26746
|
+
const resolveSettings$29 = (settings) => {
|
|
26805
26747
|
const reactDoctor = settings?.["react-doctor"];
|
|
26806
26748
|
if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
|
|
26807
26749
|
return reactDoctor.jsxNoScriptUrl ?? {};
|
|
@@ -26823,7 +26765,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26823
26765
|
recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
|
|
26824
26766
|
category: "Security",
|
|
26825
26767
|
create: (context) => {
|
|
26826
|
-
const options = resolveSettings$
|
|
26768
|
+
const options = resolveSettings$29(context.settings);
|
|
26827
26769
|
return { JSXOpeningElement(node) {
|
|
26828
26770
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
26829
26771
|
const elementName = resolveJsxElementType(node);
|
|
@@ -26844,6 +26786,283 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26844
26786
|
}
|
|
26845
26787
|
});
|
|
26846
26788
|
//#endregion
|
|
26789
|
+
//#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
|
|
26790
|
+
const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
|
|
26791
|
+
const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
|
|
26792
|
+
const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
|
|
26793
|
+
const resolveSettings$28 = (settings) => {
|
|
26794
|
+
const reactDoctor = settings?.["react-doctor"];
|
|
26795
|
+
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
|
|
26796
|
+
const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
|
|
26797
|
+
const linkComponents = /* @__PURE__ */ new Map();
|
|
26798
|
+
for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
|
|
26799
|
+
else if (typeof entry === "object" && entry !== null) {
|
|
26800
|
+
const linkAttribute = entry.linkAttribute ?? "href";
|
|
26801
|
+
linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
|
|
26802
|
+
}
|
|
26803
|
+
const formComponents = /* @__PURE__ */ new Map();
|
|
26804
|
+
for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
|
|
26805
|
+
else if (typeof entry === "object" && entry !== null) {
|
|
26806
|
+
const formAttribute = entry.formAttribute ?? "action";
|
|
26807
|
+
formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
|
|
26808
|
+
}
|
|
26809
|
+
return {
|
|
26810
|
+
enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
|
|
26811
|
+
warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
|
|
26812
|
+
allowReferrer: ruleSettings.allowReferrer ?? false,
|
|
26813
|
+
links: ruleSettings.links ?? true,
|
|
26814
|
+
forms: ruleSettings.forms ?? false,
|
|
26815
|
+
linkComponents,
|
|
26816
|
+
formComponents
|
|
26817
|
+
};
|
|
26818
|
+
};
|
|
26819
|
+
const isExternalLink = (href) => href.includes("//");
|
|
26820
|
+
const matchHrefExpression = (expression, state) => {
|
|
26821
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
|
|
26822
|
+
if (isExternalLink(expression.value)) state.isExternal = true;
|
|
26823
|
+
return;
|
|
26824
|
+
}
|
|
26825
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
26826
|
+
state.isDynamic = true;
|
|
26827
|
+
return;
|
|
26828
|
+
}
|
|
26829
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26830
|
+
matchHrefExpression(expression.consequent, state);
|
|
26831
|
+
matchHrefExpression(expression.alternate, state);
|
|
26832
|
+
}
|
|
26833
|
+
};
|
|
26834
|
+
const checkHref = (attributeValue, enforceDynamicLinks) => {
|
|
26835
|
+
const state = {
|
|
26836
|
+
isExternal: false,
|
|
26837
|
+
isDynamic: false
|
|
26838
|
+
};
|
|
26839
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
|
|
26840
|
+
else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
|
|
26841
|
+
if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
|
|
26842
|
+
return !(state.isExternal || state.isDynamic);
|
|
26843
|
+
};
|
|
26844
|
+
const checkRelValue = (text, allowReferrer) => {
|
|
26845
|
+
const tokens = text.split(/\s+/);
|
|
26846
|
+
if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
|
|
26847
|
+
return tokens.some((token) => token.toLowerCase() === "noreferrer");
|
|
26848
|
+
};
|
|
26849
|
+
const matchRelExpression = (expression, allowReferrer) => {
|
|
26850
|
+
const empty = {
|
|
26851
|
+
combined: false,
|
|
26852
|
+
testName: "",
|
|
26853
|
+
consequent: false,
|
|
26854
|
+
alternate: false
|
|
26855
|
+
};
|
|
26856
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
26857
|
+
combined: checkRelValue(expression.value, allowReferrer),
|
|
26858
|
+
testName: "",
|
|
26859
|
+
consequent: false,
|
|
26860
|
+
alternate: false
|
|
26861
|
+
};
|
|
26862
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26863
|
+
const consequent = matchRelExpression(expression.consequent, allowReferrer);
|
|
26864
|
+
const alternate = matchRelExpression(expression.alternate, allowReferrer);
|
|
26865
|
+
const test = expression.test;
|
|
26866
|
+
if (isNodeOfType(test, "Identifier")) return {
|
|
26867
|
+
combined: consequent.combined && alternate.combined,
|
|
26868
|
+
testName: test.name,
|
|
26869
|
+
consequent: consequent.combined,
|
|
26870
|
+
alternate: alternate.combined
|
|
26871
|
+
};
|
|
26872
|
+
return {
|
|
26873
|
+
combined: consequent.combined && alternate.combined,
|
|
26874
|
+
testName: "",
|
|
26875
|
+
consequent: consequent.combined,
|
|
26876
|
+
alternate: alternate.combined
|
|
26877
|
+
};
|
|
26878
|
+
}
|
|
26879
|
+
return empty;
|
|
26880
|
+
};
|
|
26881
|
+
const checkRel = (attributeValue, allowReferrer) => {
|
|
26882
|
+
const empty = {
|
|
26883
|
+
combined: false,
|
|
26884
|
+
testName: "",
|
|
26885
|
+
consequent: false,
|
|
26886
|
+
alternate: false
|
|
26887
|
+
};
|
|
26888
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
26889
|
+
combined: checkRelValue(attributeValue.value, allowReferrer),
|
|
26890
|
+
testName: "",
|
|
26891
|
+
consequent: false,
|
|
26892
|
+
alternate: false
|
|
26893
|
+
};
|
|
26894
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
26895
|
+
const expression = attributeValue.expression;
|
|
26896
|
+
if (expression.type === "JSXEmptyExpression") return empty;
|
|
26897
|
+
return matchRelExpression(expression, allowReferrer);
|
|
26898
|
+
}
|
|
26899
|
+
return empty;
|
|
26900
|
+
};
|
|
26901
|
+
const matchTargetExpression = (expression) => {
|
|
26902
|
+
const empty = {
|
|
26903
|
+
combined: false,
|
|
26904
|
+
testName: "",
|
|
26905
|
+
consequent: false,
|
|
26906
|
+
alternate: false
|
|
26907
|
+
};
|
|
26908
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
26909
|
+
combined: expression.value.toLowerCase() === "_blank",
|
|
26910
|
+
testName: "",
|
|
26911
|
+
consequent: false,
|
|
26912
|
+
alternate: false
|
|
26913
|
+
};
|
|
26914
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26915
|
+
const consequent = matchTargetExpression(expression.consequent);
|
|
26916
|
+
const alternate = matchTargetExpression(expression.alternate);
|
|
26917
|
+
const test = expression.test;
|
|
26918
|
+
const combined = consequent.combined || alternate.combined;
|
|
26919
|
+
if (isNodeOfType(test, "Identifier")) return {
|
|
26920
|
+
combined,
|
|
26921
|
+
testName: test.name,
|
|
26922
|
+
consequent: consequent.combined,
|
|
26923
|
+
alternate: alternate.combined
|
|
26924
|
+
};
|
|
26925
|
+
return {
|
|
26926
|
+
combined,
|
|
26927
|
+
testName: "",
|
|
26928
|
+
consequent: consequent.combined,
|
|
26929
|
+
alternate: alternate.combined
|
|
26930
|
+
};
|
|
26931
|
+
}
|
|
26932
|
+
return empty;
|
|
26933
|
+
};
|
|
26934
|
+
const checkTarget = (attributeValue) => {
|
|
26935
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
26936
|
+
combined: attributeValue.value.toLowerCase() === "_blank",
|
|
26937
|
+
testName: "",
|
|
26938
|
+
consequent: false,
|
|
26939
|
+
alternate: false
|
|
26940
|
+
};
|
|
26941
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
26942
|
+
const expression = attributeValue.expression;
|
|
26943
|
+
if (expression.type === "JSXEmptyExpression") return {
|
|
26944
|
+
combined: false,
|
|
26945
|
+
testName: "",
|
|
26946
|
+
consequent: false,
|
|
26947
|
+
alternate: false
|
|
26948
|
+
};
|
|
26949
|
+
return matchTargetExpression(expression);
|
|
26950
|
+
}
|
|
26951
|
+
return {
|
|
26952
|
+
combined: false,
|
|
26953
|
+
testName: "",
|
|
26954
|
+
consequent: false,
|
|
26955
|
+
alternate: false
|
|
26956
|
+
};
|
|
26957
|
+
};
|
|
26958
|
+
const jsxNoTargetBlank = defineRule({
|
|
26959
|
+
id: "jsx-no-target-blank",
|
|
26960
|
+
title: "Unsafe target=_blank link",
|
|
26961
|
+
severity: "warn",
|
|
26962
|
+
recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
|
|
26963
|
+
category: "Security",
|
|
26964
|
+
create: (context) => {
|
|
26965
|
+
const settings = resolveSettings$28(context.settings);
|
|
26966
|
+
const isLink = (tagName) => {
|
|
26967
|
+
if (!settings.links) return false;
|
|
26968
|
+
if (tagName === "a") return true;
|
|
26969
|
+
return settings.linkComponents.has(tagName);
|
|
26970
|
+
};
|
|
26971
|
+
const isForm = (tagName) => {
|
|
26972
|
+
if (!settings.forms) return false;
|
|
26973
|
+
if (tagName === "form") return true;
|
|
26974
|
+
return settings.formComponents.has(tagName);
|
|
26975
|
+
};
|
|
26976
|
+
return { JSXOpeningElement(node) {
|
|
26977
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
26978
|
+
const tagName = resolveJsxElementType(node);
|
|
26979
|
+
if (!tagName) return;
|
|
26980
|
+
if (!isLink(tagName) && !isForm(tagName)) return;
|
|
26981
|
+
const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
|
|
26982
|
+
const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
|
|
26983
|
+
let targetTuple = {
|
|
26984
|
+
combined: false,
|
|
26985
|
+
testName: "",
|
|
26986
|
+
consequent: false,
|
|
26987
|
+
alternate: false
|
|
26988
|
+
};
|
|
26989
|
+
let relTuple = {
|
|
26990
|
+
combined: false,
|
|
26991
|
+
testName: "",
|
|
26992
|
+
consequent: false,
|
|
26993
|
+
alternate: false
|
|
26994
|
+
};
|
|
26995
|
+
let isHrefValid = true;
|
|
26996
|
+
let hasHrefValue = false;
|
|
26997
|
+
let warnSpread = false;
|
|
26998
|
+
let targetReportNode = node.name;
|
|
26999
|
+
let spreadReportNode = null;
|
|
27000
|
+
for (const attribute of node.attributes) {
|
|
27001
|
+
if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
|
|
27002
|
+
if (settings.warnOnSpreadAttributes) {
|
|
27003
|
+
warnSpread = true;
|
|
27004
|
+
spreadReportNode = attribute;
|
|
27005
|
+
targetTuple = {
|
|
27006
|
+
combined: false,
|
|
27007
|
+
testName: "",
|
|
27008
|
+
consequent: false,
|
|
27009
|
+
alternate: false
|
|
27010
|
+
};
|
|
27011
|
+
relTuple = {
|
|
27012
|
+
combined: false,
|
|
27013
|
+
testName: "",
|
|
27014
|
+
consequent: false,
|
|
27015
|
+
alternate: false
|
|
27016
|
+
};
|
|
27017
|
+
isHrefValid = false;
|
|
27018
|
+
hasHrefValue = true;
|
|
27019
|
+
}
|
|
27020
|
+
continue;
|
|
27021
|
+
}
|
|
27022
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
27023
|
+
const attributeName = attribute.name;
|
|
27024
|
+
if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
|
|
27025
|
+
const propName = attributeName.name;
|
|
27026
|
+
const value = attribute.value;
|
|
27027
|
+
if (propName === "target") {
|
|
27028
|
+
if (value) {
|
|
27029
|
+
targetTuple = checkTarget(value);
|
|
27030
|
+
targetReportNode = value;
|
|
27031
|
+
}
|
|
27032
|
+
} else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
|
|
27033
|
+
if (value) {
|
|
27034
|
+
hasHrefValue = true;
|
|
27035
|
+
isHrefValid = checkHref(value, settings.enforceDynamicLinks);
|
|
27036
|
+
}
|
|
27037
|
+
} else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
|
|
27038
|
+
}
|
|
27039
|
+
if (warnSpread) {
|
|
27040
|
+
if (hasHrefValue && isHrefValid || relTuple.combined) return;
|
|
27041
|
+
context.report({
|
|
27042
|
+
node: spreadReportNode ?? node,
|
|
27043
|
+
message: SPREAD_MESSAGE
|
|
27044
|
+
});
|
|
27045
|
+
return;
|
|
27046
|
+
}
|
|
27047
|
+
if (!isHrefValid) {
|
|
27048
|
+
if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
|
|
27049
|
+
const consequentBad = targetTuple.consequent && !relTuple.consequent;
|
|
27050
|
+
const alternateBad = targetTuple.alternate && !relTuple.alternate;
|
|
27051
|
+
if (consequentBad || alternateBad) context.report({
|
|
27052
|
+
node: targetReportNode,
|
|
27053
|
+
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
27054
|
+
});
|
|
27055
|
+
return;
|
|
27056
|
+
}
|
|
27057
|
+
if (targetTuple.combined && !relTuple.combined) context.report({
|
|
27058
|
+
node: targetReportNode,
|
|
27059
|
+
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
27060
|
+
});
|
|
27061
|
+
}
|
|
27062
|
+
} };
|
|
27063
|
+
}
|
|
27064
|
+
});
|
|
27065
|
+
//#endregion
|
|
26847
27066
|
//#region src/plugin/rules/react-builtins/jsx-no-undef.ts
|
|
26848
27067
|
const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
|
|
26849
27068
|
const KNOWN_GLOBALS = new Set([
|
|
@@ -28501,30 +28720,6 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
|
|
|
28501
28720
|
const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
|
|
28502
28721
|
//#endregion
|
|
28503
28722
|
//#region src/plugin/rules/nextjs/nextjs-missing-metadata.ts
|
|
28504
|
-
const unwrapAwaitedExpression = (node) => {
|
|
28505
|
-
let expression = stripParenExpression(node);
|
|
28506
|
-
while (isNodeOfType(expression, "AwaitExpression")) expression = stripParenExpression(expression.argument);
|
|
28507
|
-
return expression;
|
|
28508
|
-
};
|
|
28509
|
-
const isNextNavigationRedirectCall = (node, context) => {
|
|
28510
|
-
const expression = unwrapAwaitedExpression(node);
|
|
28511
|
-
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
28512
|
-
const callee = stripParenExpression(expression.callee);
|
|
28513
|
-
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
28514
|
-
if (context.scopes.symbolFor(callee)?.kind !== "import") return false;
|
|
28515
|
-
const importedName = getImportedNameFromModule(expression, callee.name, "next/navigation");
|
|
28516
|
-
return importedName === "redirect" || importedName === "permanentRedirect";
|
|
28517
|
-
};
|
|
28518
|
-
const isRedirectOnlyDefaultExport = (programNode, context) => {
|
|
28519
|
-
const defaultExport = findExportedValue(programNode, "default");
|
|
28520
|
-
if (!defaultExport || !isFunctionLike$1(defaultExport)) return false;
|
|
28521
|
-
if (!isNodeOfType(defaultExport.body, "BlockStatement")) return isNextNavigationRedirectCall(defaultExport.body, context);
|
|
28522
|
-
if (defaultExport.body.body.length !== 1) return false;
|
|
28523
|
-
const onlyStatement = defaultExport.body.body[0];
|
|
28524
|
-
if (isNodeOfType(onlyStatement, "ExpressionStatement")) return isNextNavigationRedirectCall(onlyStatement.expression, context);
|
|
28525
|
-
if (isNodeOfType(onlyStatement, "ReturnStatement") && onlyStatement.argument) return isNextNavigationRedirectCall(onlyStatement.argument, context);
|
|
28526
|
-
return false;
|
|
28527
|
-
};
|
|
28528
28723
|
const nextjsMissingMetadata = defineRule({
|
|
28529
28724
|
id: "nextjs-missing-metadata",
|
|
28530
28725
|
title: "Page missing metadata for search previews",
|
|
@@ -28544,7 +28739,6 @@ const nextjsMissingMetadata = defineRule({
|
|
|
28544
28739
|
if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
|
|
28545
28740
|
return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
|
|
28546
28741
|
})) return;
|
|
28547
|
-
if (isRedirectOnlyDefaultExport(programNode, context)) return;
|
|
28548
28742
|
if (hasAncestorMetadataLayout(context.filename ?? "")) return;
|
|
28549
28743
|
context.report({
|
|
28550
28744
|
node: programNode,
|
|
@@ -36803,6 +36997,19 @@ const noDefaultProps = defineRule({
|
|
|
36803
36997
|
} })
|
|
36804
36998
|
});
|
|
36805
36999
|
//#endregion
|
|
37000
|
+
//#region src/plugin/utils/is-component-function.ts
|
|
37001
|
+
const isFunctionAssignedToComponent = (functionNode) => {
|
|
37002
|
+
let cursor = functionNode.parent ?? null;
|
|
37003
|
+
while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
37004
|
+
if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
|
|
37005
|
+
return isNodeOfType(cursor, "ExportDefaultDeclaration");
|
|
37006
|
+
};
|
|
37007
|
+
const isComponentFunction$1 = (node) => {
|
|
37008
|
+
if (!isFunctionLike$1(node)) return false;
|
|
37009
|
+
if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
|
|
37010
|
+
return isFunctionAssignedToComponent(node);
|
|
37011
|
+
};
|
|
37012
|
+
//#endregion
|
|
36806
37013
|
//#region src/plugin/utils/create-component-prop-stack-tracker.ts
|
|
36807
37014
|
const extractDestructuredPropNames = (params) => {
|
|
36808
37015
|
const propNames = /* @__PURE__ */ new Set();
|
|
@@ -47186,103 +47393,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
|
47186
47393
|
"useVisibility",
|
|
47187
47394
|
"useWindowSize"
|
|
47188
47395
|
]);
|
|
47189
|
-
const EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES = new Set([
|
|
47190
|
-
"useMatchMedia",
|
|
47191
|
-
"useMediaQuery",
|
|
47192
|
-
"useVisibility"
|
|
47193
|
-
]);
|
|
47194
|
-
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
47195
|
-
const getImportedExternalSubscriptionHookName = (analysis, rawCallee) => {
|
|
47196
|
-
const callee = stripParenExpression(rawCallee);
|
|
47197
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
47198
|
-
const calleeRef = getRef(analysis, callee);
|
|
47199
|
-
if (!calleeRef || !isImportBindingRef(calleeRef)) return null;
|
|
47200
|
-
const importBinding = getImportBindingForName(callee, callee.name);
|
|
47201
|
-
if (!importBinding || importBinding.isNamespace) return null;
|
|
47202
|
-
if (importBinding.exportedName && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(importBinding.exportedName)) return importBinding.exportedName;
|
|
47203
|
-
return importBinding.exportedName === "default" && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name) ? callee.name : null;
|
|
47204
|
-
}
|
|
47205
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47206
|
-
const hookName = getStaticMemberPropertyName(callee);
|
|
47207
|
-
const namespaceIdentifier = stripParenExpression(callee.object);
|
|
47208
|
-
if (!hookName || !EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(hookName) || !isNodeOfType(namespaceIdentifier, "Identifier")) return null;
|
|
47209
|
-
const namespaceRef = getRef(analysis, namespaceIdentifier);
|
|
47210
|
-
if (!namespaceRef || !isImportBindingRef(namespaceRef)) return null;
|
|
47211
|
-
return getImportBindingForName(namespaceIdentifier, namespaceIdentifier.name)?.isNamespace ? hookName : null;
|
|
47212
|
-
};
|
|
47213
|
-
const isSafeExternalSubscriptionResultBinding = (bindingIdentifier, bindingPattern) => {
|
|
47214
|
-
let current = bindingIdentifier;
|
|
47215
|
-
let didCrossDestructuringBoundary = false;
|
|
47216
|
-
while (current !== bindingPattern) {
|
|
47217
|
-
const parent = current.parent;
|
|
47218
|
-
if (!parent || isNodeOfType(parent, "AssignmentPattern") || isNodeOfType(parent, "RestElement")) return false;
|
|
47219
|
-
if (isNodeOfType(parent, "Property")) {
|
|
47220
|
-
if (parent.value !== current) return false;
|
|
47221
|
-
didCrossDestructuringBoundary = true;
|
|
47222
|
-
}
|
|
47223
|
-
if (isNodeOfType(parent, "ArrayPattern")) didCrossDestructuringBoundary = true;
|
|
47224
|
-
current = parent;
|
|
47225
|
-
}
|
|
47226
|
-
return didCrossDestructuringBoundary;
|
|
47227
|
-
};
|
|
47228
|
-
const getVariablesDefinedByDeclarator = (analysis, declarator) => analysis.scopeManager.scopes.flatMap((scope) => scope.variables).filter((variable) => variable.defs.some((definition) => definition.node === declarator));
|
|
47229
|
-
const hasUnsafeExternalSubscriptionBindingUse = (analysis, variable, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
47230
|
-
if (visitedVariables.has(variable)) return false;
|
|
47231
|
-
visitedVariables.add(variable);
|
|
47232
|
-
return variable.references.some((candidateReference) => {
|
|
47233
|
-
if (candidateReference.init) return false;
|
|
47234
|
-
if (candidateReference.isWrite()) return true;
|
|
47235
|
-
let usageRoot = findTransparentExpressionRoot(candidateReference.identifier);
|
|
47236
|
-
while (isNodeOfType(usageRoot.parent, "MemberExpression") && usageRoot.parent.object === usageRoot) usageRoot = findTransparentExpressionRoot(usageRoot.parent);
|
|
47237
|
-
const usageParent = usageRoot.parent;
|
|
47238
|
-
if (isNodeOfType(usageParent, "AssignmentExpression") && usageParent.left === usageRoot || isNodeOfType(usageParent, "UpdateExpression") && usageParent.argument === usageRoot || isNodeOfType(usageParent, "UnaryExpression") && usageParent.operator === "delete" && usageParent.argument === usageRoot) return true;
|
|
47239
|
-
let usageAncestor = usageRoot.parent;
|
|
47240
|
-
while (usageAncestor && !isNodeOfType(usageAncestor, "VariableDeclarator") && !isFunctionLike$1(usageAncestor) && !isNodeOfType(usageAncestor, "Program")) usageAncestor = usageAncestor.parent;
|
|
47241
|
-
if (isNodeOfType(usageAncestor, "VariableDeclarator") && usageAncestor.init) {
|
|
47242
|
-
const aliasVariables = getVariablesDefinedByDeclarator(analysis, usageAncestor);
|
|
47243
|
-
return aliasVariables.length === 0 || aliasVariables.some((aliasVariable) => hasUnsafeExternalSubscriptionBindingUse(analysis, aliasVariable, visitedVariables));
|
|
47244
|
-
}
|
|
47245
|
-
return false;
|
|
47246
|
-
});
|
|
47247
|
-
};
|
|
47248
|
-
const hasOnlySafeExternalSubscriptionResultBindings = (analysis, declarator, variables = getVariablesDefinedByDeclarator(analysis, declarator)) => {
|
|
47249
|
-
return variables.length > 0 && variables.every((variable) => {
|
|
47250
|
-
const bindingIdentifier = variable.defs.find((candidateDefinition) => candidateDefinition.node === declarator)?.name;
|
|
47251
|
-
return Boolean(bindingIdentifier && isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && !hasUnsafeExternalSubscriptionBindingUse(analysis, variable));
|
|
47252
|
-
});
|
|
47253
|
-
};
|
|
47254
|
-
const hasImmutableExternalSubscriptionCallResult = (analysis, rawCallee, relatedRefs, allowWholeResult) => {
|
|
47255
|
-
const callee = stripParenExpression(rawCallee);
|
|
47256
|
-
const callExpression = callee.parent;
|
|
47257
|
-
if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== callee) return false;
|
|
47258
|
-
const initializer = findTransparentExpressionRoot(callExpression);
|
|
47259
|
-
const declarator = initializer.parent;
|
|
47260
|
-
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== initializer || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
|
|
47261
|
-
const relatedVariables = getVariablesDefinedByDeclarator(analysis, declarator).filter((variable) => relatedRefs.some((relatedRef) => relatedRef.resolved === variable));
|
|
47262
|
-
if (relatedVariables.length === 0) return false;
|
|
47263
|
-
return isNodeOfType(declarator.id, "Identifier") ? allowWholeResult && relatedVariables.every((variable) => !hasUnsafeExternalSubscriptionBindingUse(analysis, variable)) : hasOnlySafeExternalSubscriptionResultBindings(analysis, declarator, relatedVariables);
|
|
47264
|
-
};
|
|
47265
|
-
const isImmutableImportedExternalSubscriptionHookCallee = (analysis, rawCallee, relatedRefs) => {
|
|
47266
|
-
const hookName = getImportedExternalSubscriptionHookName(analysis, rawCallee);
|
|
47267
|
-
return Boolean(hookName && hasImmutableExternalSubscriptionCallResult(analysis, rawCallee, relatedRefs, EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName)));
|
|
47268
|
-
};
|
|
47269
|
-
const isExternalSubscriptionHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
47270
|
-
const declarator = def.node;
|
|
47271
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
|
|
47272
|
-
const initializer = stripParenExpression(declarator.init);
|
|
47273
|
-
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
47274
|
-
const hookName = getImportedExternalSubscriptionHookName(analysis, initializer.callee);
|
|
47275
|
-
if (!hookName) return false;
|
|
47276
|
-
if (isNodeOfType(declarator.id, "Identifier")) return EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
|
|
47277
|
-
const bindingIdentifier = def.name;
|
|
47278
|
-
return isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
|
|
47279
|
-
}));
|
|
47280
|
-
const isExternalSubscriptionHookResultArgument = (analysis, argument) => {
|
|
47281
|
-
const unwrappedArgument = stripParenExpression(argument);
|
|
47282
|
-
if (!isNodeOfType(unwrappedArgument, "Identifier")) return false;
|
|
47283
|
-
const argumentRef = getRef(analysis, unwrappedArgument);
|
|
47284
|
-
return Boolean(argumentRef && isExternalSubscriptionHookResultRef(analysis, argumentRef));
|
|
47285
|
-
};
|
|
47286
47396
|
const isCallbackPropReference = (analysis, ref) => {
|
|
47287
47397
|
if (!isProp(analysis, ref)) return false;
|
|
47288
47398
|
const identifier = ref.identifier;
|
|
@@ -47315,9 +47425,8 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
47315
47425
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
47316
47426
|
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
47317
47427
|
};
|
|
47318
|
-
const getLocalHookExternalStateProof = (analysis, ref
|
|
47428
|
+
const getLocalHookExternalStateProof = (analysis, ref) => {
|
|
47319
47429
|
let hookFunction = resolveToFunction(ref);
|
|
47320
|
-
let didResolveThroughResultBinding = false;
|
|
47321
47430
|
if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
|
|
47322
47431
|
const definitionNode = definition.node;
|
|
47323
47432
|
if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
|
|
@@ -47328,27 +47437,29 @@ const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
|
|
|
47328
47437
|
const calleeReference = getRef(analysis, callee);
|
|
47329
47438
|
if (!calleeReference) continue;
|
|
47330
47439
|
hookFunction = resolveToFunction(calleeReference);
|
|
47331
|
-
if (hookFunction)
|
|
47332
|
-
didResolveThroughResultBinding = true;
|
|
47333
|
-
break;
|
|
47334
|
-
}
|
|
47440
|
+
if (hookFunction) break;
|
|
47335
47441
|
}
|
|
47336
47442
|
if (!hookFunction) return null;
|
|
47337
|
-
|
|
47338
|
-
if (isNodeOfType(hookFunction, "ArrowFunctionExpression") && !isNodeOfType(hookFunction.body, "BlockStatement") && isReactHookCall(stripParenExpression(hookFunction.body), "useSyncExternalStore", scopes)) return true;
|
|
47339
|
-
const returnStatements = collectFunctionReturnStatements(hookFunction);
|
|
47340
|
-
if (returnStatements.length > 0 && returnStatements.every((returnStatement) => returnStatement.argument && isReactHookCall(stripParenExpression(returnStatement.argument), "useSyncExternalStore", scopes))) return true;
|
|
47341
|
-
const returnedReferences = returnStatements.flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
47443
|
+
const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
47342
47444
|
if (returnedReferences.length === 0) return null;
|
|
47343
47445
|
return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
|
|
47344
47446
|
};
|
|
47345
|
-
const
|
|
47346
|
-
const
|
|
47347
|
-
if (!isNodeOfType(
|
|
47348
|
-
const
|
|
47349
|
-
if (
|
|
47350
|
-
|
|
47447
|
+
const isExternalSubscriptionHookRef = (analysis, ref) => {
|
|
47448
|
+
const identifier = ref.identifier;
|
|
47449
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
47450
|
+
const localHookProof = getLocalHookExternalStateProof(analysis, ref);
|
|
47451
|
+
if (localHookProof !== null) return localHookProof;
|
|
47452
|
+
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
47453
|
+
return Boolean(ref.resolved?.defs.some((def) => {
|
|
47454
|
+
const node = def.node;
|
|
47455
|
+
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
47456
|
+
const initializer = stripParenExpression(node.init);
|
|
47457
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
47458
|
+
const callee = stripParenExpression(initializer.callee);
|
|
47459
|
+
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
|
|
47460
|
+
}));
|
|
47351
47461
|
};
|
|
47462
|
+
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
47352
47463
|
const isCalleePosition = (identifier) => {
|
|
47353
47464
|
const parent = identifier.parent;
|
|
47354
47465
|
return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
|
|
@@ -47409,35 +47520,25 @@ const noPassDataToParent = defineRule({
|
|
|
47409
47520
|
return getFunctionalUpdaterDataRefs(analysis, argument);
|
|
47410
47521
|
}
|
|
47411
47522
|
if (isHandlerBagArgument(analysis, argument)) return [];
|
|
47412
|
-
if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
|
|
47413
47523
|
if (isParentWiredHookResultArgument(analysis, argument)) return [];
|
|
47414
47524
|
if (isNodeOfType(argument, "Identifier")) {
|
|
47415
47525
|
const argumentRef = getRef(analysis, argument);
|
|
47416
47526
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
47417
47527
|
}
|
|
47418
47528
|
return getDownstreamRefs(analysis, argument);
|
|
47419
|
-
}).flatMap((argumentRef) =>
|
|
47420
|
-
|
|
47421
|
-
const upstreamRefs = getUpstreamRefs(analysis, argumentRef);
|
|
47422
|
-
return upstreamRefs.filter((upstreamRef) => {
|
|
47423
|
-
if (!isLeafRef(upstreamRef)) return false;
|
|
47424
|
-
return !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, upstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, upstreamRefs, context.scopes);
|
|
47425
|
-
});
|
|
47426
|
-
});
|
|
47427
|
-
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) {
|
|
47428
|
-
const wrapperUpstreamRefs = getArgsUpstreamRefs(analysis, ref);
|
|
47429
|
-
argsUpstreamRefs.push(...wrapperUpstreamRefs.filter((upstreamRef) => isLeafRef(upstreamRef) && !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs, context.scopes)));
|
|
47430
|
-
}
|
|
47529
|
+
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
47530
|
+
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
47431
47531
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
47432
|
-
|
|
47433
|
-
if (
|
|
47532
|
+
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
47533
|
+
if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
|
|
47434
47534
|
if (isProp(analysis, argRef)) return false;
|
|
47435
|
-
if (isUseRefIdentifier(
|
|
47535
|
+
if (isUseRefIdentifier(argRef.identifier)) return false;
|
|
47436
47536
|
if (isRefCurrent(argRef)) return false;
|
|
47437
47537
|
if (isConstant(argRef)) return false;
|
|
47438
47538
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
47439
47539
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
47440
47540
|
if (resolveToFunction(argRef)) return false;
|
|
47541
|
+
const argIdentifier = argRef.identifier;
|
|
47441
47542
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
47442
47543
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
47443
47544
|
return true;
|
|
@@ -59754,7 +59855,7 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
59754
59855
|
const isRepositorySecretFilePath = (relativePath) => DOTENV_FILE_PATTERN.test(relativePath) || /(?:^|\/)\.npmrc$/.test(relativePath) || /(?:^|\/)[^/]*(?:credential|credentials|service-account|serviceAccount|firebase-admin|google-service-account|gcp-service-account)[^/]*\.(?:json|env|pem|key)$/i.test(relativePath);
|
|
59755
59856
|
//#endregion
|
|
59756
59857
|
//#region src/plugin/rules/security-scan/repository-secret-file.ts
|
|
59757
|
-
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env(
|
|
59858
|
+
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
|
|
59758
59859
|
const repositorySecretFile = defineRule({
|
|
59759
59860
|
id: "repository-secret-file",
|
|
59760
59861
|
title: "Secret file checked into repository",
|
|
@@ -73173,6 +73274,18 @@ const reactDoctorRules = [
|
|
|
73173
73274
|
requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
|
|
73174
73275
|
}
|
|
73175
73276
|
},
|
|
73277
|
+
{
|
|
73278
|
+
key: "react-doctor/jsx-no-target-blank",
|
|
73279
|
+
id: "jsx-no-target-blank",
|
|
73280
|
+
source: "react-doctor",
|
|
73281
|
+
originallyExternal: true,
|
|
73282
|
+
rule: {
|
|
73283
|
+
...jsxNoTargetBlank,
|
|
73284
|
+
framework: "global",
|
|
73285
|
+
category: "Security",
|
|
73286
|
+
requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
|
|
73287
|
+
}
|
|
73288
|
+
},
|
|
73176
73289
|
{
|
|
73177
73290
|
key: "react-doctor/jsx-no-undef",
|
|
73178
73291
|
id: "jsx-no-undef",
|