oxlint-plugin-react-doctor 0.8.1-dev.5774353 → 0.8.1-dev.61bf03e
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 +553 -1
- package/dist/index.js +1378 -165
- 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$66 = "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$66
|
|
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$65 = "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$65
|
|
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$65
|
|
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$64 = "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$64
|
|
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$64
|
|
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$63 = "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$63,
|
|
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$63
|
|
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$62 = "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$62
|
|
8169
8169
|
});
|
|
8170
8170
|
} };
|
|
8171
8171
|
}
|
|
@@ -10243,21 +10243,19 @@ const isReactComponentName = (name) => {
|
|
|
10243
10243
|
return firstCharacter >= 65 && firstCharacter <= 90;
|
|
10244
10244
|
};
|
|
10245
10245
|
//#endregion
|
|
10246
|
-
//#region src/plugin/
|
|
10247
|
-
const
|
|
10248
|
-
if (!isNodeOfType(
|
|
10249
|
-
if (
|
|
10250
|
-
if (isNodeOfType(
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
if (isNodeOfType(expression, "TemplateLiteral") && expression.quasis?.length === 1) return expression.quasis[0].value?.raw ?? null;
|
|
10255
|
-
}
|
|
10246
|
+
//#region src/plugin/utils/get-jsx-attribute-static-string.ts
|
|
10247
|
+
const getJsxAttributeStaticString = (attribute) => {
|
|
10248
|
+
if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return null;
|
|
10249
|
+
if (isNodeOfType(attribute.value, "Literal") && typeof attribute.value.value === "string") return attribute.value.value;
|
|
10250
|
+
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
10251
|
+
const expression = attribute.value.expression;
|
|
10252
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
|
|
10253
|
+
if (isNodeOfType(expression, "TemplateLiteral") && expression.expressions.length === 0 && expression.quasis.length === 1) return expression.quasis[0].value.raw;
|
|
10256
10254
|
return null;
|
|
10257
10255
|
};
|
|
10258
10256
|
//#endregion
|
|
10259
10257
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
10260
|
-
const MESSAGE$
|
|
10258
|
+
const MESSAGE$61 = "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
10259
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
10262
10260
|
"td",
|
|
10263
10261
|
"th",
|
|
@@ -10315,7 +10313,7 @@ const collectStaticTemplateClassTokens = (templateLiteral) => {
|
|
|
10315
10313
|
const hasDisplayNoneClass = (opening) => {
|
|
10316
10314
|
const classAttribute = hasJsxPropIgnoreCase(opening.attributes, "className") ?? hasJsxPropIgnoreCase(opening.attributes, "class");
|
|
10317
10315
|
if (!classAttribute) return false;
|
|
10318
|
-
const literalValue =
|
|
10316
|
+
const literalValue = getJsxAttributeStaticString(classAttribute);
|
|
10319
10317
|
if (literalValue !== null) return literalValue.split(/\s+/).some(isDisplayNoneClassToken);
|
|
10320
10318
|
if (classAttribute.value && isNodeOfType(classAttribute.value, "JSXExpressionContainer") && isNodeOfType(classAttribute.value.expression, "TemplateLiteral")) return collectStaticTemplateClassTokens(classAttribute.value.expression).some(isDisplayNoneClassToken);
|
|
10321
10319
|
return false;
|
|
@@ -10787,7 +10785,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
10787
10785
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
10788
10786
|
context.report({
|
|
10789
10787
|
node: candidate.opening,
|
|
10790
|
-
message: MESSAGE$
|
|
10788
|
+
message: MESSAGE$61
|
|
10791
10789
|
});
|
|
10792
10790
|
}
|
|
10793
10791
|
}
|
|
@@ -12431,7 +12429,7 @@ const noRedundantPaddingAxes = defineRule({
|
|
|
12431
12429
|
recommendation: "Collapse matching padding axes to `p-N` so duplicated classes do not make spacing harder to scan; keep split axes only when breakpoints differ.",
|
|
12432
12430
|
create: (context) => ({ JSXAttribute(jsxAttribute) {
|
|
12433
12431
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
12434
|
-
const classNameLiteral =
|
|
12432
|
+
const classNameLiteral = getJsxAttributeStaticString(jsxAttribute);
|
|
12435
12433
|
if (!classNameLiteral) return;
|
|
12436
12434
|
if (!classNameLiteral.includes("px-") || !classNameLiteral.includes("py-")) return;
|
|
12437
12435
|
if (hasResponsivePrefix(classNameLiteral, "px") || hasResponsivePrefix(classNameLiteral, "py")) return;
|
|
@@ -12459,7 +12457,7 @@ const noRedundantSizeAxes = defineRule({
|
|
|
12459
12457
|
return { JSXAttribute(jsxAttribute) {
|
|
12460
12458
|
if (didReportInFile) return;
|
|
12461
12459
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
12462
|
-
const classNameLiteral =
|
|
12460
|
+
const classNameLiteral = getJsxAttributeStaticString(jsxAttribute);
|
|
12463
12461
|
if (!classNameLiteral) return;
|
|
12464
12462
|
if (!classNameLiteral.includes("w-") || !classNameLiteral.includes("h-")) return;
|
|
12465
12463
|
if (hasResponsivePrefix(classNameLiteral, "w") || hasResponsivePrefix(classNameLiteral, "h")) return;
|
|
@@ -12487,7 +12485,7 @@ const noSpaceOnFlexChildren = defineRule({
|
|
|
12487
12485
|
recommendation: "Use `gap-*` on the flex or grid parent. `space-x-*` and `space-y-*` leave gaps when a child is hidden, miss spacing on wrapped lines, and don't flip in right-to-left layouts.",
|
|
12488
12486
|
create: (context) => ({ JSXAttribute(jsxAttribute) {
|
|
12489
12487
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
12490
|
-
const classNameLiteral =
|
|
12488
|
+
const classNameLiteral = getJsxAttributeStaticString(jsxAttribute);
|
|
12491
12489
|
if (!classNameLiteral) return;
|
|
12492
12490
|
if (!classNameLiteral.includes("space-")) return;
|
|
12493
12491
|
const tokens = tokenizeClassName(classNameLiteral);
|
|
@@ -12594,7 +12592,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
12594
12592
|
});
|
|
12595
12593
|
//#endregion
|
|
12596
12594
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
12597
|
-
const MESSAGE$
|
|
12595
|
+
const MESSAGE$60 = "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
12596
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
12599
12597
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
12600
12598
|
"aria-label",
|
|
@@ -12619,7 +12617,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
12619
12617
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
12620
12618
|
context.report({
|
|
12621
12619
|
node: node.name,
|
|
12622
|
-
message: MESSAGE$
|
|
12620
|
+
message: MESSAGE$60
|
|
12623
12621
|
});
|
|
12624
12622
|
} };
|
|
12625
12623
|
}
|
|
@@ -12659,7 +12657,7 @@ const isEs6Component = (node) => {
|
|
|
12659
12657
|
};
|
|
12660
12658
|
//#endregion
|
|
12661
12659
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
12662
|
-
const MESSAGE$
|
|
12660
|
+
const MESSAGE$59 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
12663
12661
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
12664
12662
|
"observer",
|
|
12665
12663
|
"lazy",
|
|
@@ -12852,7 +12850,7 @@ const displayName = defineRule({
|
|
|
12852
12850
|
const reportAt = (node) => {
|
|
12853
12851
|
context.report({
|
|
12854
12852
|
node,
|
|
12855
|
-
message: MESSAGE$
|
|
12853
|
+
message: MESSAGE$59
|
|
12856
12854
|
});
|
|
12857
12855
|
};
|
|
12858
12856
|
return {
|
|
@@ -19266,7 +19264,7 @@ const forbidElements = defineRule({
|
|
|
19266
19264
|
});
|
|
19267
19265
|
//#endregion
|
|
19268
19266
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
19269
|
-
const MESSAGE$
|
|
19267
|
+
const MESSAGE$58 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
19270
19268
|
const forwardRefUsesRef = defineRule({
|
|
19271
19269
|
id: "forward-ref-uses-ref",
|
|
19272
19270
|
title: "forwardRef without ref parameter",
|
|
@@ -19289,7 +19287,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
19289
19287
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
19290
19288
|
context.report({
|
|
19291
19289
|
node: inner,
|
|
19292
|
-
message: MESSAGE$
|
|
19290
|
+
message: MESSAGE$58
|
|
19293
19291
|
});
|
|
19294
19292
|
} })
|
|
19295
19293
|
});
|
|
@@ -19330,7 +19328,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
19330
19328
|
});
|
|
19331
19329
|
//#endregion
|
|
19332
19330
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
19333
|
-
const MESSAGE$
|
|
19331
|
+
const MESSAGE$57 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
19334
19332
|
const DEFAULT_HEADING_TAGS = [
|
|
19335
19333
|
"h1",
|
|
19336
19334
|
"h2",
|
|
@@ -19364,7 +19362,7 @@ const headingHasContent = defineRule({
|
|
|
19364
19362
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
19365
19363
|
context.report({
|
|
19366
19364
|
node,
|
|
19367
|
-
message: MESSAGE$
|
|
19365
|
+
message: MESSAGE$57
|
|
19368
19366
|
});
|
|
19369
19367
|
} };
|
|
19370
19368
|
}
|
|
@@ -19532,7 +19530,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19532
19530
|
});
|
|
19533
19531
|
//#endregion
|
|
19534
19532
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
19535
|
-
const MESSAGE$
|
|
19533
|
+
const MESSAGE$56 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
19536
19534
|
const resolveSettings$38 = (settings) => {
|
|
19537
19535
|
const reactDoctor = settings?.["react-doctor"];
|
|
19538
19536
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -19579,13 +19577,13 @@ const htmlHasLang = defineRule({
|
|
|
19579
19577
|
if (!lang) {
|
|
19580
19578
|
context.report({
|
|
19581
19579
|
node: node.name,
|
|
19582
|
-
message: MESSAGE$
|
|
19580
|
+
message: MESSAGE$56
|
|
19583
19581
|
});
|
|
19584
19582
|
return;
|
|
19585
19583
|
}
|
|
19586
19584
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
19587
19585
|
node: lang,
|
|
19588
|
-
message: MESSAGE$
|
|
19586
|
+
message: MESSAGE$56
|
|
19589
19587
|
});
|
|
19590
19588
|
} };
|
|
19591
19589
|
}
|
|
@@ -19858,7 +19856,7 @@ const isJsxFragmentElement = (node, scopes) => {
|
|
|
19858
19856
|
};
|
|
19859
19857
|
//#endregion
|
|
19860
19858
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
19861
|
-
const MESSAGE$
|
|
19859
|
+
const MESSAGE$55 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
19862
19860
|
const isStaticallyAriaHidden = (openingElement) => {
|
|
19863
19861
|
const ariaHiddenAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "aria-hidden", false);
|
|
19864
19862
|
if (!ariaHiddenAttribute) return false;
|
|
@@ -19947,14 +19945,14 @@ const iframeHasTitle = defineRule({
|
|
|
19947
19945
|
if (!titleAttr) {
|
|
19948
19946
|
if (hasSpread || tag === "iframe") context.report({
|
|
19949
19947
|
node: node.name,
|
|
19950
|
-
message: MESSAGE$
|
|
19948
|
+
message: MESSAGE$55
|
|
19951
19949
|
});
|
|
19952
19950
|
return;
|
|
19953
19951
|
}
|
|
19954
19952
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
19955
19953
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
19956
19954
|
node: titleAttr,
|
|
19957
|
-
message: MESSAGE$
|
|
19955
|
+
message: MESSAGE$55
|
|
19958
19956
|
});
|
|
19959
19957
|
} })
|
|
19960
19958
|
});
|
|
@@ -20079,7 +20077,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
20079
20077
|
});
|
|
20080
20078
|
//#endregion
|
|
20081
20079
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
20082
|
-
const MESSAGE$
|
|
20080
|
+
const MESSAGE$54 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
20083
20081
|
const DEFAULT_COMPONENTS = ["img"];
|
|
20084
20082
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
20085
20083
|
"image",
|
|
@@ -20144,7 +20142,7 @@ const imgRedundantAlt = defineRule({
|
|
|
20144
20142
|
if (!altAttribute) return;
|
|
20145
20143
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
20146
20144
|
node: altAttribute,
|
|
20147
|
-
message: MESSAGE$
|
|
20145
|
+
message: MESSAGE$54
|
|
20148
20146
|
});
|
|
20149
20147
|
} };
|
|
20150
20148
|
}
|
|
@@ -21797,7 +21795,7 @@ const isDiscardedProbeInsideTry = (node) => {
|
|
|
21797
21795
|
return false;
|
|
21798
21796
|
};
|
|
21799
21797
|
const isComponentOrHookName = (name) => /^[A-Z]/.test(name) || /^use[A-Z0-9]/.test(name);
|
|
21800
|
-
const getFunctionName = (functionNode) => {
|
|
21798
|
+
const getFunctionName$1 = (functionNode) => {
|
|
21801
21799
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id.name;
|
|
21802
21800
|
const holder = functionNode.parent;
|
|
21803
21801
|
if (holder && isNodeOfType(holder, "VariableDeclarator") && isNodeOfType(holder.id, "Identifier")) return holder.id.name;
|
|
@@ -21806,7 +21804,7 @@ const getFunctionName = (functionNode) => {
|
|
|
21806
21804
|
const isUncacheableOptionsMergeUtility = (node) => {
|
|
21807
21805
|
const enclosingFunction = findEnclosingFunction$1(node);
|
|
21808
21806
|
if (!enclosingFunction || !isFunctionLike$1(enclosingFunction)) return false;
|
|
21809
|
-
const functionName = getFunctionName(enclosingFunction);
|
|
21807
|
+
const functionName = getFunctionName$1(enclosingFunction);
|
|
21810
21808
|
if (!functionName || isComponentOrHookName(functionName)) return false;
|
|
21811
21809
|
const parameterNames = /* @__PURE__ */ new Set();
|
|
21812
21810
|
for (const parameter of enclosingFunction.params ?? []) collectPatternNames(parameter, parameterNames);
|
|
@@ -22108,7 +22106,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
22108
22106
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
22109
22107
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
22110
22108
|
};
|
|
22111
|
-
const MESSAGE$
|
|
22109
|
+
const MESSAGE$53 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
22112
22110
|
const jsHoistRegexp = defineRule({
|
|
22113
22111
|
id: "js-hoist-regexp",
|
|
22114
22112
|
title: "RegExp built inside a loop",
|
|
@@ -22125,7 +22123,7 @@ const jsHoistRegexp = defineRule({
|
|
|
22125
22123
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
22126
22124
|
context.report({
|
|
22127
22125
|
node,
|
|
22128
|
-
message: MESSAGE$
|
|
22126
|
+
message: MESSAGE$53
|
|
22129
22127
|
});
|
|
22130
22128
|
};
|
|
22131
22129
|
return createLoopAwareVisitors({
|
|
@@ -24892,7 +24890,7 @@ const jsxMaxDepth = defineRule({
|
|
|
24892
24890
|
});
|
|
24893
24891
|
//#endregion
|
|
24894
24892
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
24895
|
-
const MESSAGE$
|
|
24893
|
+
const MESSAGE$52 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
24896
24894
|
const LITERAL_TEXT_TAGS = new Set([
|
|
24897
24895
|
"code",
|
|
24898
24896
|
"pre",
|
|
@@ -24962,7 +24960,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
24962
24960
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
24963
24961
|
context.report({
|
|
24964
24962
|
node,
|
|
24965
|
-
message: MESSAGE$
|
|
24963
|
+
message: MESSAGE$52
|
|
24966
24964
|
});
|
|
24967
24965
|
} })
|
|
24968
24966
|
});
|
|
@@ -24993,7 +24991,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
24993
24991
|
};
|
|
24994
24992
|
//#endregion
|
|
24995
24993
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
24996
|
-
const MESSAGE$
|
|
24994
|
+
const MESSAGE$51 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
24997
24995
|
const CONTEXT_MODULES$1 = [
|
|
24998
24996
|
"react",
|
|
24999
24997
|
"use-context-selector",
|
|
@@ -25096,7 +25094,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
25096
25094
|
if (!isConstructedValue(innerExpression)) continue;
|
|
25097
25095
|
context.report({
|
|
25098
25096
|
node: attribute,
|
|
25099
|
-
message: MESSAGE$
|
|
25097
|
+
message: MESSAGE$51
|
|
25100
25098
|
});
|
|
25101
25099
|
}
|
|
25102
25100
|
}
|
|
@@ -25962,7 +25960,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
25962
25960
|
];
|
|
25963
25961
|
//#endregion
|
|
25964
25962
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
25965
|
-
const MESSAGE$
|
|
25963
|
+
const MESSAGE$50 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
25966
25964
|
const isDataArrayPropName = (propName) => {
|
|
25967
25965
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
25968
25966
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -26049,7 +26047,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
26049
26047
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
26050
26048
|
context.report({
|
|
26051
26049
|
node,
|
|
26052
|
-
message: MESSAGE$
|
|
26050
|
+
message: MESSAGE$50
|
|
26053
26051
|
});
|
|
26054
26052
|
}
|
|
26055
26053
|
};
|
|
@@ -26307,7 +26305,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
26307
26305
|
]);
|
|
26308
26306
|
//#endregion
|
|
26309
26307
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
26310
|
-
const MESSAGE$
|
|
26308
|
+
const MESSAGE$49 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
26311
26309
|
const isAccessorPredicateName = (propName) => {
|
|
26312
26310
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
26313
26311
|
if (propName.length <= prefix.length) continue;
|
|
@@ -26514,7 +26512,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
26514
26512
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
26515
26513
|
context.report({
|
|
26516
26514
|
node,
|
|
26517
|
-
message: MESSAGE$
|
|
26515
|
+
message: MESSAGE$49
|
|
26518
26516
|
});
|
|
26519
26517
|
}
|
|
26520
26518
|
};
|
|
@@ -26734,7 +26732,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
26734
26732
|
];
|
|
26735
26733
|
//#endregion
|
|
26736
26734
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
26737
|
-
const MESSAGE$
|
|
26735
|
+
const MESSAGE$48 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
26738
26736
|
const isConfigObjectPropName = (propName) => {
|
|
26739
26737
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
26740
26738
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -26823,7 +26821,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26823
26821
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
26824
26822
|
context.report({
|
|
26825
26823
|
node,
|
|
26826
|
-
message: MESSAGE$
|
|
26824
|
+
message: MESSAGE$48
|
|
26827
26825
|
});
|
|
26828
26826
|
}
|
|
26829
26827
|
};
|
|
@@ -26831,7 +26829,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26831
26829
|
});
|
|
26832
26830
|
//#endregion
|
|
26833
26831
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
26834
|
-
const MESSAGE$
|
|
26832
|
+
const MESSAGE$47 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
26835
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;
|
|
26836
26834
|
const resolveSettings$28 = (settings) => {
|
|
26837
26835
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -26869,7 +26867,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26869
26867
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
26870
26868
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
26871
26869
|
node: attribute,
|
|
26872
|
-
message: MESSAGE$
|
|
26870
|
+
message: MESSAGE$47
|
|
26873
26871
|
});
|
|
26874
26872
|
}
|
|
26875
26873
|
} };
|
|
@@ -27212,7 +27210,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
27212
27210
|
});
|
|
27213
27211
|
//#endregion
|
|
27214
27212
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
27215
|
-
const MESSAGE$
|
|
27213
|
+
const MESSAGE$46 = "You can't tell what props reach this element when you spread them.";
|
|
27216
27214
|
const resolveSettings$25 = (settings) => {
|
|
27217
27215
|
const reactDoctor = settings?.["react-doctor"];
|
|
27218
27216
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -27255,7 +27253,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
27255
27253
|
}
|
|
27256
27254
|
context.report({
|
|
27257
27255
|
node: attribute,
|
|
27258
|
-
message: MESSAGE$
|
|
27256
|
+
message: MESSAGE$46
|
|
27259
27257
|
});
|
|
27260
27258
|
didReportInFile = true;
|
|
27261
27259
|
return;
|
|
@@ -27531,7 +27529,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
27531
27529
|
});
|
|
27532
27530
|
//#endregion
|
|
27533
27531
|
//#region src/plugin/rules/a11y/lang.ts
|
|
27534
|
-
const MESSAGE$
|
|
27532
|
+
const MESSAGE$45 = "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`.";
|
|
27535
27533
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
27536
27534
|
"aa",
|
|
27537
27535
|
"ab",
|
|
@@ -28011,7 +28009,7 @@ const lang = defineRule({
|
|
|
28011
28009
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
28012
28010
|
context.report({
|
|
28013
28011
|
node: langAttr,
|
|
28014
|
-
message: MESSAGE$
|
|
28012
|
+
message: MESSAGE$45
|
|
28015
28013
|
});
|
|
28016
28014
|
return;
|
|
28017
28015
|
}
|
|
@@ -28020,7 +28018,7 @@ const lang = defineRule({
|
|
|
28020
28018
|
if (value === null) return;
|
|
28021
28019
|
if (!isValidLangTag(value)) context.report({
|
|
28022
28020
|
node: langAttr,
|
|
28023
|
-
message: MESSAGE$
|
|
28021
|
+
message: MESSAGE$45
|
|
28024
28022
|
});
|
|
28025
28023
|
} })
|
|
28026
28024
|
});
|
|
@@ -28071,7 +28069,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
28071
28069
|
});
|
|
28072
28070
|
//#endregion
|
|
28073
28071
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
28074
|
-
const MESSAGE$
|
|
28072
|
+
const MESSAGE$44 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
28075
28073
|
const DEFAULT_AUDIO = ["audio"];
|
|
28076
28074
|
const DEFAULT_VIDEO = ["video"];
|
|
28077
28075
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -28161,7 +28159,7 @@ const mediaHasCaption = defineRule({
|
|
|
28161
28159
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
28162
28160
|
context.report({
|
|
28163
28161
|
node: node.name,
|
|
28164
|
-
message: MESSAGE$
|
|
28162
|
+
message: MESSAGE$44
|
|
28165
28163
|
});
|
|
28166
28164
|
return;
|
|
28167
28165
|
}
|
|
@@ -28180,12 +28178,553 @@ const mediaHasCaption = defineRule({
|
|
|
28180
28178
|
return kindValue.value.toLowerCase() === "captions";
|
|
28181
28179
|
})) context.report({
|
|
28182
28180
|
node: node.name,
|
|
28183
|
-
message: MESSAGE$
|
|
28181
|
+
message: MESSAGE$44
|
|
28184
28182
|
});
|
|
28185
28183
|
} };
|
|
28186
28184
|
}
|
|
28187
28185
|
});
|
|
28188
28186
|
//#endregion
|
|
28187
|
+
//#region src/plugin/utils/get-class-binding-symbol.ts
|
|
28188
|
+
const getClassBindingSymbol = (classNode, scopes) => {
|
|
28189
|
+
if (isNodeOfType(classNode.id, "Identifier")) return scopes.symbolFor(classNode.id);
|
|
28190
|
+
const parent = classNode.parent;
|
|
28191
|
+
return isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier") ? scopes.symbolFor(parent.id) : null;
|
|
28192
|
+
};
|
|
28193
|
+
//#endregion
|
|
28194
|
+
//#region src/plugin/utils/mobx-rule-gates.ts
|
|
28195
|
+
const MOBX_RULE_GATES = {
|
|
28196
|
+
"mobx-reaction-disposer-discarded": { requires: ["mobx:4"] },
|
|
28197
|
+
"mobx-no-make-auto-observable-in-inheritance": { requires: ["mobx:6"] },
|
|
28198
|
+
"mobx-no-computed-side-effects": { requires: ["mobx:4"] },
|
|
28199
|
+
"mobx-async-action-requires-action": { requires: ["mobx:4"] },
|
|
28200
|
+
"mobx-no-observer-wrapped-memo": { requires: [
|
|
28201
|
+
"mobx:4",
|
|
28202
|
+
"mobx-react-binding",
|
|
28203
|
+
"react"
|
|
28204
|
+
] },
|
|
28205
|
+
"mobx-make-observable-unconditional": { requires: ["mobx:6"] },
|
|
28206
|
+
"mobx-legacy-decorator-needs-make-observable": { requires: ["mobx:6"] },
|
|
28207
|
+
"mobx-initialize-before-make-auto-observable": { requires: ["mobx:6"] },
|
|
28208
|
+
"mobx-observable-read-needs-observer": {
|
|
28209
|
+
requires: [
|
|
28210
|
+
"mobx:4",
|
|
28211
|
+
"mobx-react-binding",
|
|
28212
|
+
"react"
|
|
28213
|
+
],
|
|
28214
|
+
disabledWhen: ["mobx-react-observer"]
|
|
28215
|
+
},
|
|
28216
|
+
"mobx-observer-before-inject": { requires: [
|
|
28217
|
+
"mobx:4",
|
|
28218
|
+
"mobx-react",
|
|
28219
|
+
"react"
|
|
28220
|
+
] },
|
|
28221
|
+
"mobx-reaction-requires-observable": { requires: ["mobx:4"] },
|
|
28222
|
+
"mobx-no-invalid-observable-override": { requires: ["mobx:6"] },
|
|
28223
|
+
"mobx-no-observable-prop-to-untracked-child": { requires: [
|
|
28224
|
+
"mobx:4",
|
|
28225
|
+
"mobx-react-binding",
|
|
28226
|
+
"react"
|
|
28227
|
+
] },
|
|
28228
|
+
"mobx-no-stale-observable-snapshot-after-await": { requires: ["mobx:4"] },
|
|
28229
|
+
"mobx-no-reaction-comparison-value-mutation": { requires: ["mobx:4"] },
|
|
28230
|
+
"mobx-observer-class-no-should-component-update": { requires: [
|
|
28231
|
+
"mobx:4",
|
|
28232
|
+
"mobx-react",
|
|
28233
|
+
"react"
|
|
28234
|
+
] },
|
|
28235
|
+
"mobx-enable-static-rendering-for-ssr": { requires: [
|
|
28236
|
+
"mobx:4",
|
|
28237
|
+
"mobx-react-binding",
|
|
28238
|
+
"react",
|
|
28239
|
+
"ssr"
|
|
28240
|
+
] },
|
|
28241
|
+
"mobx-no-rest-destructure-observable": { requires: [
|
|
28242
|
+
"mobx:4",
|
|
28243
|
+
"mobx-react-binding",
|
|
28244
|
+
"react"
|
|
28245
|
+
] },
|
|
28246
|
+
"mobx-computed-depends-on-non-observable": { requires: ["mobx:4"] },
|
|
28247
|
+
"mobx-no-keepalive-computed-without-disposal": { requires: ["mobx:4"] }
|
|
28248
|
+
};
|
|
28249
|
+
//#endregion
|
|
28250
|
+
//#region src/plugin/utils/resolve-imported-api-reference.ts
|
|
28251
|
+
const resolveImportSymbol = (symbol) => {
|
|
28252
|
+
const importDeclaration = getImportDeclarationForSymbol(symbol);
|
|
28253
|
+
if (!importDeclaration || typeof importDeclaration.source.value !== "string") return null;
|
|
28254
|
+
if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return {
|
|
28255
|
+
source: importDeclaration.source.value,
|
|
28256
|
+
importedName: null,
|
|
28257
|
+
isNamespace: true
|
|
28258
|
+
};
|
|
28259
|
+
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return {
|
|
28260
|
+
source: importDeclaration.source.value,
|
|
28261
|
+
importedName: "default",
|
|
28262
|
+
isNamespace: false
|
|
28263
|
+
};
|
|
28264
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
28265
|
+
return importedName ? {
|
|
28266
|
+
source: importDeclaration.source.value,
|
|
28267
|
+
importedName,
|
|
28268
|
+
isNamespace: false
|
|
28269
|
+
} : null;
|
|
28270
|
+
};
|
|
28271
|
+
const resolveIdentifierReference = (identifier, scopes, visitedSymbolIds) => {
|
|
28272
|
+
const symbol = scopes.symbolFor(identifier);
|
|
28273
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
28274
|
+
if (symbol.kind === "import") return resolveImportSymbol(symbol);
|
|
28275
|
+
if (symbol.kind !== "const" || !symbol.initializer) return null;
|
|
28276
|
+
visitedSymbolIds.add(symbol.id);
|
|
28277
|
+
if (isNodeOfType(symbol.declarationNode, "VariableDeclarator") && isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) {
|
|
28278
|
+
const receiver = resolveImportedApiReference(symbol.initializer, scopes, visitedSymbolIds);
|
|
28279
|
+
if (!receiver || !receiver.isNamespace && receiver.importedName !== "default") return null;
|
|
28280
|
+
for (const property of symbol.declarationNode.id.properties) {
|
|
28281
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
28282
|
+
if ((isNodeOfType(property.value, "AssignmentPattern") ? property.value.left : property.value) !== symbol.bindingIdentifier) continue;
|
|
28283
|
+
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
28284
|
+
return propertyName ? {
|
|
28285
|
+
source: receiver.source,
|
|
28286
|
+
importedName: propertyName,
|
|
28287
|
+
isNamespace: false
|
|
28288
|
+
} : null;
|
|
28289
|
+
}
|
|
28290
|
+
return null;
|
|
28291
|
+
}
|
|
28292
|
+
return resolveImportedApiReference(symbol.initializer, scopes, visitedSymbolIds);
|
|
28293
|
+
};
|
|
28294
|
+
const resolveImportedApiReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
28295
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
28296
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) return resolveIdentifierReference(unwrappedExpression, scopes, visitedSymbolIds);
|
|
28297
|
+
if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
|
|
28298
|
+
const propertyName = getStaticPropertyName(unwrappedExpression);
|
|
28299
|
+
if (!propertyName) return null;
|
|
28300
|
+
const receiver = resolveImportedApiReference(unwrappedExpression.object, scopes, visitedSymbolIds);
|
|
28301
|
+
if (!receiver || !receiver.isNamespace && receiver.importedName !== "default") return null;
|
|
28302
|
+
return {
|
|
28303
|
+
source: receiver.source,
|
|
28304
|
+
importedName: propertyName,
|
|
28305
|
+
isNamespace: false
|
|
28306
|
+
};
|
|
28307
|
+
};
|
|
28308
|
+
//#endregion
|
|
28309
|
+
//#region src/plugin/rules/mobx/mobx-no-make-auto-observable-in-inheritance.ts
|
|
28310
|
+
const MESSAGE$43 = "MobX does not support `makeAutoObservable(this)` in inherited classes. Use composition or explicit `makeObservable` annotations.";
|
|
28311
|
+
const getEnclosingConstructorClass = (node) => {
|
|
28312
|
+
let ancestor = node.parent;
|
|
28313
|
+
while (ancestor) {
|
|
28314
|
+
if (!isFunctionLike$1(ancestor)) {
|
|
28315
|
+
ancestor = ancestor.parent ?? null;
|
|
28316
|
+
continue;
|
|
28317
|
+
}
|
|
28318
|
+
const methodDefinition = ancestor.parent;
|
|
28319
|
+
if (!isNodeOfType(methodDefinition, "MethodDefinition") || methodDefinition.kind !== "constructor") return null;
|
|
28320
|
+
const classNode = methodDefinition.parent?.parent;
|
|
28321
|
+
return isNodeOfType(classNode, "ClassDeclaration") || isNodeOfType(classNode, "ClassExpression") ? classNode : null;
|
|
28322
|
+
}
|
|
28323
|
+
return null;
|
|
28324
|
+
};
|
|
28325
|
+
const isNonNullSuperclass = (classNode) => {
|
|
28326
|
+
if (!classNode.superClass) return false;
|
|
28327
|
+
const superClass = stripParenExpression(classNode.superClass);
|
|
28328
|
+
return !(isNodeOfType(superClass, "Literal") && superClass.value === null);
|
|
28329
|
+
};
|
|
28330
|
+
const subclassedClassSymbolIdsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
28331
|
+
const getSubclassedClassSymbolIds = (node, scopes) => {
|
|
28332
|
+
const cached = subclassedClassSymbolIdsByAnalysis.get(scopes);
|
|
28333
|
+
if (cached) return cached;
|
|
28334
|
+
const symbolIds = /* @__PURE__ */ new Set();
|
|
28335
|
+
const program = findProgramRoot(node);
|
|
28336
|
+
if (program) walkAst(program, (candidate) => {
|
|
28337
|
+
if (!isNodeOfType(candidate, "ClassDeclaration") && !isNodeOfType(candidate, "ClassExpression") || !isNonNullSuperclass(candidate)) return;
|
|
28338
|
+
const superClassExpression = candidate.superClass;
|
|
28339
|
+
if (!superClassExpression) return;
|
|
28340
|
+
const superClass = stripParenExpression(superClassExpression);
|
|
28341
|
+
if (!isNodeOfType(superClass, "Identifier")) return;
|
|
28342
|
+
const symbol = resolveConstIdentifierAlias(superClass, scopes);
|
|
28343
|
+
if (symbol?.kind === "class" || symbol?.kind === "const" && isNodeOfType(symbol.initializer, "ClassExpression")) symbolIds.add(symbol.id);
|
|
28344
|
+
});
|
|
28345
|
+
subclassedClassSymbolIdsByAnalysis.set(scopes, symbolIds);
|
|
28346
|
+
return symbolIds;
|
|
28347
|
+
};
|
|
28348
|
+
const isMakeAutoObservableCall = (callExpression, scopes) => {
|
|
28349
|
+
const reference = resolveImportedApiReference(callExpression.callee, scopes);
|
|
28350
|
+
return reference?.source === "mobx" && reference.importedName === "makeAutoObservable";
|
|
28351
|
+
};
|
|
28352
|
+
const mobxNoMakeAutoObservableInInheritance = defineRule({
|
|
28353
|
+
id: "mobx-no-make-auto-observable-in-inheritance",
|
|
28354
|
+
title: "Unsupported MobX auto-observable inheritance",
|
|
28355
|
+
severity: "error",
|
|
28356
|
+
category: "Bugs",
|
|
28357
|
+
requires: MOBX_RULE_GATES["mobx-no-make-auto-observable-in-inheritance"].requires,
|
|
28358
|
+
recommendation: "Replace inheritance with composition, or annotate inherited members explicitly with `makeObservable`.",
|
|
28359
|
+
create: (context) => ({ CallExpression(callExpression) {
|
|
28360
|
+
if (!isMakeAutoObservableCall(callExpression, context.scopes)) return;
|
|
28361
|
+
const target = callExpression.arguments[0];
|
|
28362
|
+
if (!target || !isNodeOfType(stripParenExpression(target), "ThisExpression")) return;
|
|
28363
|
+
const classNode = getEnclosingConstructorClass(callExpression);
|
|
28364
|
+
if (!classNode) return;
|
|
28365
|
+
const classSymbol = getClassBindingSymbol(classNode, context.scopes);
|
|
28366
|
+
const isSubclassed = Boolean(classSymbol && getSubclassedClassSymbolIds(callExpression, context.scopes).has(classSymbol.id));
|
|
28367
|
+
if (!isNonNullSuperclass(classNode) && !isSubclassed) return;
|
|
28368
|
+
context.report({
|
|
28369
|
+
node: callExpression,
|
|
28370
|
+
message: MESSAGE$43
|
|
28371
|
+
});
|
|
28372
|
+
} })
|
|
28373
|
+
});
|
|
28374
|
+
//#endregion
|
|
28375
|
+
//#region src/plugin/rules/mobx/mobx-no-observer-wrapped-memo.ts
|
|
28376
|
+
const OBSERVER_MODULES = new Set(["mobx-react", "mobx-react-lite"]);
|
|
28377
|
+
const MESSAGE$42 = "`observer` cannot wrap an already memoized or observed component. Apply `observer` first, then place `memo` outside only if needed.";
|
|
28378
|
+
const isObserverCall = (callExpression, scopes) => {
|
|
28379
|
+
const reference = resolveImportedApiReference(callExpression.callee, scopes);
|
|
28380
|
+
return Boolean(reference?.importedName === "observer" && OBSERVER_MODULES.has(reference.source));
|
|
28381
|
+
};
|
|
28382
|
+
const hasInvalidInnerWrapper = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
28383
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
28384
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
28385
|
+
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
28386
|
+
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
28387
|
+
visitedSymbolIds.add(symbol.id);
|
|
28388
|
+
return hasInvalidInnerWrapper(symbol.initializer, scopes, visitedSymbolIds);
|
|
28389
|
+
}
|
|
28390
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
|
|
28391
|
+
const reference = resolveImportedApiReference(unwrappedExpression.callee, scopes);
|
|
28392
|
+
if (reference?.importedName === "observer" && OBSERVER_MODULES.has(reference.source)) return true;
|
|
28393
|
+
return reference?.source === "react" && reference.importedName === "memo";
|
|
28394
|
+
};
|
|
28395
|
+
const mobxNoObserverWrappedMemo = defineRule({
|
|
28396
|
+
id: "mobx-no-observer-wrapped-memo",
|
|
28397
|
+
title: "Invalid MobX observer wrapper order",
|
|
28398
|
+
severity: "error",
|
|
28399
|
+
category: "Bugs",
|
|
28400
|
+
requires: MOBX_RULE_GATES["mobx-no-observer-wrapped-memo"].requires,
|
|
28401
|
+
recommendation: "Pass the component directly to `observer`, or apply React `memo` outside the resulting observer component.",
|
|
28402
|
+
create: (context) => ({ CallExpression(callExpression) {
|
|
28403
|
+
if (!isObserverCall(callExpression, context.scopes)) return;
|
|
28404
|
+
const componentArgument = callExpression.arguments[0];
|
|
28405
|
+
if (!componentArgument) return;
|
|
28406
|
+
if (!hasInvalidInnerWrapper(componentArgument, context.scopes)) return;
|
|
28407
|
+
context.report({
|
|
28408
|
+
node: callExpression,
|
|
28409
|
+
message: MESSAGE$42
|
|
28410
|
+
});
|
|
28411
|
+
} })
|
|
28412
|
+
});
|
|
28413
|
+
//#endregion
|
|
28414
|
+
//#region src/plugin/utils/is-result-discarded-call.ts
|
|
28415
|
+
const isResultDiscardedCall = (callExpression) => {
|
|
28416
|
+
let node = callExpression;
|
|
28417
|
+
let parent = node.parent;
|
|
28418
|
+
while (parent) {
|
|
28419
|
+
if (isNodeOfType(parent, "ExpressionStatement")) return true;
|
|
28420
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
|
|
28421
|
+
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
|
|
28422
|
+
if (isNodeOfType(parent, "ChainExpression")) {
|
|
28423
|
+
node = parent;
|
|
28424
|
+
parent = node.parent;
|
|
28425
|
+
continue;
|
|
28426
|
+
}
|
|
28427
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
|
|
28428
|
+
node = parent;
|
|
28429
|
+
parent = node.parent;
|
|
28430
|
+
continue;
|
|
28431
|
+
}
|
|
28432
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
|
|
28433
|
+
node = parent;
|
|
28434
|
+
parent = node.parent;
|
|
28435
|
+
continue;
|
|
28436
|
+
}
|
|
28437
|
+
if (isNodeOfType(parent, "SequenceExpression")) {
|
|
28438
|
+
const expressions = parent.expressions ?? [];
|
|
28439
|
+
if (expressions[expressions.length - 1] !== node) return true;
|
|
28440
|
+
node = parent;
|
|
28441
|
+
parent = node.parent;
|
|
28442
|
+
continue;
|
|
28443
|
+
}
|
|
28444
|
+
return false;
|
|
28445
|
+
}
|
|
28446
|
+
return false;
|
|
28447
|
+
};
|
|
28448
|
+
//#endregion
|
|
28449
|
+
//#region src/plugin/utils/resolve-stable-options-object.ts
|
|
28450
|
+
const resolveStableOptionsObject = (expression, observedPropertyNames, scopes, referenceNode = expression, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
28451
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
28452
|
+
if (isNodeOfType(unwrappedExpression, "ObjectExpression")) return unwrappedExpression;
|
|
28453
|
+
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
28454
|
+
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
28455
|
+
if (!symbol?.initializer || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, referenceNode, scopes) || observedPropertyNames.some((propertyName) => hasPossibleStaticPropertyWriteBefore(unwrappedExpression, propertyName, referenceNode, scopes))) return null;
|
|
28456
|
+
visitedSymbolIds.add(symbol.id);
|
|
28457
|
+
return resolveStableOptionsObject(symbol.initializer, observedPropertyNames, scopes, referenceNode, visitedSymbolIds);
|
|
28458
|
+
};
|
|
28459
|
+
//#endregion
|
|
28460
|
+
//#region src/plugin/rules/mobx/mobx-reaction-disposer-discarded.ts
|
|
28461
|
+
const MESSAGE$41 = "This MobX reaction discards its disposer and can outlive its owner. Store and dispose it during teardown, or provide an AbortSignal.";
|
|
28462
|
+
const LEAKING_SUBSCRIPTION_NAMES = new Set(["reaction", "autorun"]);
|
|
28463
|
+
const OPTIONS_ARGUMENT_INDEX = {
|
|
28464
|
+
autorun: 1,
|
|
28465
|
+
reaction: 2
|
|
28466
|
+
};
|
|
28467
|
+
const DISPOSER_COERCION_NAMES = new Set([
|
|
28468
|
+
"Boolean",
|
|
28469
|
+
"Number",
|
|
28470
|
+
"String"
|
|
28471
|
+
]);
|
|
28472
|
+
const NON_OBSERVABLE_GLOBAL_RECEIVER_NAMES = new Set([
|
|
28473
|
+
"Array",
|
|
28474
|
+
"Boolean",
|
|
28475
|
+
"JSON",
|
|
28476
|
+
"Math",
|
|
28477
|
+
"Number",
|
|
28478
|
+
"Object",
|
|
28479
|
+
"String",
|
|
28480
|
+
"console"
|
|
28481
|
+
]);
|
|
28482
|
+
const NON_OBSERVING_IMPORTED_METHOD_NAMES = new Set([
|
|
28483
|
+
"add",
|
|
28484
|
+
"clear",
|
|
28485
|
+
"delete",
|
|
28486
|
+
"remove",
|
|
28487
|
+
"save",
|
|
28488
|
+
"set",
|
|
28489
|
+
"update",
|
|
28490
|
+
"write"
|
|
28491
|
+
]);
|
|
28492
|
+
const REACTION_PARAMETER_INDEX = {
|
|
28493
|
+
autorun: 0,
|
|
28494
|
+
reaction: 2
|
|
28495
|
+
};
|
|
28496
|
+
const REACTION_CALLBACK_INDEX = {
|
|
28497
|
+
autorun: 0,
|
|
28498
|
+
reaction: 1
|
|
28499
|
+
};
|
|
28500
|
+
const OBSERVATION_CALLBACK_INDEX = {
|
|
28501
|
+
autorun: 0,
|
|
28502
|
+
reaction: 0
|
|
28503
|
+
};
|
|
28504
|
+
const PROCESS_LIFETIME_WIRING_NAME_PATTERN = /^(?:register.*(?:reactions?|autoruns?)|init.*(?:stores?|reactions?|autoruns?)|setup.*(?:stores?|reactions?|autoruns?)|bootstrap(?:app(?:lication)?|stores?|reactions?|autoruns?))$/i;
|
|
28505
|
+
const resolveLeakingSubscriptionName = (callExpression, scopes) => {
|
|
28506
|
+
const reference = resolveImportedApiReference(callExpression.callee, scopes);
|
|
28507
|
+
if (reference?.source !== "mobx" || !reference.importedName || !LEAKING_SUBSCRIPTION_NAMES.has(reference.importedName)) return null;
|
|
28508
|
+
return reference.importedName;
|
|
28509
|
+
};
|
|
28510
|
+
const isEvaluatedAtModuleScope = (node) => {
|
|
28511
|
+
let ancestor = node.parent;
|
|
28512
|
+
while (ancestor) {
|
|
28513
|
+
if (isNodeOfType(ancestor, "PropertyDefinition") || isNodeOfType(ancestor, "AccessorProperty")) {
|
|
28514
|
+
if (!ancestor.static) return false;
|
|
28515
|
+
ancestor = ancestor.parent ?? null;
|
|
28516
|
+
continue;
|
|
28517
|
+
}
|
|
28518
|
+
if (isNodeOfType(ancestor, "StaticBlock")) {
|
|
28519
|
+
ancestor = ancestor.parent ?? null;
|
|
28520
|
+
continue;
|
|
28521
|
+
}
|
|
28522
|
+
if (isFunctionLike$1(ancestor)) {
|
|
28523
|
+
const functionRoot = findTransparentExpressionRoot(ancestor);
|
|
28524
|
+
const invocation = functionRoot.parent;
|
|
28525
|
+
if (isNodeOfType(invocation, "CallExpression") && stripParenExpression(invocation.callee) === functionRoot) {
|
|
28526
|
+
ancestor = invocation.parent ?? null;
|
|
28527
|
+
continue;
|
|
28528
|
+
}
|
|
28529
|
+
return false;
|
|
28530
|
+
}
|
|
28531
|
+
ancestor = ancestor.parent ?? null;
|
|
28532
|
+
}
|
|
28533
|
+
return true;
|
|
28534
|
+
};
|
|
28535
|
+
const getFunctionName = (functionNode) => {
|
|
28536
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id.name;
|
|
28537
|
+
const parent = functionNode.parent;
|
|
28538
|
+
return isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier") ? parent.id.name : null;
|
|
28539
|
+
};
|
|
28540
|
+
const isModuleScopedFunction = (functionNode) => {
|
|
28541
|
+
let ancestor = functionNode.parent;
|
|
28542
|
+
while (ancestor) {
|
|
28543
|
+
if (isFunctionLike$1(ancestor) || isNodeOfType(ancestor, "ClassBody")) return false;
|
|
28544
|
+
if (isNodeOfType(ancestor, "Program")) return true;
|
|
28545
|
+
ancestor = ancestor.parent ?? null;
|
|
28546
|
+
}
|
|
28547
|
+
return false;
|
|
28548
|
+
};
|
|
28549
|
+
const getDirectCalls = (functionNode, scopes) => {
|
|
28550
|
+
const bindingIdentifier = isNodeOfType(functionNode, "FunctionDeclaration") ? functionNode.id : isNodeOfType(functionNode.parent, "VariableDeclarator") ? functionNode.parent.id : null;
|
|
28551
|
+
if (!bindingIdentifier || !isNodeOfType(bindingIdentifier, "Identifier")) return [];
|
|
28552
|
+
const symbol = scopes.scopeFor(functionNode).symbolsByName.get(bindingIdentifier.name);
|
|
28553
|
+
if (!symbol) return [];
|
|
28554
|
+
const calls = [];
|
|
28555
|
+
const program = findProgramRoot(functionNode);
|
|
28556
|
+
if (!program) return calls;
|
|
28557
|
+
walkAst(program, (candidate) => {
|
|
28558
|
+
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
28559
|
+
const callee = stripParenExpression(candidate.callee);
|
|
28560
|
+
if (!isNodeOfType(callee, "Identifier")) return;
|
|
28561
|
+
if (resolveConstIdentifierAlias(callee, scopes)?.id === symbol.id) calls.push(candidate);
|
|
28562
|
+
});
|
|
28563
|
+
return calls;
|
|
28564
|
+
};
|
|
28565
|
+
const processLifetimeClassSymbolIdsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
28566
|
+
const getProcessLifetimeClassSymbolIds = (node, scopes) => {
|
|
28567
|
+
const cached = processLifetimeClassSymbolIdsByAnalysis.get(scopes);
|
|
28568
|
+
if (cached) return cached;
|
|
28569
|
+
const instantiationScopeBySymbolId = /* @__PURE__ */ new Map();
|
|
28570
|
+
const program = findProgramRoot(node);
|
|
28571
|
+
if (program) walkAst(program, (candidate) => {
|
|
28572
|
+
if (!isNodeOfType(candidate, "NewExpression")) return;
|
|
28573
|
+
const callee = stripParenExpression(candidate.callee);
|
|
28574
|
+
const symbol = isNodeOfType(callee, "Identifier") ? resolveConstIdentifierAlias(callee, scopes) : null;
|
|
28575
|
+
if (!symbol) return;
|
|
28576
|
+
const wasModuleOnly = instantiationScopeBySymbolId.get(symbol.id) ?? true;
|
|
28577
|
+
instantiationScopeBySymbolId.set(symbol.id, wasModuleOnly && isEvaluatedAtModuleScope(candidate));
|
|
28578
|
+
});
|
|
28579
|
+
const processLifetimeSymbolIds = /* @__PURE__ */ new Set();
|
|
28580
|
+
for (const [symbolId, isModuleOnly] of instantiationScopeBySymbolId) if (isModuleOnly) processLifetimeSymbolIds.add(symbolId);
|
|
28581
|
+
processLifetimeClassSymbolIdsByAnalysis.set(scopes, processLifetimeSymbolIds);
|
|
28582
|
+
return processLifetimeSymbolIds;
|
|
28583
|
+
};
|
|
28584
|
+
const isProcessLifetimeWiring = (node, scopes) => {
|
|
28585
|
+
let ancestor = node.parent;
|
|
28586
|
+
while (ancestor) {
|
|
28587
|
+
if (!isFunctionLike$1(ancestor)) {
|
|
28588
|
+
ancestor = ancestor.parent ?? null;
|
|
28589
|
+
continue;
|
|
28590
|
+
}
|
|
28591
|
+
const functionName = getFunctionName(ancestor);
|
|
28592
|
+
if (functionName && PROCESS_LIFETIME_WIRING_NAME_PATTERN.test(functionName) && isModuleScopedFunction(ancestor)) {
|
|
28593
|
+
const calls = getDirectCalls(ancestor, scopes);
|
|
28594
|
+
return calls.length > 0 && calls.every(isEvaluatedAtModuleScope);
|
|
28595
|
+
}
|
|
28596
|
+
const methodDefinition = ancestor.parent;
|
|
28597
|
+
if (isNodeOfType(methodDefinition, "MethodDefinition") && methodDefinition.kind === "constructor") {
|
|
28598
|
+
let classNode = methodDefinition.parent?.parent;
|
|
28599
|
+
if (isNodeOfType(classNode, "ClassDeclaration") || isNodeOfType(classNode, "ClassExpression")) {
|
|
28600
|
+
const classRoot = findTransparentExpressionRoot(classNode);
|
|
28601
|
+
const classInstantiation = classRoot.parent;
|
|
28602
|
+
if (isNodeOfType(classNode, "ClassExpression") && isNodeOfType(classInstantiation, "NewExpression") && stripParenExpression(classInstantiation.callee) === classRoot) return isEvaluatedAtModuleScope(classInstantiation);
|
|
28603
|
+
const classSymbol = getClassBindingSymbol(classNode, scopes);
|
|
28604
|
+
return Boolean(classSymbol && getProcessLifetimeClassSymbolIds(node, scopes).has(classSymbol.id));
|
|
28605
|
+
}
|
|
28606
|
+
}
|
|
28607
|
+
return false;
|
|
28608
|
+
}
|
|
28609
|
+
return false;
|
|
28610
|
+
};
|
|
28611
|
+
const mayCarryAbortSignal = (optionsArgument, scopes) => {
|
|
28612
|
+
if (!optionsArgument) return false;
|
|
28613
|
+
const options = resolveStableOptionsObject(optionsArgument, ["signal"], scopes);
|
|
28614
|
+
if (!options) return true;
|
|
28615
|
+
return options.properties.some((property) => {
|
|
28616
|
+
if (!isNodeOfType(property, "Property")) return true;
|
|
28617
|
+
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
28618
|
+
if (propertyName === null) return true;
|
|
28619
|
+
if (propertyName !== "signal") return false;
|
|
28620
|
+
const value = property.value;
|
|
28621
|
+
if (isNodeOfType(value, "Identifier") && value.name === "undefined") return false;
|
|
28622
|
+
if (isNodeOfType(value, "Literal") && value.value == null) return false;
|
|
28623
|
+
return !(isNodeOfType(value, "UnaryExpression") && value.operator === "void");
|
|
28624
|
+
});
|
|
28625
|
+
};
|
|
28626
|
+
const callbackDisposesReaction = (callExpression, subscriptionName, scopes) => {
|
|
28627
|
+
const callbackArgument = callExpression.arguments[REACTION_CALLBACK_INDEX[subscriptionName]];
|
|
28628
|
+
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return false;
|
|
28629
|
+
const callback = resolveExactLocalFunction(callbackArgument, scopes);
|
|
28630
|
+
if (!callback || !isFunctionLike$1(callback)) return false;
|
|
28631
|
+
const reactionParameter = callback.params?.[REACTION_PARAMETER_INDEX[subscriptionName]];
|
|
28632
|
+
if (!reactionParameter || !isNodeOfType(reactionParameter, "Identifier")) return false;
|
|
28633
|
+
const reactionSymbol = scopes.symbolFor(reactionParameter);
|
|
28634
|
+
if (!reactionSymbol) return false;
|
|
28635
|
+
let doesDisposeReaction = false;
|
|
28636
|
+
walkAst(callback, (candidate) => {
|
|
28637
|
+
if (doesDisposeReaction || !isNodeOfType(candidate, "CallExpression")) return;
|
|
28638
|
+
const callee = stripParenExpression(candidate.callee);
|
|
28639
|
+
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
28640
|
+
const receiver = stripParenExpression(callee.object);
|
|
28641
|
+
if (isNodeOfType(receiver, "Identifier") && scopes.symbolFor(receiver)?.id === reactionSymbol.id && getStaticPropertyKeyName(callee, { allowComputedString: true }) === "dispose") doesDisposeReaction = true;
|
|
28642
|
+
});
|
|
28643
|
+
return doesDisposeReaction;
|
|
28644
|
+
};
|
|
28645
|
+
const getMemberReceiverRoot = (memberExpression) => {
|
|
28646
|
+
let receiver = stripParenExpression(memberExpression);
|
|
28647
|
+
while (isNodeOfType(receiver, "MemberExpression")) receiver = stripParenExpression(receiver.object);
|
|
28648
|
+
return receiver;
|
|
28649
|
+
};
|
|
28650
|
+
const observesOnlyInstanceRootedState = (callExpression, subscriptionName, scopes) => {
|
|
28651
|
+
const callbackArgument = callExpression.arguments[OBSERVATION_CALLBACK_INDEX[subscriptionName]];
|
|
28652
|
+
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return false;
|
|
28653
|
+
const callback = resolveExactLocalFunction(callbackArgument, scopes);
|
|
28654
|
+
if (!callback || !isFunctionLike$1(callback)) return false;
|
|
28655
|
+
let observesExternalState = false;
|
|
28656
|
+
walkAst(callback, (candidate) => {
|
|
28657
|
+
if (candidate !== callback && isFunctionLike$1(candidate)) return false;
|
|
28658
|
+
if (observesExternalState || !isNodeOfType(candidate, "MemberExpression")) return;
|
|
28659
|
+
const receiver = getMemberReceiverRoot(candidate);
|
|
28660
|
+
const candidateRoot = findTransparentExpressionRoot(candidate);
|
|
28661
|
+
const parent = candidateRoot.parent;
|
|
28662
|
+
const isDirectMethodCall = isNodeOfType(parent, "CallExpression") && parent.callee === candidateRoot;
|
|
28663
|
+
if (isNodeOfType(receiver, "ThisExpression")) {
|
|
28664
|
+
if (isDirectMethodCall) observesExternalState = true;
|
|
28665
|
+
return;
|
|
28666
|
+
}
|
|
28667
|
+
if (!isNodeOfType(receiver, "Identifier")) {
|
|
28668
|
+
observesExternalState = true;
|
|
28669
|
+
return;
|
|
28670
|
+
}
|
|
28671
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
28672
|
+
if (receiverSymbol && isAstDescendant(receiverSymbol.declarationNode, callback) || scopes.isGlobalReference(receiver) && NON_OBSERVABLE_GLOBAL_RECEIVER_NAMES.has(receiver.name)) return;
|
|
28673
|
+
if (receiverSymbol?.kind === "import" && isDirectMethodCall && NON_OBSERVING_IMPORTED_METHOD_NAMES.has(getStaticPropertyKeyName(candidate, { allowComputedString: true }) ?? "")) return;
|
|
28674
|
+
observesExternalState = true;
|
|
28675
|
+
});
|
|
28676
|
+
return !observesExternalState;
|
|
28677
|
+
};
|
|
28678
|
+
const isForwardedFromConciseArrow = (callExpression) => {
|
|
28679
|
+
let expressionRoot = findTransparentExpressionRoot(callExpression);
|
|
28680
|
+
let parent = expressionRoot.parent;
|
|
28681
|
+
while (parent) {
|
|
28682
|
+
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === expressionRoot) return true;
|
|
28683
|
+
if (isNodeOfType(parent, "LogicalExpression") && (parent.right === expressionRoot || parent.left === expressionRoot && parent.operator !== "&&") || isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === expressionRoot || parent.alternate === expressionRoot) || isNodeOfType(parent, "SequenceExpression") && parent.expressions[parent.expressions.length - 1] === expressionRoot) {
|
|
28684
|
+
expressionRoot = findTransparentExpressionRoot(parent);
|
|
28685
|
+
parent = expressionRoot.parent;
|
|
28686
|
+
continue;
|
|
28687
|
+
}
|
|
28688
|
+
return false;
|
|
28689
|
+
}
|
|
28690
|
+
return false;
|
|
28691
|
+
};
|
|
28692
|
+
const isDisposerDiscarded = (callExpression) => {
|
|
28693
|
+
if (isForwardedFromConciseArrow(callExpression)) return false;
|
|
28694
|
+
const expressionRoot = findTransparentExpressionRoot(callExpression);
|
|
28695
|
+
if (isResultDiscardedCall(callExpression)) return true;
|
|
28696
|
+
const parent = expressionRoot.parent;
|
|
28697
|
+
if (!parent) return false;
|
|
28698
|
+
if (isNodeOfType(parent, "UnaryExpression") || isNodeOfType(parent, "BinaryExpression")) return true;
|
|
28699
|
+
if ((isNodeOfType(parent, "IfStatement") || isNodeOfType(parent, "WhileStatement") || isNodeOfType(parent, "DoWhileStatement") || isNodeOfType(parent, "ForStatement")) && parent.test === expressionRoot) return true;
|
|
28700
|
+
if (isNodeOfType(parent, "ConditionalExpression") && parent.test === expressionRoot || isNodeOfType(parent, "SwitchStatement") && parent.discriminant === expressionRoot) return true;
|
|
28701
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.left === expressionRoot) return parent.operator === "&&" || isResultDiscardedCall(parent);
|
|
28702
|
+
const callee = isNodeOfType(parent, "CallExpression") ? stripParenExpression(parent.callee) : null;
|
|
28703
|
+
return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments.some((argument) => argument === expressionRoot) && isNodeOfType(callee, "Identifier") && DISPOSER_COERCION_NAMES.has(callee.name));
|
|
28704
|
+
};
|
|
28705
|
+
const mobxReactionDisposerDiscarded = defineRule({
|
|
28706
|
+
id: "mobx-reaction-disposer-discarded",
|
|
28707
|
+
title: "MobX reaction disposer discarded",
|
|
28708
|
+
severity: "warn",
|
|
28709
|
+
category: "Bugs",
|
|
28710
|
+
requires: MOBX_RULE_GATES["mobx-reaction-disposer-discarded"].requires,
|
|
28711
|
+
recommendation: "Keep the disposer returned by `reaction` or `autorun` and invoke it during teardown, or provide an AbortSignal.",
|
|
28712
|
+
create: (context) => ({ CallExpression(callExpression) {
|
|
28713
|
+
const subscriptionName = resolveLeakingSubscriptionName(callExpression, context.scopes);
|
|
28714
|
+
if (!subscriptionName || !isDisposerDiscarded(callExpression)) return;
|
|
28715
|
+
if (isEvaluatedAtModuleScope(callExpression)) return;
|
|
28716
|
+
if (isProcessLifetimeWiring(callExpression, context.scopes)) return;
|
|
28717
|
+
if (callbackDisposesReaction(callExpression, subscriptionName, context.scopes)) return;
|
|
28718
|
+
if (observesOnlyInstanceRootedState(callExpression, subscriptionName, context.scopes)) return;
|
|
28719
|
+
const optionsArgument = callExpression.arguments[OPTIONS_ARGUMENT_INDEX[subscriptionName]];
|
|
28720
|
+
if (mayCarryAbortSignal(optionsArgument, context.scopes)) return;
|
|
28721
|
+
context.report({
|
|
28722
|
+
node: callExpression,
|
|
28723
|
+
message: MESSAGE$41
|
|
28724
|
+
});
|
|
28725
|
+
} })
|
|
28726
|
+
});
|
|
28727
|
+
//#endregion
|
|
28189
28728
|
//#region src/plugin/utils/has-jsx-prop.ts
|
|
28190
28729
|
const hasJsxProp = (attributes, targetProp) => {
|
|
28191
28730
|
for (const attribute of attributes) {
|
|
@@ -28976,11 +29515,11 @@ const hasEmailTemplateImport = (programRoot) => {
|
|
|
28976
29515
|
return found;
|
|
28977
29516
|
};
|
|
28978
29517
|
//#endregion
|
|
28979
|
-
//#region src/plugin/utils/build-
|
|
28980
|
-
const
|
|
28981
|
-
const
|
|
28982
|
-
const
|
|
28983
|
-
const
|
|
29518
|
+
//#region src/plugin/utils/build-source-project-index.ts
|
|
29519
|
+
const SOURCE_PROJECT_FILE_PATTERN = /\.[cm]?[jt]sx?$/i;
|
|
29520
|
+
const SOURCE_PROJECT_DECLARATION_FILE_PATTERN = /\.d\.[cm]?[jt]s$/i;
|
|
29521
|
+
const SOURCE_PROJECT_MDX_FILE_PATTERN = /\.mdx$/i;
|
|
29522
|
+
const SOURCE_PROJECT_IGNORED_DIRECTORY_NAMES = new Set([
|
|
28984
29523
|
".angular",
|
|
28985
29524
|
".astro",
|
|
28986
29525
|
".cache",
|
|
@@ -29001,12 +29540,12 @@ const GENERATED_IMAGE_IGNORED_DIRECTORY_NAMES = new Set([
|
|
|
29001
29540
|
"out",
|
|
29002
29541
|
"storybook-static"
|
|
29003
29542
|
]);
|
|
29004
|
-
const
|
|
29005
|
-
const
|
|
29006
|
-
const cachedScopes =
|
|
29543
|
+
const sourceProjectScopeCache = /* @__PURE__ */ new WeakMap();
|
|
29544
|
+
const getSourceProjectModuleScopes = (programNode) => {
|
|
29545
|
+
const cachedScopes = sourceProjectScopeCache.get(programNode);
|
|
29007
29546
|
if (cachedScopes) return cachedScopes;
|
|
29008
29547
|
const scopes = analyzeScopes(programNode);
|
|
29009
|
-
|
|
29548
|
+
sourceProjectScopeCache.set(programNode, scopes);
|
|
29010
29549
|
return scopes;
|
|
29011
29550
|
};
|
|
29012
29551
|
const listProductionSourceFiles = (rootDirectory) => {
|
|
@@ -29024,7 +29563,7 @@ const listProductionSourceFiles = (rootDirectory) => {
|
|
|
29024
29563
|
}
|
|
29025
29564
|
for (const entry of entries) {
|
|
29026
29565
|
const absolutePath = path.join(currentDirectory, entry.name);
|
|
29027
|
-
const isIgnoredDirectoryName =
|
|
29566
|
+
const isIgnoredDirectoryName = SOURCE_PROJECT_IGNORED_DIRECTORY_NAMES.has(entry.name) || entry.name.startsWith(".") && entry.name !== ".dumi" && entry.name !== ".storybook";
|
|
29028
29567
|
if (entry.isSymbolicLink() && isIgnoredDirectoryName) continue;
|
|
29029
29568
|
if (entry.isSymbolicLink()) return null;
|
|
29030
29569
|
if (entry.isDirectory()) {
|
|
@@ -29033,12 +29572,12 @@ const listProductionSourceFiles = (rootDirectory) => {
|
|
|
29033
29572
|
continue;
|
|
29034
29573
|
}
|
|
29035
29574
|
if (!entry.isFile() || isTestlikeFilename(absolutePath)) continue;
|
|
29036
|
-
if (
|
|
29575
|
+
if (SOURCE_PROJECT_MDX_FILE_PATTERN.test(entry.name)) {
|
|
29037
29576
|
hasOpaqueMdxConsumerSurface = true;
|
|
29038
29577
|
continue;
|
|
29039
29578
|
}
|
|
29040
|
-
if (!
|
|
29041
|
-
if (
|
|
29579
|
+
if (!SOURCE_PROJECT_FILE_PATTERN.test(entry.name)) continue;
|
|
29580
|
+
if (SOURCE_PROJECT_DECLARATION_FILE_PATTERN.test(entry.name)) continue;
|
|
29042
29581
|
sourceFilePaths.push(normalizeFilename(absolutePath));
|
|
29043
29582
|
}
|
|
29044
29583
|
}
|
|
@@ -29083,7 +29622,7 @@ const indexModuleSources = (module, consumerModulesByFilePath, resolvedSourcePat
|
|
|
29083
29622
|
consumerModulesByFilePath.set(normalizedSourcePath, consumerModules);
|
|
29084
29623
|
});
|
|
29085
29624
|
};
|
|
29086
|
-
const
|
|
29625
|
+
const buildSourceProjectIndex = (rootDirectory, currentFilePath, currentProgramNode, currentScopes) => {
|
|
29087
29626
|
const productionSourceFiles = listProductionSourceFiles(rootDirectory);
|
|
29088
29627
|
if (!productionSourceFiles) return null;
|
|
29089
29628
|
const modulesByFilePath = /* @__PURE__ */ new Map();
|
|
@@ -29101,7 +29640,7 @@ const buildGeneratedImageProjectIndex = (rootDirectory, currentFilePath, current
|
|
|
29101
29640
|
modulesByFilePath.set(filePath, {
|
|
29102
29641
|
filePath,
|
|
29103
29642
|
programNode: parsedProgram,
|
|
29104
|
-
scopes:
|
|
29643
|
+
scopes: getSourceProjectModuleScopes(parsedProgram)
|
|
29105
29644
|
});
|
|
29106
29645
|
}
|
|
29107
29646
|
const consumerModulesByFilePath = /* @__PURE__ */ new Map();
|
|
@@ -29117,6 +29656,51 @@ const buildGeneratedImageProjectIndex = (rootDirectory, currentFilePath, current
|
|
|
29117
29656
|
};
|
|
29118
29657
|
};
|
|
29119
29658
|
//#endregion
|
|
29659
|
+
//#region src/plugin/utils/get-direct-function-binding-identifier.ts
|
|
29660
|
+
const getDirectFunctionBindingIdentifier = (functionNode) => {
|
|
29661
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
29662
|
+
const functionValueRoot = findTransparentExpressionRoot(functionNode);
|
|
29663
|
+
const parent = functionValueRoot.parent;
|
|
29664
|
+
return isNodeOfType(parent, "VariableDeclarator") && parent.init === functionValueRoot && isNodeOfType(parent.id, "Identifier") ? parent.id : null;
|
|
29665
|
+
};
|
|
29666
|
+
//#endregion
|
|
29667
|
+
//#region src/plugin/utils/get-function-export-names.ts
|
|
29668
|
+
const getExportedSpecifierName$1 = (specifier) => {
|
|
29669
|
+
const exported = specifier.exported;
|
|
29670
|
+
if (isNodeOfType(exported, "Identifier")) return exported.name;
|
|
29671
|
+
return isNodeOfType(exported, "Literal") && typeof exported.value === "string" ? exported.value : null;
|
|
29672
|
+
};
|
|
29673
|
+
const getLocalSpecifierName = (specifier) => {
|
|
29674
|
+
const local = specifier.local;
|
|
29675
|
+
if (isNodeOfType(local, "Identifier")) return local.name;
|
|
29676
|
+
return isNodeOfType(local, "Literal") && typeof local.value === "string" ? local.value : null;
|
|
29677
|
+
};
|
|
29678
|
+
const getFunctionExportNames = (programNode, functionNode) => {
|
|
29679
|
+
const functionValueRoot = findTransparentExpressionRoot(functionNode);
|
|
29680
|
+
const bindingName = getDirectFunctionBindingIdentifier(functionNode)?.name ?? null;
|
|
29681
|
+
const exportedNames = /* @__PURE__ */ new Set();
|
|
29682
|
+
for (const statement of programNode.body) {
|
|
29683
|
+
if (isNodeOfType(statement, "ExportDefaultDeclaration")) {
|
|
29684
|
+
if (statement.declaration === functionValueRoot || bindingName && isNodeOfType(statement.declaration, "Identifier") && statement.declaration.name === bindingName) exportedNames.add("default");
|
|
29685
|
+
continue;
|
|
29686
|
+
}
|
|
29687
|
+
if (!isNodeOfType(statement, "ExportNamedDeclaration")) continue;
|
|
29688
|
+
const declaration = statement.declaration;
|
|
29689
|
+
if (declaration === functionValueRoot && bindingName) exportedNames.add(bindingName);
|
|
29690
|
+
if (declaration && isNodeOfType(declaration, "VariableDeclaration")) {
|
|
29691
|
+
for (const declarator of declaration.declarations) if (declarator.init === functionValueRoot && isNodeOfType(declarator.id, "Identifier")) exportedNames.add(declarator.id.name);
|
|
29692
|
+
}
|
|
29693
|
+
if (!bindingName || statement.source) continue;
|
|
29694
|
+
for (const specifier of statement.specifiers) {
|
|
29695
|
+
if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
|
|
29696
|
+
if (getLocalSpecifierName(specifier) !== bindingName) continue;
|
|
29697
|
+
const exportedName = getExportedSpecifierName$1(specifier);
|
|
29698
|
+
if (exportedName) exportedNames.add(exportedName);
|
|
29699
|
+
}
|
|
29700
|
+
}
|
|
29701
|
+
return [...exportedNames];
|
|
29702
|
+
};
|
|
29703
|
+
//#endregion
|
|
29120
29704
|
//#region src/plugin/utils/read-nearest-package-manifest.ts
|
|
29121
29705
|
const cachedPackageDirectoryByFilename = /* @__PURE__ */ new Map();
|
|
29122
29706
|
const cachedManifestByPackageDirectory = /* @__PURE__ */ new Map();
|
|
@@ -29201,37 +29785,6 @@ const getImportSpecifierName = (specifier) => {
|
|
|
29201
29785
|
if (isNodeOfType(imported, "Identifier")) return imported.name;
|
|
29202
29786
|
return isNodeOfType(imported, "Literal") && typeof imported.value === "string" ? imported.value : null;
|
|
29203
29787
|
};
|
|
29204
|
-
const getDirectFunctionBindingIdentifier = (functionNode) => {
|
|
29205
|
-
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
29206
|
-
const functionValueRoot = findTransparentExpressionRoot(functionNode);
|
|
29207
|
-
const parent = functionValueRoot.parent;
|
|
29208
|
-
return isNodeOfType(parent, "VariableDeclarator") && parent.init === functionValueRoot && isNodeOfType(parent.id, "Identifier") ? parent.id : null;
|
|
29209
|
-
};
|
|
29210
|
-
const getExportNamesForFunction = (programNode, functionNode) => {
|
|
29211
|
-
const functionValueRoot = findTransparentExpressionRoot(functionNode);
|
|
29212
|
-
const bindingName = getDirectFunctionBindingIdentifier(functionNode)?.name ?? null;
|
|
29213
|
-
const exportedNames = /* @__PURE__ */ new Set();
|
|
29214
|
-
for (const statement of programNode.body) {
|
|
29215
|
-
if (isNodeOfType(statement, "ExportDefaultDeclaration")) {
|
|
29216
|
-
if (statement.declaration === functionValueRoot || bindingName && isNodeOfType(statement.declaration, "Identifier") && statement.declaration.name === bindingName) exportedNames.add("default");
|
|
29217
|
-
continue;
|
|
29218
|
-
}
|
|
29219
|
-
if (!isNodeOfType(statement, "ExportNamedDeclaration")) continue;
|
|
29220
|
-
const declaration = statement.declaration;
|
|
29221
|
-
if (declaration === functionValueRoot && bindingName) exportedNames.add(bindingName);
|
|
29222
|
-
if (declaration && isNodeOfType(declaration, "VariableDeclaration")) {
|
|
29223
|
-
for (const declarator of declaration.declarations) if (declarator.init === functionValueRoot && isNodeOfType(declarator.id, "Identifier")) exportedNames.add(declarator.id.name);
|
|
29224
|
-
}
|
|
29225
|
-
if (!bindingName || statement.source) continue;
|
|
29226
|
-
for (const specifier of statement.specifiers) {
|
|
29227
|
-
if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
|
|
29228
|
-
if (getImportedSpecifierName(specifier) !== bindingName) continue;
|
|
29229
|
-
const exportedName = getExportedSpecifierName(specifier);
|
|
29230
|
-
if (exportedName) exportedNames.add(exportedName);
|
|
29231
|
-
}
|
|
29232
|
-
}
|
|
29233
|
-
return [...exportedNames];
|
|
29234
|
-
};
|
|
29235
29788
|
const isTransparentGeneratedImageValueFlow = (expression, target) => {
|
|
29236
29789
|
let current = findTransparentExpressionRoot(expression);
|
|
29237
29790
|
while (current !== target) {
|
|
@@ -29290,7 +29843,7 @@ const classifyInvokedExpression = (module, expression, state) => {
|
|
|
29290
29843
|
}
|
|
29291
29844
|
const forwardingFunction = getForwardingFunction(expression);
|
|
29292
29845
|
if (!forwardingFunction) return false;
|
|
29293
|
-
const exportedNames =
|
|
29846
|
+
const exportedNames = getFunctionExportNames(module.programNode, forwardingFunction);
|
|
29294
29847
|
if (exportedNames.length === 0) return false;
|
|
29295
29848
|
for (const exportedName of exportedNames) enqueueExport(state, module.filePath, exportedName);
|
|
29296
29849
|
return true;
|
|
@@ -29423,9 +29976,9 @@ const createExportedJsxGeneratedImageOwnershipAnalyzer = (context) => {
|
|
|
29423
29976
|
const programNode = findProgramRoot(jsxNode);
|
|
29424
29977
|
const enclosingFunction = findEnclosingFunction$1(jsxNode);
|
|
29425
29978
|
if (!programNode || !enclosingFunction) return false;
|
|
29426
|
-
const initialExportNames =
|
|
29979
|
+
const initialExportNames = getFunctionExportNames(programNode, enclosingFunction);
|
|
29427
29980
|
if (initialExportNames.length === 0) return false;
|
|
29428
|
-
if (projectIndex === void 0) projectIndex =
|
|
29981
|
+
if (projectIndex === void 0) projectIndex = buildSourceProjectIndex(rootDirectory, filename, programNode, context.scopes);
|
|
29429
29982
|
if (!projectIndex || projectIndex.hasOpaqueMdxConsumerSurface) return false;
|
|
29430
29983
|
const state = {
|
|
29431
29984
|
projectIndex,
|
|
@@ -41733,18 +42286,10 @@ const isCompletionSinkAfterCancellationEarlyExit = (completionSink, cancellation
|
|
|
41733
42286
|
return false;
|
|
41734
42287
|
};
|
|
41735
42288
|
const isCompletionSinkGuardedByCancellationFlag = (completionSink, cancellationFlagKey, context) => isCompletionSinkInsideCancellationGuard(completionSink, cancellationFlagKey, context) || isCompletionSinkAfterCancellationEarlyExit(completionSink, cancellationFlagKey, context);
|
|
41736
|
-
const isDescendantOf = (node, ancestor) => {
|
|
41737
|
-
let currentNode = node;
|
|
41738
|
-
while (currentNode) {
|
|
41739
|
-
if (currentNode === ancestor) return true;
|
|
41740
|
-
currentNode = currentNode.parent ?? null;
|
|
41741
|
-
}
|
|
41742
|
-
return false;
|
|
41743
|
-
};
|
|
41744
42289
|
const isPromiseContinuationForRequest = (functionNode, request) => {
|
|
41745
42290
|
const callNode = functionNode.parent;
|
|
41746
42291
|
if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((callArgument) => callArgument === functionNode) || !isNodeOfType(callNode.callee, "MemberExpression") || callNode.callee.computed || !isNodeOfType(callNode.callee.property, "Identifier") || !PROMISE_CONTINUATION_METHOD_NAMES$1.has(callNode.callee.property.name)) return false;
|
|
41747
|
-
return
|
|
42292
|
+
return isAstDescendant(request, callNode.callee.object);
|
|
41748
42293
|
};
|
|
41749
42294
|
const isAwaitedInFunction = (request, functionNode) => {
|
|
41750
42295
|
let currentNode = request.parent;
|
|
@@ -45416,41 +45961,6 @@ const noMutableInDeps = defineRule({
|
|
|
45416
45961
|
}
|
|
45417
45962
|
});
|
|
45418
45963
|
//#endregion
|
|
45419
|
-
//#region src/plugin/utils/is-result-discarded-call.ts
|
|
45420
|
-
const isResultDiscardedCall = (callExpression) => {
|
|
45421
|
-
let node = callExpression;
|
|
45422
|
-
let parent = node.parent;
|
|
45423
|
-
while (parent) {
|
|
45424
|
-
if (isNodeOfType(parent, "ExpressionStatement")) return true;
|
|
45425
|
-
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
|
|
45426
|
-
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
|
|
45427
|
-
if (isNodeOfType(parent, "ChainExpression")) {
|
|
45428
|
-
node = parent;
|
|
45429
|
-
parent = node.parent;
|
|
45430
|
-
continue;
|
|
45431
|
-
}
|
|
45432
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
|
|
45433
|
-
node = parent;
|
|
45434
|
-
parent = node.parent;
|
|
45435
|
-
continue;
|
|
45436
|
-
}
|
|
45437
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
|
|
45438
|
-
node = parent;
|
|
45439
|
-
parent = node.parent;
|
|
45440
|
-
continue;
|
|
45441
|
-
}
|
|
45442
|
-
if (isNodeOfType(parent, "SequenceExpression")) {
|
|
45443
|
-
const expressions = parent.expressions ?? [];
|
|
45444
|
-
if (expressions[expressions.length - 1] !== node) return true;
|
|
45445
|
-
node = parent;
|
|
45446
|
-
parent = node.parent;
|
|
45447
|
-
continue;
|
|
45448
|
-
}
|
|
45449
|
-
return false;
|
|
45450
|
-
}
|
|
45451
|
-
return false;
|
|
45452
|
-
};
|
|
45453
|
-
//#endregion
|
|
45454
45964
|
//#region src/plugin/rules/state-and-effects/utils/lodash-mutator-call.ts
|
|
45455
45965
|
const LODASH_MUTATOR_NAMES = new Set([
|
|
45456
45966
|
"set",
|
|
@@ -53688,7 +54198,7 @@ const isElementTypeJsxAttribute = (node) => {
|
|
|
53688
54198
|
const attributeName = node.name.name;
|
|
53689
54199
|
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
53690
54200
|
};
|
|
53691
|
-
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
54201
|
+
const isReactUseMemoCallback$1 = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
53692
54202
|
allowGlobalReactNamespace: true,
|
|
53693
54203
|
resolveNamedAliases: true
|
|
53694
54204
|
});
|
|
@@ -53712,7 +54222,7 @@ const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @_
|
|
|
53712
54222
|
case "ArrowFunctionExpression": return false;
|
|
53713
54223
|
case "CallExpression":
|
|
53714
54224
|
if (parent.callee === valueNode) return false;
|
|
53715
|
-
if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
|
|
54225
|
+
if (isReactUseMemoCallback$1(parent, valueNode, scopes)) return false;
|
|
53716
54226
|
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
53717
54227
|
valueNode = parent;
|
|
53718
54228
|
parent = parent.parent;
|
|
@@ -58739,6 +59249,560 @@ const reduxUseselectorReturnsNewCollection = defineRule({
|
|
|
58739
59249
|
}
|
|
58740
59250
|
});
|
|
58741
59251
|
//#endregion
|
|
59252
|
+
//#region src/plugin/utils/is-remotion-module-source.ts
|
|
59253
|
+
const isRemotionModuleSource = (moduleSource) => moduleSource === "remotion" || moduleSource.startsWith("@remotion/");
|
|
59254
|
+
//#endregion
|
|
59255
|
+
//#region src/plugin/utils/resolve-remotion-api.ts
|
|
59256
|
+
const resolveRemotionApi = (referenceNode, scopes) => {
|
|
59257
|
+
const candidate = stripParenExpression(referenceNode);
|
|
59258
|
+
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "JSXIdentifier")) {
|
|
59259
|
+
const symbol = scopes.symbolFor(candidate);
|
|
59260
|
+
if (symbol?.kind !== "import") return null;
|
|
59261
|
+
const importBinding = getImportBindingForName(candidate, symbol.name);
|
|
59262
|
+
if (!importBinding || importBinding.isNamespace || importBinding.exportedName === null || !isRemotionModuleSource(importBinding.source)) return null;
|
|
59263
|
+
return {
|
|
59264
|
+
apiName: importBinding.exportedName,
|
|
59265
|
+
moduleSource: importBinding.source
|
|
59266
|
+
};
|
|
59267
|
+
}
|
|
59268
|
+
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
59269
|
+
const apiName = getStaticPropertyKeyName(candidate, { allowComputedString: true });
|
|
59270
|
+
const namespaceObject = stripParenExpression(candidate.object);
|
|
59271
|
+
if (!apiName || !isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
59272
|
+
const symbol = scopes.symbolFor(namespaceObject);
|
|
59273
|
+
if (symbol?.kind !== "import") return null;
|
|
59274
|
+
const importBinding = getImportBindingForName(namespaceObject, symbol.name);
|
|
59275
|
+
if (!importBinding?.isNamespace || !isRemotionModuleSource(importBinding.source)) return null;
|
|
59276
|
+
return {
|
|
59277
|
+
apiName,
|
|
59278
|
+
moduleSource: importBinding.source
|
|
59279
|
+
};
|
|
59280
|
+
}
|
|
59281
|
+
if (!isNodeOfType(candidate, "JSXMemberExpression") || !isNodeOfType(candidate.object, "JSXIdentifier") || !isNodeOfType(candidate.property, "JSXIdentifier")) return null;
|
|
59282
|
+
const symbol = scopes.symbolFor(candidate.object);
|
|
59283
|
+
if (symbol?.kind !== "import") return null;
|
|
59284
|
+
const importBinding = getImportBindingForName(candidate.object, symbol.name);
|
|
59285
|
+
if (!importBinding?.isNamespace || !isRemotionModuleSource(importBinding.source)) return null;
|
|
59286
|
+
return {
|
|
59287
|
+
apiName: candidate.property.name,
|
|
59288
|
+
moduleSource: importBinding.source
|
|
59289
|
+
};
|
|
59290
|
+
};
|
|
59291
|
+
//#endregion
|
|
59292
|
+
//#region src/plugin/utils/create-remotion-composition-ownership-analyzer.ts
|
|
59293
|
+
const compositionAttributeFunctionCacheBySettings = /* @__PURE__ */ new WeakMap();
|
|
59294
|
+
const getFunctionExportKeys = (filePath, programNode, functionNode) => getFunctionExportNames(programNode, functionNode).map((exportedName) => `${normalizeFilename(filePath)}\0${exportedName}`);
|
|
59295
|
+
const resolveImportedCompositionFunction = (expression, module) => {
|
|
59296
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
59297
|
+
let importReference;
|
|
59298
|
+
let exportedName;
|
|
59299
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
59300
|
+
const symbol = module.scopes.symbolFor(unwrappedExpression);
|
|
59301
|
+
if (symbol?.kind !== "import") return null;
|
|
59302
|
+
importReference = unwrappedExpression;
|
|
59303
|
+
exportedName = getImportBindingForName(unwrappedExpression, symbol.name)?.exportedName ?? null;
|
|
59304
|
+
} else if (isNodeOfType(unwrappedExpression, "MemberExpression") && isNodeOfType(stripParenExpression(unwrappedExpression.object), "Identifier")) {
|
|
59305
|
+
const namespaceObject = stripParenExpression(unwrappedExpression.object);
|
|
59306
|
+
if (!isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
59307
|
+
const symbol = module.scopes.symbolFor(namespaceObject);
|
|
59308
|
+
if (symbol?.kind !== "import") return null;
|
|
59309
|
+
if (!getImportBindingForName(namespaceObject, symbol.name)?.isNamespace) return null;
|
|
59310
|
+
importReference = namespaceObject;
|
|
59311
|
+
exportedName = getStaticPropertyKeyName(unwrappedExpression, { allowComputedString: true });
|
|
59312
|
+
} else return null;
|
|
59313
|
+
if (!exportedName) return null;
|
|
59314
|
+
const importBinding = getImportBindingForName(importReference, importReference.name);
|
|
59315
|
+
if (!importBinding) return null;
|
|
59316
|
+
return resolveCrossFileFunctionExportWithFilePath(module.filePath, importBinding.source, exportedName);
|
|
59317
|
+
};
|
|
59318
|
+
const collectCompositionAttributeFunctionKeys = (context, currentProgram, attributeName) => {
|
|
59319
|
+
const filename = context.filename ? normalizeFilename(context.filename) : "";
|
|
59320
|
+
const rootDirectorySetting = getReactDoctorStringSetting(context.settings, "rootDirectory");
|
|
59321
|
+
const rootDirectory = rootDirectorySetting ? normalizeFilename(rootDirectorySetting).replace(/\/$/, "") : "";
|
|
59322
|
+
if (!filename || !rootDirectory || filename !== rootDirectory && !filename.startsWith(`${rootDirectory}/`)) return null;
|
|
59323
|
+
const projectIndex = buildSourceProjectIndex(rootDirectory, filename, currentProgram, context.scopes);
|
|
59324
|
+
if (!projectIndex) return null;
|
|
59325
|
+
const functionKeys = /* @__PURE__ */ new Set();
|
|
59326
|
+
for (const module of projectIndex.modulesByFilePath.values()) walkAst(module.programNode, (candidate) => {
|
|
59327
|
+
if (!isNodeOfType(candidate, "JSXOpeningElement")) return;
|
|
59328
|
+
const apiBinding = resolveRemotionApi(candidate.name, module.scopes);
|
|
59329
|
+
if (apiBinding?.apiName !== "Composition" || apiBinding.moduleSource !== "remotion") return;
|
|
59330
|
+
const functionAttribute = findJsxAttribute(candidate.attributes, attributeName);
|
|
59331
|
+
if (!functionAttribute?.value || !isNodeOfType(functionAttribute.value, "JSXExpressionContainer") || !functionAttribute.value.expression) return;
|
|
59332
|
+
const resolvedFunction = resolveImportedCompositionFunction(functionAttribute.value.expression, module);
|
|
59333
|
+
if (!resolvedFunction || !isNodeOfType(resolvedFunction.programNode, "Program")) return;
|
|
59334
|
+
for (const functionKey of getFunctionExportKeys(resolvedFunction.filePath, resolvedFunction.programNode, resolvedFunction.functionNode)) functionKeys.add(functionKey);
|
|
59335
|
+
});
|
|
59336
|
+
return functionKeys;
|
|
59337
|
+
};
|
|
59338
|
+
const createRemotionCompositionAttributeOwnershipAnalyzer = (context, attributeName) => {
|
|
59339
|
+
const settings = context.settings;
|
|
59340
|
+
return (functionNode) => {
|
|
59341
|
+
const currentProgram = findProgramRoot(functionNode);
|
|
59342
|
+
if (!currentProgram || !isNodeOfType(currentProgram, "Program") || !context.filename || !settings) return false;
|
|
59343
|
+
const currentFunctionKeys = getFunctionExportKeys(context.filename, currentProgram, functionNode);
|
|
59344
|
+
if (currentFunctionKeys.length === 0) return false;
|
|
59345
|
+
let cacheByAttributeName = compositionAttributeFunctionCacheBySettings.get(settings);
|
|
59346
|
+
if (!cacheByAttributeName) {
|
|
59347
|
+
cacheByAttributeName = /* @__PURE__ */ new Map();
|
|
59348
|
+
compositionAttributeFunctionCacheBySettings.set(settings, cacheByAttributeName);
|
|
59349
|
+
}
|
|
59350
|
+
let cache = cacheByAttributeName.get(attributeName);
|
|
59351
|
+
if (!cache) {
|
|
59352
|
+
cache = { failedFilenames: /* @__PURE__ */ new Set() };
|
|
59353
|
+
cacheByAttributeName.set(attributeName, cache);
|
|
59354
|
+
}
|
|
59355
|
+
let functionKeys = cache.functionKeys;
|
|
59356
|
+
if (!functionKeys) {
|
|
59357
|
+
const filename = normalizeFilename(context.filename);
|
|
59358
|
+
if (cache.failedFilenames.has(filename)) return false;
|
|
59359
|
+
const collectedFunctionKeys = collectCompositionAttributeFunctionKeys(context, currentProgram, attributeName);
|
|
59360
|
+
if (!collectedFunctionKeys) {
|
|
59361
|
+
cache.failedFilenames.add(filename);
|
|
59362
|
+
return false;
|
|
59363
|
+
}
|
|
59364
|
+
functionKeys = collectedFunctionKeys;
|
|
59365
|
+
cache.functionKeys = collectedFunctionKeys;
|
|
59366
|
+
}
|
|
59367
|
+
return currentFunctionKeys.some((functionKey) => functionKeys.has(functionKey));
|
|
59368
|
+
};
|
|
59369
|
+
};
|
|
59370
|
+
const createRemotionCompositionOwnershipAnalyzer = (context) => createRemotionCompositionAttributeOwnershipAnalyzer(context, "component");
|
|
59371
|
+
const createRemotionMetadataOwnershipAnalyzer = (context) => createRemotionCompositionAttributeOwnershipAnalyzer(context, "calculateMetadata");
|
|
59372
|
+
//#endregion
|
|
59373
|
+
//#region src/plugin/rules/correctness/remotion-calculate-metadata-fetch-signal.ts
|
|
59374
|
+
const getParameterIdentifier = (parameter) => {
|
|
59375
|
+
if (isNodeOfType(parameter, "Identifier")) return parameter;
|
|
59376
|
+
if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
|
|
59377
|
+
return null;
|
|
59378
|
+
};
|
|
59379
|
+
const getAbortSignalBinding = (functionNode) => {
|
|
59380
|
+
if (!isFunctionLike$1(functionNode)) return {
|
|
59381
|
+
parameterIdentifier: null,
|
|
59382
|
+
signalIdentifier: null
|
|
59383
|
+
};
|
|
59384
|
+
const firstParameter = functionNode.params[0];
|
|
59385
|
+
const parameterIdentifier = getParameterIdentifier(firstParameter);
|
|
59386
|
+
if (parameterIdentifier) return {
|
|
59387
|
+
parameterIdentifier,
|
|
59388
|
+
signalIdentifier: null
|
|
59389
|
+
};
|
|
59390
|
+
if (!isNodeOfType(firstParameter, "ObjectPattern")) return {
|
|
59391
|
+
parameterIdentifier: null,
|
|
59392
|
+
signalIdentifier: null
|
|
59393
|
+
};
|
|
59394
|
+
for (const property of firstParameter.properties) {
|
|
59395
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
59396
|
+
if (getStaticPropertyKeyName(property) !== "abortSignal") continue;
|
|
59397
|
+
const signalIdentifier = getParameterIdentifier(property.value);
|
|
59398
|
+
if (signalIdentifier) return {
|
|
59399
|
+
parameterIdentifier: null,
|
|
59400
|
+
signalIdentifier
|
|
59401
|
+
};
|
|
59402
|
+
}
|
|
59403
|
+
return {
|
|
59404
|
+
parameterIdentifier: null,
|
|
59405
|
+
signalIdentifier: null
|
|
59406
|
+
};
|
|
59407
|
+
};
|
|
59408
|
+
const identifiersResolveToSameSymbol = (leftIdentifier, rightIdentifier, scopes) => {
|
|
59409
|
+
if (!isNodeOfType(leftIdentifier, "Identifier") || !isNodeOfType(rightIdentifier, "Identifier")) return false;
|
|
59410
|
+
const leftSymbol = scopes.symbolFor(leftIdentifier);
|
|
59411
|
+
return Boolean(leftSymbol && leftSymbol === scopes.symbolFor(rightIdentifier));
|
|
59412
|
+
};
|
|
59413
|
+
const isMetadataAbortSignal = (expression, binding, scopes) => {
|
|
59414
|
+
const candidate = stripParenExpression(expression);
|
|
59415
|
+
if (binding.signalIdentifier && identifiersResolveToSameSymbol(candidate, binding.signalIdentifier, scopes)) return true;
|
|
59416
|
+
return Boolean(binding.parameterIdentifier && isNodeOfType(candidate, "MemberExpression") && getStaticPropertyKeyName(candidate, { allowComputedString: true }) === "abortSignal" && identifiersResolveToSameSymbol(candidate.object, binding.parameterIdentifier, scopes));
|
|
59417
|
+
};
|
|
59418
|
+
const fetchUsesMetadataAbortSignal = (fetchCall, binding, scopes) => {
|
|
59419
|
+
const optionsArgument = fetchCall.arguments[1];
|
|
59420
|
+
if (!optionsArgument) return false;
|
|
59421
|
+
const options = stripParenExpression(optionsArgument);
|
|
59422
|
+
if (!isNodeOfType(options, "ObjectExpression")) return null;
|
|
59423
|
+
if (options.properties.some((property) => isNodeOfType(property, "SpreadElement"))) return null;
|
|
59424
|
+
for (let propertyIndex = options.properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
|
|
59425
|
+
const property = options.properties[propertyIndex];
|
|
59426
|
+
if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "signal") return isMetadataAbortSignal(property.value, binding, scopes);
|
|
59427
|
+
}
|
|
59428
|
+
return false;
|
|
59429
|
+
};
|
|
59430
|
+
const reportFetchesWithoutAbortSignal = (functionNode, context) => {
|
|
59431
|
+
const binding = getAbortSignalBinding(functionNode);
|
|
59432
|
+
walkAst(functionNode, (candidate) => {
|
|
59433
|
+
if (candidate !== functionNode && isFunctionLike$1(candidate)) return false;
|
|
59434
|
+
if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "Identifier") || candidate.callee.name !== "fetch" || !context.scopes.isGlobalReference(candidate.callee) || fetchUsesMetadataAbortSignal(candidate, binding, context.scopes) !== false) return;
|
|
59435
|
+
context.report({
|
|
59436
|
+
node: candidate,
|
|
59437
|
+
message: "Pass Remotion's `abortSignal` to this fetch with `{signal: abortSignal}` so superseded metadata requests are cancelled."
|
|
59438
|
+
});
|
|
59439
|
+
});
|
|
59440
|
+
};
|
|
59441
|
+
const isCalculateMetadataFunctionType = (variableDeclarator) => {
|
|
59442
|
+
if (!isNodeOfType(variableDeclarator.id, "Identifier")) return false;
|
|
59443
|
+
const annotation = variableDeclarator.id.typeAnnotation;
|
|
59444
|
+
if (!isNodeOfType(annotation, "TSTypeAnnotation") || !isNodeOfType(annotation.typeAnnotation, "TSTypeReference")) return false;
|
|
59445
|
+
const typeName = annotation.typeAnnotation.typeName;
|
|
59446
|
+
if (!isNodeOfType(typeName, "Identifier")) return false;
|
|
59447
|
+
const apiBinding = getImportBindingForName(typeName, typeName.name);
|
|
59448
|
+
return Boolean(apiBinding?.exportedName === "CalculateMetadataFunction" && apiBinding.source === "remotion");
|
|
59449
|
+
};
|
|
59450
|
+
const remotionCalculateMetadataFetchSignal = defineRule({
|
|
59451
|
+
id: "remotion-calculate-metadata-fetch-signal",
|
|
59452
|
+
title: "calculateMetadata fetch ignores abortSignal",
|
|
59453
|
+
tags: ["react-jsx-only"],
|
|
59454
|
+
requires: ["remotion:4"],
|
|
59455
|
+
severity: "error",
|
|
59456
|
+
recommendation: "Destructure `abortSignal` from the calculateMetadata argument and pass it to direct fetch calls as `{signal: abortSignal}`.",
|
|
59457
|
+
create: (context) => {
|
|
59458
|
+
const analyzedFunctions = /* @__PURE__ */ new WeakSet();
|
|
59459
|
+
const isOwnedByCalculateMetadata = createRemotionMetadataOwnershipAnalyzer(context);
|
|
59460
|
+
const analyzeFunction = (functionNode) => {
|
|
59461
|
+
if (!functionNode || analyzedFunctions.has(functionNode)) return;
|
|
59462
|
+
analyzedFunctions.add(functionNode);
|
|
59463
|
+
reportFetchesWithoutAbortSignal(functionNode, context);
|
|
59464
|
+
};
|
|
59465
|
+
return {
|
|
59466
|
+
CallExpression(node) {
|
|
59467
|
+
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "fetch" || !context.scopes.isGlobalReference(node.callee)) return;
|
|
59468
|
+
const functionNode = findEnclosingFunction$1(node);
|
|
59469
|
+
if (functionNode && isOwnedByCalculateMetadata(functionNode)) analyzeFunction(functionNode);
|
|
59470
|
+
},
|
|
59471
|
+
JSXOpeningElement(node) {
|
|
59472
|
+
const apiBinding = resolveRemotionApi(node.name, context.scopes);
|
|
59473
|
+
if (apiBinding?.apiName !== "Composition" || apiBinding.moduleSource !== "remotion") return;
|
|
59474
|
+
const calculateMetadataAttribute = findJsxAttribute(node.attributes, "calculateMetadata");
|
|
59475
|
+
if (!calculateMetadataAttribute?.value || !isNodeOfType(calculateMetadataAttribute.value, "JSXExpressionContainer") || !calculateMetadataAttribute.value.expression) return;
|
|
59476
|
+
analyzeFunction(resolveExactLocalFunction(calculateMetadataAttribute.value.expression, context.scopes));
|
|
59477
|
+
},
|
|
59478
|
+
VariableDeclarator(node) {
|
|
59479
|
+
if (!node.init || !isCalculateMetadataFunctionType(node)) return;
|
|
59480
|
+
analyzeFunction(resolveExactLocalFunction(node.init, context.scopes));
|
|
59481
|
+
}
|
|
59482
|
+
};
|
|
59483
|
+
}
|
|
59484
|
+
});
|
|
59485
|
+
//#endregion
|
|
59486
|
+
//#region src/plugin/utils/create-remotion-render-evidence-checker.ts
|
|
59487
|
+
const REMOTION_RENDER_CALL_NAMES = new Set([
|
|
59488
|
+
"continueRender",
|
|
59489
|
+
"delayRender",
|
|
59490
|
+
"getInputProps",
|
|
59491
|
+
"random",
|
|
59492
|
+
"spring",
|
|
59493
|
+
"useCurrentFrame",
|
|
59494
|
+
"useDelayRender",
|
|
59495
|
+
"useVideoConfig"
|
|
59496
|
+
]);
|
|
59497
|
+
const REMOTION_RENDER_COMPONENT_MODULE_BY_NAME = new Map([
|
|
59498
|
+
["Audio", "@remotion/media"],
|
|
59499
|
+
["Freeze", "remotion"],
|
|
59500
|
+
["IFrame", "remotion"],
|
|
59501
|
+
["Img", "remotion"],
|
|
59502
|
+
["Loop", "remotion"],
|
|
59503
|
+
["OffthreadVideo", "remotion"],
|
|
59504
|
+
["Sequence", "remotion"],
|
|
59505
|
+
["Series", "remotion"],
|
|
59506
|
+
["Video", "@remotion/media"]
|
|
59507
|
+
]);
|
|
59508
|
+
const createRemotionRenderEvidenceChecker = (context) => {
|
|
59509
|
+
const scopes = context.scopes;
|
|
59510
|
+
const evidenceByFunction = /* @__PURE__ */ new WeakMap();
|
|
59511
|
+
const registeredCompositionFunctions = /* @__PURE__ */ new WeakSet();
|
|
59512
|
+
const inspectedPrograms = /* @__PURE__ */ new WeakSet();
|
|
59513
|
+
const isOwnedByRegisteredComposition = createRemotionCompositionOwnershipAnalyzer(context);
|
|
59514
|
+
const collectRegisteredCompositionFunctions = (functionNode) => {
|
|
59515
|
+
const program = findProgramRoot(functionNode);
|
|
59516
|
+
if (!program || inspectedPrograms.has(program)) return;
|
|
59517
|
+
inspectedPrograms.add(program);
|
|
59518
|
+
walkAst(program, (candidate) => {
|
|
59519
|
+
if (!isNodeOfType(candidate, "JSXOpeningElement")) return;
|
|
59520
|
+
const apiBinding = resolveRemotionApi(candidate.name, scopes);
|
|
59521
|
+
if (apiBinding?.apiName !== "Composition" || apiBinding.moduleSource !== "remotion") return;
|
|
59522
|
+
const componentAttribute = findJsxAttribute(candidate.attributes, "component");
|
|
59523
|
+
if (!componentAttribute?.value || !isNodeOfType(componentAttribute.value, "JSXExpressionContainer") || !componentAttribute.value.expression) return;
|
|
59524
|
+
const registeredFunction = resolveExactLocalFunction(componentAttribute.value.expression, scopes);
|
|
59525
|
+
if (registeredFunction) registeredCompositionFunctions.add(registeredFunction);
|
|
59526
|
+
});
|
|
59527
|
+
};
|
|
59528
|
+
const functionUsesRemotionRenderApi = (functionNode) => {
|
|
59529
|
+
let hasEvidence = false;
|
|
59530
|
+
walkAst(functionNode, (candidate) => {
|
|
59531
|
+
if (hasEvidence) return false;
|
|
59532
|
+
if (!isNodeOfType(candidate, "CallExpression") && !isNodeOfType(candidate, "JSXOpeningElement")) return;
|
|
59533
|
+
const apiBinding = resolveRemotionApi(isNodeOfType(candidate, "CallExpression") ? candidate.callee : candidate.name, scopes);
|
|
59534
|
+
if (isNodeOfType(candidate, "CallExpression") && apiBinding?.moduleSource === "remotion" && REMOTION_RENDER_CALL_NAMES.has(apiBinding.apiName) || isNodeOfType(candidate, "JSXOpeningElement") && apiBinding !== null && REMOTION_RENDER_COMPONENT_MODULE_BY_NAME.get(apiBinding.apiName) === apiBinding.moduleSource) {
|
|
59535
|
+
hasEvidence = true;
|
|
59536
|
+
return false;
|
|
59537
|
+
}
|
|
59538
|
+
});
|
|
59539
|
+
return hasEvidence;
|
|
59540
|
+
};
|
|
59541
|
+
return { functionHasEvidence: (functionNode) => {
|
|
59542
|
+
const cachedEvidence = evidenceByFunction.get(functionNode);
|
|
59543
|
+
if (cachedEvidence !== void 0) return cachedEvidence;
|
|
59544
|
+
collectRegisteredCompositionFunctions(functionNode);
|
|
59545
|
+
const hasEvidence = registeredCompositionFunctions.has(functionNode) || functionUsesRemotionRenderApi(functionNode) || isOwnedByRegisteredComposition(functionNode);
|
|
59546
|
+
evidenceByFunction.set(functionNode, hasEvidence);
|
|
59547
|
+
return hasEvidence;
|
|
59548
|
+
} };
|
|
59549
|
+
};
|
|
59550
|
+
//#endregion
|
|
59551
|
+
//#region src/plugin/rules/correctness/remotion-deterministic-randomness.ts
|
|
59552
|
+
const isGlobalMathObject = (node, scopes) => {
|
|
59553
|
+
const candidate = stripParenExpression(node);
|
|
59554
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate.name === "Math" && scopes.isGlobalReference(candidate);
|
|
59555
|
+
return Boolean(isNodeOfType(candidate, "MemberExpression") && getStaticPropertyKeyName(candidate, { allowComputedString: true }) === "Math" && isNodeOfType(candidate.object, "Identifier") && candidate.object.name === "globalThis" && scopes.isGlobalReference(candidate.object));
|
|
59556
|
+
};
|
|
59557
|
+
const remotionDeterministicRandomness = defineRule({
|
|
59558
|
+
id: "remotion-deterministic-randomness",
|
|
59559
|
+
title: "Randomness changes between rendered frames",
|
|
59560
|
+
tags: ["react-jsx-only"],
|
|
59561
|
+
requires: ["remotion:4"],
|
|
59562
|
+
severity: "error",
|
|
59563
|
+
recommendation: "Use Remotion's seeded `random(seed)` helper so the same frame produces the same value in every render tab.",
|
|
59564
|
+
create: (context) => {
|
|
59565
|
+
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59566
|
+
return { CallExpression(node) {
|
|
59567
|
+
const callee = stripParenExpression(node.callee);
|
|
59568
|
+
if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyKeyName(callee, { allowComputedString: true }) !== "random" || !isGlobalMathObject(callee.object, context.scopes)) return;
|
|
59569
|
+
const componentOrHook = findRenderPhaseComponentOrHook(node, context.scopes) ?? findEnclosingFunction$1(node);
|
|
59570
|
+
if (!componentOrHook || !renderEvidence.functionHasEvidence(componentOrHook)) return;
|
|
59571
|
+
const displayName = componentOrHookDisplayNameForFunction(componentOrHook);
|
|
59572
|
+
if (displayName && !isReactHookName(displayName) && !functionHasReactComponentEvidence(componentOrHook, context.scopes, context.cfg)) return;
|
|
59573
|
+
context.report({
|
|
59574
|
+
node,
|
|
59575
|
+
message: "`Math.random()` can return a different value in each parallel Remotion render tab, so the same frame is not deterministic. Use `random(seed)` from `remotion` instead."
|
|
59576
|
+
});
|
|
59577
|
+
} };
|
|
59578
|
+
}
|
|
59579
|
+
});
|
|
59580
|
+
//#endregion
|
|
59581
|
+
//#region src/plugin/utils/create-remotion-css-time-rule-visitors.ts
|
|
59582
|
+
const createRemotionCssTimeRuleVisitors = (context, options) => {
|
|
59583
|
+
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59584
|
+
return {
|
|
59585
|
+
Property(node) {
|
|
59586
|
+
if (!options.stylePropertyNames.has(getStaticPropertyKeyName(node) ?? "")) return;
|
|
59587
|
+
const styleObject = node.parent;
|
|
59588
|
+
const expressionContainer = styleObject?.parent;
|
|
59589
|
+
const styleAttribute = expressionContainer?.parent;
|
|
59590
|
+
if (!isNodeOfType(styleObject, "ObjectExpression") || !isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== styleObject || !isNodeOfType(styleAttribute, "JSXAttribute") || !isNodeOfType(styleAttribute.name, "JSXIdentifier") || styleAttribute.name.name !== "style") return;
|
|
59591
|
+
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59592
|
+
if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
|
|
59593
|
+
context.report({
|
|
59594
|
+
node,
|
|
59595
|
+
message: options.styleMessage
|
|
59596
|
+
});
|
|
59597
|
+
},
|
|
59598
|
+
JSXAttribute(node) {
|
|
59599
|
+
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "className") return;
|
|
59600
|
+
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59601
|
+
if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
|
|
59602
|
+
const className = getJsxAttributeStaticString(node);
|
|
59603
|
+
if (!className) return;
|
|
59604
|
+
if (className.split(/\s+/).filter(Boolean).some((classToken) => options.classTokenIsForbidden(classToken.split(":").at(-1) ?? ""))) context.report({
|
|
59605
|
+
node,
|
|
59606
|
+
message: options.classMessage
|
|
59607
|
+
});
|
|
59608
|
+
}
|
|
59609
|
+
};
|
|
59610
|
+
};
|
|
59611
|
+
//#endregion
|
|
59612
|
+
//#region src/plugin/rules/correctness/remotion-no-css-animation.ts
|
|
59613
|
+
const ANIMATION_STYLE_PROPERTY_NAMES = new Set(["animation", "animationName"]);
|
|
59614
|
+
const remotionNoCssAnimation = defineRule({
|
|
59615
|
+
id: "remotion-no-css-animation",
|
|
59616
|
+
title: "CSS animation is not frame-driven",
|
|
59617
|
+
tags: ["react-jsx-only"],
|
|
59618
|
+
requires: ["remotion:4"],
|
|
59619
|
+
severity: "error",
|
|
59620
|
+
recommendation: "Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",
|
|
59621
|
+
create: (context) => createRemotionCssTimeRuleVisitors(context, {
|
|
59622
|
+
classTokenIsForbidden: (classToken) => classToken.startsWith("animate-") && classToken !== "animate-none",
|
|
59623
|
+
classMessage: "Tailwind animations advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
|
|
59624
|
+
styleMessage: "CSS animations advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
|
|
59625
|
+
stylePropertyNames: ANIMATION_STYLE_PROPERTY_NAMES
|
|
59626
|
+
})
|
|
59627
|
+
});
|
|
59628
|
+
//#endregion
|
|
59629
|
+
//#region src/plugin/rules/correctness/remotion-no-css-transition.ts
|
|
59630
|
+
const TRANSITION_STYLE_PROPERTY_NAMES = new Set(["transition", "transitionProperty"]);
|
|
59631
|
+
const remotionNoCssTransition = defineRule({
|
|
59632
|
+
id: "remotion-no-css-transition",
|
|
59633
|
+
title: "CSS transition is not frame-driven",
|
|
59634
|
+
tags: ["react-jsx-only"],
|
|
59635
|
+
requires: ["remotion:4"],
|
|
59636
|
+
severity: "error",
|
|
59637
|
+
recommendation: "Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",
|
|
59638
|
+
create: (context) => createRemotionCssTimeRuleVisitors(context, {
|
|
59639
|
+
classTokenIsForbidden: (classToken) => (classToken === "transition" || classToken.startsWith("transition-")) && classToken !== "transition-none",
|
|
59640
|
+
classMessage: "Tailwind transitions advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
|
|
59641
|
+
styleMessage: "CSS transitions advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
|
|
59642
|
+
stylePropertyNames: TRANSITION_STYLE_PROPERTY_NAMES
|
|
59643
|
+
})
|
|
59644
|
+
});
|
|
59645
|
+
//#endregion
|
|
59646
|
+
//#region src/plugin/rules/correctness/remotion-no-css-url-assets.ts
|
|
59647
|
+
const CSS_URL_ASSET_PROPERTY_NAMES = new Set([
|
|
59648
|
+
"backgroundImage",
|
|
59649
|
+
"maskImage",
|
|
59650
|
+
"WebkitMaskImage"
|
|
59651
|
+
]);
|
|
59652
|
+
const CSS_URL_PATTERN = /\burl\(\s*(["']?)([^"')]+)\1\s*\)/i;
|
|
59653
|
+
const isEmbeddedAssetSource = (assetSource) => assetSource.startsWith("data:") || assetSource.startsWith("#");
|
|
59654
|
+
const getStaticStringExpression = (node) => {
|
|
59655
|
+
if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
|
|
59656
|
+
if (isNodeOfType(node, "TemplateLiteral") && node.expressions.length === 0 && node.quasis.length === 1) return node.quasis[0].value.raw;
|
|
59657
|
+
return null;
|
|
59658
|
+
};
|
|
59659
|
+
const isReactUseMemoCallback = (node, scopes) => {
|
|
59660
|
+
const parent = node.parent;
|
|
59661
|
+
return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments[0] === node && isReactApiCall(parent, "useMemo", scopes, { resolveNamedAliases: true }));
|
|
59662
|
+
};
|
|
59663
|
+
const componentPreloadsStaticImage = (componentNode, assetSource, scopes) => {
|
|
59664
|
+
let hasPreload = false;
|
|
59665
|
+
walkAst(componentNode, (child) => {
|
|
59666
|
+
if (hasPreload) return false;
|
|
59667
|
+
if (child !== componentNode && isFunctionLike$1(child) && !isReactUseMemoCallback(child, scopes)) return false;
|
|
59668
|
+
if (!isNodeOfType(child, "JSXOpeningElement")) return;
|
|
59669
|
+
const apiBinding = resolveRemotionApi(child.name, scopes);
|
|
59670
|
+
if (apiBinding?.apiName !== "Img" || apiBinding.moduleSource !== "remotion") return;
|
|
59671
|
+
const sourceAttribute = findJsxAttribute(child.attributes, "src");
|
|
59672
|
+
if (sourceAttribute && getJsxAttributeStaticString(sourceAttribute) === assetSource) {
|
|
59673
|
+
hasPreload = true;
|
|
59674
|
+
return false;
|
|
59675
|
+
}
|
|
59676
|
+
});
|
|
59677
|
+
return hasPreload;
|
|
59678
|
+
};
|
|
59679
|
+
const remotionNoCssUrlAssets = defineRule({
|
|
59680
|
+
id: "remotion-no-css-url-assets",
|
|
59681
|
+
title: "CSS URL asset can flicker in Remotion",
|
|
59682
|
+
tags: ["react-jsx-only"],
|
|
59683
|
+
requires: ["remotion:4"],
|
|
59684
|
+
severity: "error",
|
|
59685
|
+
recommendation: "Render the asset with `Img` inside an `AbsoluteFill`, or preload the same source with a hidden `Img` when a CSS mask is required.",
|
|
59686
|
+
create: (context) => {
|
|
59687
|
+
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59688
|
+
return { Property(node) {
|
|
59689
|
+
if (!CSS_URL_ASSET_PROPERTY_NAMES.has(getStaticPropertyKeyName(node) ?? "")) return;
|
|
59690
|
+
const styleObject = node.parent;
|
|
59691
|
+
const expressionContainer = styleObject?.parent;
|
|
59692
|
+
const styleAttribute = expressionContainer?.parent;
|
|
59693
|
+
if (!isNodeOfType(styleObject, "ObjectExpression") || !isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== styleObject || !isNodeOfType(styleAttribute, "JSXAttribute") || !isNodeOfType(styleAttribute.name, "JSXIdentifier") || styleAttribute.name.name !== "style") return;
|
|
59694
|
+
const cssValue = getStaticStringExpression(node.value);
|
|
59695
|
+
const urlMatch = cssValue ? CSS_URL_PATTERN.exec(cssValue) : null;
|
|
59696
|
+
if (!urlMatch) return;
|
|
59697
|
+
const assetSource = urlMatch[2].trim();
|
|
59698
|
+
if (isEmbeddedAssetSource(assetSource)) return;
|
|
59699
|
+
const componentNode = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59700
|
+
if (!componentNode || !renderEvidence.functionHasEvidence(componentNode) || componentPreloadsStaticImage(componentNode, assetSource, context.scopes)) return;
|
|
59701
|
+
context.report({
|
|
59702
|
+
node,
|
|
59703
|
+
message: "Remotion cannot detect when a CSS `url()` asset has loaded, so the rendered frame can flicker. Render or preload the source with <Img> instead."
|
|
59704
|
+
});
|
|
59705
|
+
} };
|
|
59706
|
+
}
|
|
59707
|
+
});
|
|
59708
|
+
//#endregion
|
|
59709
|
+
//#region src/plugin/rules/correctness/remotion-no-module-scope-delay-render.ts
|
|
59710
|
+
const remotionNoModuleScopeDelayRender = defineRule({
|
|
59711
|
+
id: "remotion-no-module-scope-delay-render",
|
|
59712
|
+
title: "Module-scoped delayRender blocks every composition",
|
|
59713
|
+
requires: ["remotion:4"],
|
|
59714
|
+
severity: "error",
|
|
59715
|
+
recommendation: "Create the handle once inside the component. Use `useDelayRender()` on Remotion 4.0.342 or newer, or lazy `useState(() => delayRender())` on earlier versions.",
|
|
59716
|
+
create: (context) => ({ CallExpression(node) {
|
|
59717
|
+
const apiBinding = resolveRemotionApi(node.callee, context.scopes);
|
|
59718
|
+
if (apiBinding?.apiName !== "delayRender" || apiBinding.moduleSource !== "remotion" || findEnclosingFunction$1(node)) return;
|
|
59719
|
+
context.report({
|
|
59720
|
+
node,
|
|
59721
|
+
message: "A module-scoped `delayRender()` handle blocks all compositions and composition discovery. Move it inside the component and create it once with `useDelayRender()` or a lazy `useState` initializer."
|
|
59722
|
+
});
|
|
59723
|
+
} })
|
|
59724
|
+
});
|
|
59725
|
+
//#endregion
|
|
59726
|
+
//#region src/plugin/rules/correctness/remotion-no-native-media-elements.ts
|
|
59727
|
+
const REMOTION_MEDIA_REPLACEMENT_BY_TAG = new Map([
|
|
59728
|
+
["audio", "`Audio` from `@remotion/media`"],
|
|
59729
|
+
["iframe", "`IFrame` from `remotion`"],
|
|
59730
|
+
["img", "`Img` from `remotion`"],
|
|
59731
|
+
["video", "`Video` from `@remotion/media`"]
|
|
59732
|
+
]);
|
|
59733
|
+
const remotionNoNativeMediaElements = defineRule({
|
|
59734
|
+
id: "remotion-no-native-media-elements",
|
|
59735
|
+
title: "Native media element bypasses Remotion loading",
|
|
59736
|
+
tags: ["react-jsx-only"],
|
|
59737
|
+
requires: ["remotion:4"],
|
|
59738
|
+
severity: "error",
|
|
59739
|
+
recommendation: "Use Remotion's media components so rendering waits for assets and seeks media to the requested frame.",
|
|
59740
|
+
create: (context) => {
|
|
59741
|
+
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59742
|
+
return { JSXOpeningElement(node) {
|
|
59743
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
59744
|
+
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59745
|
+
if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
|
|
59746
|
+
const replacement = REMOTION_MEDIA_REPLACEMENT_BY_TAG.get(node.name.name);
|
|
59747
|
+
if (!replacement) return;
|
|
59748
|
+
context.report({
|
|
59749
|
+
node,
|
|
59750
|
+
message: `Native <${node.name.name}> does not let Remotion reliably wait for and synchronize the asset. Use ${replacement} instead.`
|
|
59751
|
+
});
|
|
59752
|
+
} };
|
|
59753
|
+
}
|
|
59754
|
+
});
|
|
59755
|
+
//#endregion
|
|
59756
|
+
//#region src/plugin/rules/correctness/remotion-no-next-image.ts
|
|
59757
|
+
const remotionNoNextImage = defineRule({
|
|
59758
|
+
id: "remotion-no-next-image",
|
|
59759
|
+
title: "Next.js Image can flicker in Remotion",
|
|
59760
|
+
tags: ["react-jsx-only"],
|
|
59761
|
+
requires: ["remotion:4"],
|
|
59762
|
+
severity: "error",
|
|
59763
|
+
recommendation: "Use `Img` from `remotion`, which delays rendering until the image is loaded.",
|
|
59764
|
+
create: (context) => {
|
|
59765
|
+
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59766
|
+
return { JSXOpeningElement(node) {
|
|
59767
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
59768
|
+
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59769
|
+
if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
|
|
59770
|
+
const symbol = context.scopes.symbolFor(node.name);
|
|
59771
|
+
if (symbol?.kind !== "import") return;
|
|
59772
|
+
const importBinding = getImportBindingForName(node.name, symbol.name);
|
|
59773
|
+
if (importBinding?.source !== "next/image" || importBinding.exportedName !== "default") return;
|
|
59774
|
+
context.report({
|
|
59775
|
+
node,
|
|
59776
|
+
message: "Next.js <Image> does not expose a reliable loading signal to Remotion, so rendered frames can flicker. Use <Img> from `remotion` instead."
|
|
59777
|
+
});
|
|
59778
|
+
} };
|
|
59779
|
+
}
|
|
59780
|
+
});
|
|
59781
|
+
//#endregion
|
|
59782
|
+
//#region src/plugin/rules/correctness/remotion-stable-delay-render-handle.ts
|
|
59783
|
+
const isUseStateLazyInitializer = (node, scopes) => {
|
|
59784
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
59785
|
+
if (!enclosingFunction) return false;
|
|
59786
|
+
const parent = enclosingFunction.parent;
|
|
59787
|
+
return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments[0] === enclosingFunction && isReactApiCall(parent, "useState", scopes, { resolveNamedAliases: true }));
|
|
59788
|
+
};
|
|
59789
|
+
const remotionStableDelayRenderHandle = defineRule({
|
|
59790
|
+
id: "remotion-stable-delay-render-handle",
|
|
59791
|
+
title: "delayRender handle is recreated during render",
|
|
59792
|
+
tags: ["react-jsx-only"],
|
|
59793
|
+
requires: ["remotion:4"],
|
|
59794
|
+
severity: "error",
|
|
59795
|
+
recommendation: "Prefer `useDelayRender()`, or initialize `delayRender()` once with `useState(() => delayRender())`.",
|
|
59796
|
+
create: (context) => ({ CallExpression(node) {
|
|
59797
|
+
const apiBinding = resolveRemotionApi(node.callee, context.scopes);
|
|
59798
|
+
if (apiBinding?.apiName !== "delayRender" || apiBinding.moduleSource !== "remotion" || !findRenderPhaseComponentOrHook(node, context.scopes) || isUseStateLazyInitializer(node, context.scopes)) return;
|
|
59799
|
+
context.report({
|
|
59800
|
+
node,
|
|
59801
|
+
message: "Calling `delayRender()` during every component render creates another outstanding handle and can make rendering time out. Use `useDelayRender()` or a lazy `useState` initializer."
|
|
59802
|
+
});
|
|
59803
|
+
} })
|
|
59804
|
+
});
|
|
59805
|
+
//#endregion
|
|
58742
59806
|
//#region src/plugin/rules/performance/rendering-animate-svg-wrapper.ts
|
|
58743
59807
|
const renderingAnimateSvgWrapper = defineRule({
|
|
58744
59808
|
id: "rendering-animate-svg-wrapper",
|
|
@@ -73902,6 +74966,39 @@ const reactDoctorRules = [
|
|
|
73902
74966
|
requires: [...new Set(["react", ...mediaHasCaption.requires ?? []])]
|
|
73903
74967
|
}
|
|
73904
74968
|
},
|
|
74969
|
+
{
|
|
74970
|
+
key: "react-doctor/mobx-no-make-auto-observable-in-inheritance",
|
|
74971
|
+
id: "mobx-no-make-auto-observable-in-inheritance",
|
|
74972
|
+
source: "react-doctor",
|
|
74973
|
+
originallyExternal: false,
|
|
74974
|
+
rule: {
|
|
74975
|
+
...mobxNoMakeAutoObservableInInheritance,
|
|
74976
|
+
framework: "global",
|
|
74977
|
+
category: "Bugs"
|
|
74978
|
+
}
|
|
74979
|
+
},
|
|
74980
|
+
{
|
|
74981
|
+
key: "react-doctor/mobx-no-observer-wrapped-memo",
|
|
74982
|
+
id: "mobx-no-observer-wrapped-memo",
|
|
74983
|
+
source: "react-doctor",
|
|
74984
|
+
originallyExternal: false,
|
|
74985
|
+
rule: {
|
|
74986
|
+
...mobxNoObserverWrappedMemo,
|
|
74987
|
+
framework: "global",
|
|
74988
|
+
category: "Bugs"
|
|
74989
|
+
}
|
|
74990
|
+
},
|
|
74991
|
+
{
|
|
74992
|
+
key: "react-doctor/mobx-reaction-disposer-discarded",
|
|
74993
|
+
id: "mobx-reaction-disposer-discarded",
|
|
74994
|
+
source: "react-doctor",
|
|
74995
|
+
originallyExternal: false,
|
|
74996
|
+
rule: {
|
|
74997
|
+
...mobxReactionDisposerDiscarded,
|
|
74998
|
+
framework: "global",
|
|
74999
|
+
category: "Bugs"
|
|
75000
|
+
}
|
|
75001
|
+
},
|
|
73905
75002
|
{
|
|
73906
75003
|
key: "react-doctor/mouse-events-have-key-events",
|
|
73907
75004
|
id: "mouse-events-have-key-events",
|
|
@@ -76084,6 +77181,105 @@ const reactDoctorRules = [
|
|
|
76084
77181
|
requires: [...new Set(["react", ...reduxUseselectorReturnsNewCollection.requires ?? []])]
|
|
76085
77182
|
}
|
|
76086
77183
|
},
|
|
77184
|
+
{
|
|
77185
|
+
key: "react-doctor/remotion-calculate-metadata-fetch-signal",
|
|
77186
|
+
id: "remotion-calculate-metadata-fetch-signal",
|
|
77187
|
+
source: "react-doctor",
|
|
77188
|
+
originallyExternal: false,
|
|
77189
|
+
rule: {
|
|
77190
|
+
...remotionCalculateMetadataFetchSignal,
|
|
77191
|
+
framework: "global",
|
|
77192
|
+
category: "Bugs"
|
|
77193
|
+
}
|
|
77194
|
+
},
|
|
77195
|
+
{
|
|
77196
|
+
key: "react-doctor/remotion-deterministic-randomness",
|
|
77197
|
+
id: "remotion-deterministic-randomness",
|
|
77198
|
+
source: "react-doctor",
|
|
77199
|
+
originallyExternal: false,
|
|
77200
|
+
rule: {
|
|
77201
|
+
...remotionDeterministicRandomness,
|
|
77202
|
+
framework: "global",
|
|
77203
|
+
category: "Bugs"
|
|
77204
|
+
}
|
|
77205
|
+
},
|
|
77206
|
+
{
|
|
77207
|
+
key: "react-doctor/remotion-no-css-animation",
|
|
77208
|
+
id: "remotion-no-css-animation",
|
|
77209
|
+
source: "react-doctor",
|
|
77210
|
+
originallyExternal: false,
|
|
77211
|
+
rule: {
|
|
77212
|
+
...remotionNoCssAnimation,
|
|
77213
|
+
framework: "global",
|
|
77214
|
+
category: "Bugs"
|
|
77215
|
+
}
|
|
77216
|
+
},
|
|
77217
|
+
{
|
|
77218
|
+
key: "react-doctor/remotion-no-css-transition",
|
|
77219
|
+
id: "remotion-no-css-transition",
|
|
77220
|
+
source: "react-doctor",
|
|
77221
|
+
originallyExternal: false,
|
|
77222
|
+
rule: {
|
|
77223
|
+
...remotionNoCssTransition,
|
|
77224
|
+
framework: "global",
|
|
77225
|
+
category: "Bugs"
|
|
77226
|
+
}
|
|
77227
|
+
},
|
|
77228
|
+
{
|
|
77229
|
+
key: "react-doctor/remotion-no-css-url-assets",
|
|
77230
|
+
id: "remotion-no-css-url-assets",
|
|
77231
|
+
source: "react-doctor",
|
|
77232
|
+
originallyExternal: false,
|
|
77233
|
+
rule: {
|
|
77234
|
+
...remotionNoCssUrlAssets,
|
|
77235
|
+
framework: "global",
|
|
77236
|
+
category: "Bugs"
|
|
77237
|
+
}
|
|
77238
|
+
},
|
|
77239
|
+
{
|
|
77240
|
+
key: "react-doctor/remotion-no-module-scope-delay-render",
|
|
77241
|
+
id: "remotion-no-module-scope-delay-render",
|
|
77242
|
+
source: "react-doctor",
|
|
77243
|
+
originallyExternal: false,
|
|
77244
|
+
rule: {
|
|
77245
|
+
...remotionNoModuleScopeDelayRender,
|
|
77246
|
+
framework: "global",
|
|
77247
|
+
category: "Bugs"
|
|
77248
|
+
}
|
|
77249
|
+
},
|
|
77250
|
+
{
|
|
77251
|
+
key: "react-doctor/remotion-no-native-media-elements",
|
|
77252
|
+
id: "remotion-no-native-media-elements",
|
|
77253
|
+
source: "react-doctor",
|
|
77254
|
+
originallyExternal: false,
|
|
77255
|
+
rule: {
|
|
77256
|
+
...remotionNoNativeMediaElements,
|
|
77257
|
+
framework: "global",
|
|
77258
|
+
category: "Bugs"
|
|
77259
|
+
}
|
|
77260
|
+
},
|
|
77261
|
+
{
|
|
77262
|
+
key: "react-doctor/remotion-no-next-image",
|
|
77263
|
+
id: "remotion-no-next-image",
|
|
77264
|
+
source: "react-doctor",
|
|
77265
|
+
originallyExternal: false,
|
|
77266
|
+
rule: {
|
|
77267
|
+
...remotionNoNextImage,
|
|
77268
|
+
framework: "global",
|
|
77269
|
+
category: "Bugs"
|
|
77270
|
+
}
|
|
77271
|
+
},
|
|
77272
|
+
{
|
|
77273
|
+
key: "react-doctor/remotion-stable-delay-render-handle",
|
|
77274
|
+
id: "remotion-stable-delay-render-handle",
|
|
77275
|
+
source: "react-doctor",
|
|
77276
|
+
originallyExternal: false,
|
|
77277
|
+
rule: {
|
|
77278
|
+
...remotionStableDelayRenderHandle,
|
|
77279
|
+
framework: "global",
|
|
77280
|
+
category: "Bugs"
|
|
77281
|
+
}
|
|
77282
|
+
},
|
|
76087
77283
|
{
|
|
76088
77284
|
key: "react-doctor/rendering-animate-svg-wrapper",
|
|
76089
77285
|
id: "rendering-animate-svg-wrapper",
|
|
@@ -77567,6 +78763,13 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
77567
78763
|
"no-unguarded-browser-global-in-render-or-hook-init",
|
|
77568
78764
|
"prefer-dynamic-import",
|
|
77569
78765
|
"rendering-hydration-mismatch-time",
|
|
78766
|
+
"remotion-calculate-metadata-fetch-signal",
|
|
78767
|
+
"remotion-deterministic-randomness",
|
|
78768
|
+
"remotion-no-css-animation",
|
|
78769
|
+
"remotion-no-css-transition",
|
|
78770
|
+
"remotion-no-css-url-assets",
|
|
78771
|
+
"remotion-no-native-media-elements",
|
|
78772
|
+
"remotion-no-next-image",
|
|
77570
78773
|
"rerender-memo-with-default-value",
|
|
77571
78774
|
"rn-no-legacy-shadow-styles",
|
|
77572
78775
|
"rn-no-raw-text",
|
|
@@ -77784,7 +78987,17 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
|
|
|
77784
78987
|
* `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
|
|
77785
78988
|
* partition), forcing a conscious classification.
|
|
77786
78989
|
*/
|
|
77787
|
-
const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set([
|
|
78990
|
+
const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set([
|
|
78991
|
+
"nextjs-no-img-element",
|
|
78992
|
+
"only-export-components",
|
|
78993
|
+
"remotion-calculate-metadata-fetch-signal",
|
|
78994
|
+
"remotion-deterministic-randomness",
|
|
78995
|
+
"remotion-no-css-animation",
|
|
78996
|
+
"remotion-no-css-transition",
|
|
78997
|
+
"remotion-no-css-url-assets",
|
|
78998
|
+
"remotion-no-native-media-elements",
|
|
78999
|
+
"remotion-no-next-image"
|
|
79000
|
+
]);
|
|
77788
79001
|
/**
|
|
77789
79002
|
* Runs the collectors for `ruleIds` over one file and returns every
|
|
77790
79003
|
* filesystem probe they made — the file's cross-file dependency set.
|