oxlint-plugin-react-doctor 0.8.1-dev.6fcd9c9 → 0.8.1-dev.7c9dbed
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 +4 -786
- package/dist/index.js +351 -2719
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1953,13 +1953,13 @@ const getImportDeclaration = (node) => {
|
|
|
1953
1953
|
}
|
|
1954
1954
|
return null;
|
|
1955
1955
|
};
|
|
1956
|
-
const getImportSource
|
|
1956
|
+
const getImportSource = (declaration) => typeof declaration.source.value === "string" ? declaration.source.value : null;
|
|
1957
1957
|
const isNamedImport = (symbol, importedName, moduleSources) => {
|
|
1958
1958
|
if (symbol.kind !== "import") return false;
|
|
1959
1959
|
const declaration = symbol.declarationNode;
|
|
1960
1960
|
if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
|
|
1961
1961
|
const importDeclaration = getImportDeclaration(declaration);
|
|
1962
|
-
const source = importDeclaration ? getImportSource
|
|
1962
|
+
const source = importDeclaration ? getImportSource(importDeclaration) : null;
|
|
1963
1963
|
if (!source || !moduleSources.has(source)) return false;
|
|
1964
1964
|
const imported = declaration.imported;
|
|
1965
1965
|
return isNodeOfType(imported, "Identifier") && imported.name === importedName || isNodeOfType(imported, "Literal") && imported.value === importedName;
|
|
@@ -1968,7 +1968,7 @@ const isSatoriImport = (symbol) => {
|
|
|
1968
1968
|
if (symbol.kind !== "import") return false;
|
|
1969
1969
|
const declaration = symbol.declarationNode;
|
|
1970
1970
|
const importDeclaration = getImportDeclaration(declaration);
|
|
1971
|
-
if (!importDeclaration || getImportSource
|
|
1971
|
+
if (!importDeclaration || getImportSource(importDeclaration) !== "satori") return false;
|
|
1972
1972
|
if (isNodeOfType(declaration, "ImportDefaultSpecifier")) return true;
|
|
1973
1973
|
if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
|
|
1974
1974
|
const imported = declaration.imported;
|
|
@@ -1989,7 +1989,7 @@ const isGeneratedImageRendererCall = (node, scopes) => {
|
|
|
1989
1989
|
const declaration = symbol.declarationNode;
|
|
1990
1990
|
if (!isNodeOfType(declaration, "ImportNamespaceSpecifier")) return false;
|
|
1991
1991
|
const importDeclaration = getImportDeclaration(declaration);
|
|
1992
|
-
const source = importDeclaration ? getImportSource
|
|
1992
|
+
const source = importDeclaration ? getImportSource(importDeclaration) : null;
|
|
1993
1993
|
return Boolean(source && IMAGE_RESPONSE_MODULES.has(source));
|
|
1994
1994
|
};
|
|
1995
1995
|
//#endregion
|
|
@@ -3173,7 +3173,7 @@ const getJsxPropStaticStringValues = (attribute, scopes) => getJsxPropStaticStri
|
|
|
3173
3173
|
const getJsxPropExhaustiveStaticStringValues = (attribute, scopes) => getJsxPropStaticStringValuesWithMode(attribute, scopes, null, true);
|
|
3174
3174
|
//#endregion
|
|
3175
3175
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
3176
|
-
const MESSAGE$
|
|
3176
|
+
const MESSAGE$62 = "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$62
|
|
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$61 = "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$61
|
|
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$61
|
|
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$60 = "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$60
|
|
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$60
|
|
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$59 = "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$59,
|
|
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$59
|
|
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$58 = "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$58
|
|
8169
8169
|
});
|
|
8170
8170
|
} };
|
|
8171
8171
|
}
|
|
@@ -10243,19 +10243,21 @@ const isReactComponentName = (name) => {
|
|
|
10243
10243
|
return firstCharacter >= 65 && firstCharacter <= 90;
|
|
10244
10244
|
};
|
|
10245
10245
|
//#endregion
|
|
10246
|
-
//#region src/plugin/utils/get-
|
|
10247
|
-
const
|
|
10248
|
-
if (!isNodeOfType(
|
|
10249
|
-
if (
|
|
10250
|
-
if (
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10246
|
+
//#region src/plugin/rules/react-ui/utils/get-class-name-literal.ts
|
|
10247
|
+
const getClassNameLiteral = (classAttribute) => {
|
|
10248
|
+
if (!isNodeOfType(classAttribute, "JSXAttribute")) return null;
|
|
10249
|
+
if (!classAttribute.value) return null;
|
|
10250
|
+
if (isNodeOfType(classAttribute.value, "Literal") && typeof classAttribute.value.value === "string") return classAttribute.value.value;
|
|
10251
|
+
if (isNodeOfType(classAttribute.value, "JSXExpressionContainer")) {
|
|
10252
|
+
const expression = classAttribute.value.expression;
|
|
10253
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
|
|
10254
|
+
if (isNodeOfType(expression, "TemplateLiteral") && expression.quasis?.length === 1) return expression.quasis[0].value?.raw ?? null;
|
|
10255
|
+
}
|
|
10254
10256
|
return null;
|
|
10255
10257
|
};
|
|
10256
10258
|
//#endregion
|
|
10257
10259
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
10258
|
-
const MESSAGE$
|
|
10260
|
+
const MESSAGE$57 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
10259
10261
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
10260
10262
|
"td",
|
|
10261
10263
|
"th",
|
|
@@ -10313,7 +10315,7 @@ const collectStaticTemplateClassTokens = (templateLiteral) => {
|
|
|
10313
10315
|
const hasDisplayNoneClass = (opening) => {
|
|
10314
10316
|
const classAttribute = hasJsxPropIgnoreCase(opening.attributes, "className") ?? hasJsxPropIgnoreCase(opening.attributes, "class");
|
|
10315
10317
|
if (!classAttribute) return false;
|
|
10316
|
-
const literalValue =
|
|
10318
|
+
const literalValue = getClassNameLiteral(classAttribute);
|
|
10317
10319
|
if (literalValue !== null) return literalValue.split(/\s+/).some(isDisplayNoneClassToken);
|
|
10318
10320
|
if (classAttribute.value && isNodeOfType(classAttribute.value, "JSXExpressionContainer") && isNodeOfType(classAttribute.value.expression, "TemplateLiteral")) return collectStaticTemplateClassTokens(classAttribute.value.expression).some(isDisplayNoneClassToken);
|
|
10319
10321
|
return false;
|
|
@@ -10785,7 +10787,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
10785
10787
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
10786
10788
|
context.report({
|
|
10787
10789
|
node: candidate.opening,
|
|
10788
|
-
message: MESSAGE$
|
|
10790
|
+
message: MESSAGE$57
|
|
10789
10791
|
});
|
|
10790
10792
|
}
|
|
10791
10793
|
}
|
|
@@ -12429,7 +12431,7 @@ const noRedundantPaddingAxes = defineRule({
|
|
|
12429
12431
|
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.",
|
|
12430
12432
|
create: (context) => ({ JSXAttribute(jsxAttribute) {
|
|
12431
12433
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
12432
|
-
const classNameLiteral =
|
|
12434
|
+
const classNameLiteral = getClassNameLiteral(jsxAttribute);
|
|
12433
12435
|
if (!classNameLiteral) return;
|
|
12434
12436
|
if (!classNameLiteral.includes("px-") || !classNameLiteral.includes("py-")) return;
|
|
12435
12437
|
if (hasResponsivePrefix(classNameLiteral, "px") || hasResponsivePrefix(classNameLiteral, "py")) return;
|
|
@@ -12457,7 +12459,7 @@ const noRedundantSizeAxes = defineRule({
|
|
|
12457
12459
|
return { JSXAttribute(jsxAttribute) {
|
|
12458
12460
|
if (didReportInFile) return;
|
|
12459
12461
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
12460
|
-
const classNameLiteral =
|
|
12462
|
+
const classNameLiteral = getClassNameLiteral(jsxAttribute);
|
|
12461
12463
|
if (!classNameLiteral) return;
|
|
12462
12464
|
if (!classNameLiteral.includes("w-") || !classNameLiteral.includes("h-")) return;
|
|
12463
12465
|
if (hasResponsivePrefix(classNameLiteral, "w") || hasResponsivePrefix(classNameLiteral, "h")) return;
|
|
@@ -12485,7 +12487,7 @@ const noSpaceOnFlexChildren = defineRule({
|
|
|
12485
12487
|
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.",
|
|
12486
12488
|
create: (context) => ({ JSXAttribute(jsxAttribute) {
|
|
12487
12489
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
12488
|
-
const classNameLiteral =
|
|
12490
|
+
const classNameLiteral = getClassNameLiteral(jsxAttribute);
|
|
12489
12491
|
if (!classNameLiteral) return;
|
|
12490
12492
|
if (!classNameLiteral.includes("space-")) return;
|
|
12491
12493
|
const tokens = tokenizeClassName(classNameLiteral);
|
|
@@ -12592,7 +12594,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
12592
12594
|
});
|
|
12593
12595
|
//#endregion
|
|
12594
12596
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
12595
|
-
const MESSAGE$
|
|
12597
|
+
const MESSAGE$56 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
12596
12598
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
12597
12599
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
12598
12600
|
"aria-label",
|
|
@@ -12617,7 +12619,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
12617
12619
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
12618
12620
|
context.report({
|
|
12619
12621
|
node: node.name,
|
|
12620
|
-
message: MESSAGE$
|
|
12622
|
+
message: MESSAGE$56
|
|
12621
12623
|
});
|
|
12622
12624
|
} };
|
|
12623
12625
|
}
|
|
@@ -12657,7 +12659,7 @@ const isEs6Component = (node) => {
|
|
|
12657
12659
|
};
|
|
12658
12660
|
//#endregion
|
|
12659
12661
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
12660
|
-
const MESSAGE$
|
|
12662
|
+
const MESSAGE$55 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
12661
12663
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
12662
12664
|
"observer",
|
|
12663
12665
|
"lazy",
|
|
@@ -12850,7 +12852,7 @@ const displayName = defineRule({
|
|
|
12850
12852
|
const reportAt = (node) => {
|
|
12851
12853
|
context.report({
|
|
12852
12854
|
node,
|
|
12853
|
-
message: MESSAGE$
|
|
12855
|
+
message: MESSAGE$55
|
|
12854
12856
|
});
|
|
12855
12857
|
};
|
|
12856
12858
|
return {
|
|
@@ -13801,18 +13803,17 @@ const isConstAliasOfGlobalArrayFrom = (node, scopes) => {
|
|
|
13801
13803
|
const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
|
|
13802
13804
|
return Boolean(declarator && scopes.symbolFor(unwrappedNode)?.declarationNode === declarator && declarator.init && declarator.parent && isNodeOfType(declarator.parent, "VariableDeclaration") && declarator.parent.kind === "const" && isGlobalArrayFromMember(declarator.init, scopes));
|
|
13803
13805
|
};
|
|
13804
|
-
const executesDuringRender = (
|
|
13805
|
-
const
|
|
13806
|
-
const parent = callbackRoot.parent;
|
|
13806
|
+
const executesDuringRender = (functionNode, scopes) => {
|
|
13807
|
+
const parent = functionNode.parent;
|
|
13807
13808
|
if (isNodeOfType(parent, "NewExpression")) {
|
|
13808
13809
|
const callee = stripParenExpression(parent.callee);
|
|
13809
|
-
return Boolean(scopes && parent.arguments?.[0] ===
|
|
13810
|
+
return Boolean(scopes && parent.arguments?.[0] === functionNode && isNodeOfType(callee, "Identifier") && callee.name === "Promise" && scopes.isGlobalReference(callee));
|
|
13810
13811
|
}
|
|
13811
13812
|
if (!isNodeOfType(parent, "CallExpression")) return false;
|
|
13812
|
-
if (parent.callee ===
|
|
13813
|
-
if ((scopes ? isReactApiCall(parent, REACT_RENDER_PHASE_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }) : isHookCall$2(parent, REACT_RENDER_PHASE_HOOK_NAMES)) && parent.arguments?.[0] ===
|
|
13814
|
-
if (scopes && parent.arguments?.[1] ===
|
|
13815
|
-
return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(parent.callee.property.name) && parent.arguments?.[0] ===
|
|
13813
|
+
if (parent.callee === functionNode) return true;
|
|
13814
|
+
if ((scopes ? isReactApiCall(parent, REACT_RENDER_PHASE_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }) : isHookCall$2(parent, REACT_RENDER_PHASE_HOOK_NAMES)) && parent.arguments?.[0] === functionNode) return true;
|
|
13815
|
+
if (scopes && parent.arguments?.[1] === functionNode && (isGlobalArrayFromMember(parent.callee, scopes) || isConstAliasOfGlobalArrayFrom(parent.callee, scopes))) return true;
|
|
13816
|
+
return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(parent.callee.property.name) && parent.arguments?.[0] === functionNode;
|
|
13816
13817
|
};
|
|
13817
13818
|
//#endregion
|
|
13818
13819
|
//#region src/plugin/utils/find-render-phase-component-or-hook.ts
|
|
@@ -13939,10 +13940,9 @@ const doNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context, o
|
|
|
13939
13940
|
//#region src/plugin/utils/get-function-binding-name.ts
|
|
13940
13941
|
const getFunctionBindingIdentifier$1 = (functionNode) => {
|
|
13941
13942
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
13942
|
-
const
|
|
13943
|
-
const parent = functionRoot.parent;
|
|
13943
|
+
const parent = functionNode.parent;
|
|
13944
13944
|
if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
13945
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.right ===
|
|
13945
|
+
if (isNodeOfType(parent, "AssignmentExpression") && parent.right === functionNode && isNodeOfType(parent.left, "Identifier")) return parent.left;
|
|
13946
13946
|
if (isNodeOfType(parent, "CallExpression")) {
|
|
13947
13947
|
const callParent = parent.parent;
|
|
13948
13948
|
if (isNodeOfType(callParent, "VariableDeclarator") && isNodeOfType(callParent.id, "Identifier")) return callParent.id;
|
|
@@ -19264,7 +19264,7 @@ const forbidElements = defineRule({
|
|
|
19264
19264
|
});
|
|
19265
19265
|
//#endregion
|
|
19266
19266
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
19267
|
-
const MESSAGE$
|
|
19267
|
+
const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
19268
19268
|
const forwardRefUsesRef = defineRule({
|
|
19269
19269
|
id: "forward-ref-uses-ref",
|
|
19270
19270
|
title: "forwardRef without ref parameter",
|
|
@@ -19287,7 +19287,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
19287
19287
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
19288
19288
|
context.report({
|
|
19289
19289
|
node: inner,
|
|
19290
|
-
message: MESSAGE$
|
|
19290
|
+
message: MESSAGE$54
|
|
19291
19291
|
});
|
|
19292
19292
|
} })
|
|
19293
19293
|
});
|
|
@@ -19328,7 +19328,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
19328
19328
|
});
|
|
19329
19329
|
//#endregion
|
|
19330
19330
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
19331
|
-
const MESSAGE$
|
|
19331
|
+
const MESSAGE$53 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
19332
19332
|
const DEFAULT_HEADING_TAGS = [
|
|
19333
19333
|
"h1",
|
|
19334
19334
|
"h2",
|
|
@@ -19362,7 +19362,7 @@ const headingHasContent = defineRule({
|
|
|
19362
19362
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
19363
19363
|
context.report({
|
|
19364
19364
|
node,
|
|
19365
|
-
message: MESSAGE$
|
|
19365
|
+
message: MESSAGE$53
|
|
19366
19366
|
});
|
|
19367
19367
|
} };
|
|
19368
19368
|
}
|
|
@@ -19530,7 +19530,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19530
19530
|
});
|
|
19531
19531
|
//#endregion
|
|
19532
19532
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
19533
|
-
const MESSAGE$
|
|
19533
|
+
const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
19534
19534
|
const resolveSettings$38 = (settings) => {
|
|
19535
19535
|
const reactDoctor = settings?.["react-doctor"];
|
|
19536
19536
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -19577,13 +19577,13 @@ const htmlHasLang = defineRule({
|
|
|
19577
19577
|
if (!lang) {
|
|
19578
19578
|
context.report({
|
|
19579
19579
|
node: node.name,
|
|
19580
|
-
message: MESSAGE$
|
|
19580
|
+
message: MESSAGE$52
|
|
19581
19581
|
});
|
|
19582
19582
|
return;
|
|
19583
19583
|
}
|
|
19584
19584
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
19585
19585
|
node: lang,
|
|
19586
|
-
message: MESSAGE$
|
|
19586
|
+
message: MESSAGE$52
|
|
19587
19587
|
});
|
|
19588
19588
|
} };
|
|
19589
19589
|
}
|
|
@@ -19856,7 +19856,7 @@ const isJsxFragmentElement = (node, scopes) => {
|
|
|
19856
19856
|
};
|
|
19857
19857
|
//#endregion
|
|
19858
19858
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
19859
|
-
const MESSAGE$
|
|
19859
|
+
const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
19860
19860
|
const isStaticallyAriaHidden = (openingElement) => {
|
|
19861
19861
|
const ariaHiddenAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "aria-hidden", false);
|
|
19862
19862
|
if (!ariaHiddenAttribute) return false;
|
|
@@ -19945,14 +19945,14 @@ const iframeHasTitle = defineRule({
|
|
|
19945
19945
|
if (!titleAttr) {
|
|
19946
19946
|
if (hasSpread || tag === "iframe") context.report({
|
|
19947
19947
|
node: node.name,
|
|
19948
|
-
message: MESSAGE$
|
|
19948
|
+
message: MESSAGE$51
|
|
19949
19949
|
});
|
|
19950
19950
|
return;
|
|
19951
19951
|
}
|
|
19952
19952
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
19953
19953
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
19954
19954
|
node: titleAttr,
|
|
19955
|
-
message: MESSAGE$
|
|
19955
|
+
message: MESSAGE$51
|
|
19956
19956
|
});
|
|
19957
19957
|
} })
|
|
19958
19958
|
});
|
|
@@ -20077,7 +20077,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
20077
20077
|
});
|
|
20078
20078
|
//#endregion
|
|
20079
20079
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
20080
|
-
const MESSAGE$
|
|
20080
|
+
const MESSAGE$50 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
20081
20081
|
const DEFAULT_COMPONENTS = ["img"];
|
|
20082
20082
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
20083
20083
|
"image",
|
|
@@ -20142,7 +20142,7 @@ const imgRedundantAlt = defineRule({
|
|
|
20142
20142
|
if (!altAttribute) return;
|
|
20143
20143
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
20144
20144
|
node: altAttribute,
|
|
20145
|
-
message: MESSAGE$
|
|
20145
|
+
message: MESSAGE$50
|
|
20146
20146
|
});
|
|
20147
20147
|
} };
|
|
20148
20148
|
}
|
|
@@ -20674,7 +20674,7 @@ const collectHandlerReferencedNames = (root) => {
|
|
|
20674
20674
|
//#endregion
|
|
20675
20675
|
//#region src/plugin/rules/jotai/jotai-select-atom-in-render-body.ts
|
|
20676
20676
|
const JOTAI_SELECT_ATOM_SOURCES = ["jotai/utils", "jotai"];
|
|
20677
|
-
const COMPONENT_NAME_PATTERN
|
|
20677
|
+
const COMPONENT_NAME_PATTERN = /^[A-Z]/;
|
|
20678
20678
|
const isImportedSelectAtom = (callExpression) => {
|
|
20679
20679
|
if (!isNodeOfType(callExpression.callee, "Identifier")) return false;
|
|
20680
20680
|
const localName = callExpression.callee.name;
|
|
@@ -20710,14 +20710,14 @@ const getHandlerNamedBindingName = (functionNode) => {
|
|
|
20710
20710
|
const containingFunctionIsComponentOrHook = (functionNode) => {
|
|
20711
20711
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) {
|
|
20712
20712
|
const declaredName = functionNode.id.name;
|
|
20713
|
-
return COMPONENT_NAME_PATTERN
|
|
20713
|
+
return COMPONENT_NAME_PATTERN.test(declaredName) || HOOK_NAME_PATTERN$3.test(declaredName);
|
|
20714
20714
|
}
|
|
20715
20715
|
let cursor = functionNode.parent ?? null;
|
|
20716
20716
|
while (cursor && isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
20717
20717
|
if (!cursor) return false;
|
|
20718
20718
|
if (!isNodeOfType(cursor, "VariableDeclarator")) return false;
|
|
20719
20719
|
if (!isNodeOfType(cursor.id, "Identifier")) return false;
|
|
20720
|
-
return COMPONENT_NAME_PATTERN
|
|
20720
|
+
return COMPONENT_NAME_PATTERN.test(cursor.id.name) || HOOK_NAME_PATTERN$3.test(cursor.id.name);
|
|
20721
20721
|
};
|
|
20722
20722
|
const isBindingInvokedOnRenderPath = (root, bindingName) => {
|
|
20723
20723
|
let didFindRenderPathInvocation = false;
|
|
@@ -21795,7 +21795,7 @@ const isDiscardedProbeInsideTry = (node) => {
|
|
|
21795
21795
|
return false;
|
|
21796
21796
|
};
|
|
21797
21797
|
const isComponentOrHookName = (name) => /^[A-Z]/.test(name) || /^use[A-Z0-9]/.test(name);
|
|
21798
|
-
const getFunctionName
|
|
21798
|
+
const getFunctionName = (functionNode) => {
|
|
21799
21799
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id.name;
|
|
21800
21800
|
const holder = functionNode.parent;
|
|
21801
21801
|
if (holder && isNodeOfType(holder, "VariableDeclarator") && isNodeOfType(holder.id, "Identifier")) return holder.id.name;
|
|
@@ -21804,7 +21804,7 @@ const getFunctionName$1 = (functionNode) => {
|
|
|
21804
21804
|
const isUncacheableOptionsMergeUtility = (node) => {
|
|
21805
21805
|
const enclosingFunction = findEnclosingFunction$1(node);
|
|
21806
21806
|
if (!enclosingFunction || !isFunctionLike$1(enclosingFunction)) return false;
|
|
21807
|
-
const functionName = getFunctionName
|
|
21807
|
+
const functionName = getFunctionName(enclosingFunction);
|
|
21808
21808
|
if (!functionName || isComponentOrHookName(functionName)) return false;
|
|
21809
21809
|
const parameterNames = /* @__PURE__ */ new Set();
|
|
21810
21810
|
for (const parameter of enclosingFunction.params ?? []) collectPatternNames(parameter, parameterNames);
|
|
@@ -22106,7 +22106,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
22106
22106
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
22107
22107
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
22108
22108
|
};
|
|
22109
|
-
const MESSAGE$
|
|
22109
|
+
const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
22110
22110
|
const jsHoistRegexp = defineRule({
|
|
22111
22111
|
id: "js-hoist-regexp",
|
|
22112
22112
|
title: "RegExp built inside a loop",
|
|
@@ -22123,7 +22123,7 @@ const jsHoistRegexp = defineRule({
|
|
|
22123
22123
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
22124
22124
|
context.report({
|
|
22125
22125
|
node,
|
|
22126
|
-
message: MESSAGE$
|
|
22126
|
+
message: MESSAGE$49
|
|
22127
22127
|
});
|
|
22128
22128
|
};
|
|
22129
22129
|
return createLoopAwareVisitors({
|
|
@@ -24890,7 +24890,7 @@ const jsxMaxDepth = defineRule({
|
|
|
24890
24890
|
});
|
|
24891
24891
|
//#endregion
|
|
24892
24892
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
24893
|
-
const MESSAGE$
|
|
24893
|
+
const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
24894
24894
|
const LITERAL_TEXT_TAGS = new Set([
|
|
24895
24895
|
"code",
|
|
24896
24896
|
"pre",
|
|
@@ -24960,7 +24960,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
24960
24960
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
24961
24961
|
context.report({
|
|
24962
24962
|
node,
|
|
24963
|
-
message: MESSAGE$
|
|
24963
|
+
message: MESSAGE$48
|
|
24964
24964
|
});
|
|
24965
24965
|
} })
|
|
24966
24966
|
});
|
|
@@ -24991,7 +24991,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
24991
24991
|
};
|
|
24992
24992
|
//#endregion
|
|
24993
24993
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
24994
|
-
const MESSAGE$
|
|
24994
|
+
const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
24995
24995
|
const CONTEXT_MODULES$1 = [
|
|
24996
24996
|
"react",
|
|
24997
24997
|
"use-context-selector",
|
|
@@ -25094,7 +25094,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
25094
25094
|
if (!isConstructedValue(innerExpression)) continue;
|
|
25095
25095
|
context.report({
|
|
25096
25096
|
node: attribute,
|
|
25097
|
-
message: MESSAGE$
|
|
25097
|
+
message: MESSAGE$47
|
|
25098
25098
|
});
|
|
25099
25099
|
}
|
|
25100
25100
|
}
|
|
@@ -25960,7 +25960,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
25960
25960
|
];
|
|
25961
25961
|
//#endregion
|
|
25962
25962
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
25963
|
-
const MESSAGE$
|
|
25963
|
+
const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
25964
25964
|
const isDataArrayPropName = (propName) => {
|
|
25965
25965
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
25966
25966
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -26047,7 +26047,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
26047
26047
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
26048
26048
|
context.report({
|
|
26049
26049
|
node,
|
|
26050
|
-
message: MESSAGE$
|
|
26050
|
+
message: MESSAGE$46
|
|
26051
26051
|
});
|
|
26052
26052
|
}
|
|
26053
26053
|
};
|
|
@@ -26305,7 +26305,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
26305
26305
|
]);
|
|
26306
26306
|
//#endregion
|
|
26307
26307
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
26308
|
-
const MESSAGE$
|
|
26308
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
26309
26309
|
const isAccessorPredicateName = (propName) => {
|
|
26310
26310
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
26311
26311
|
if (propName.length <= prefix.length) continue;
|
|
@@ -26512,7 +26512,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
26512
26512
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
26513
26513
|
context.report({
|
|
26514
26514
|
node,
|
|
26515
|
-
message: MESSAGE$
|
|
26515
|
+
message: MESSAGE$45
|
|
26516
26516
|
});
|
|
26517
26517
|
}
|
|
26518
26518
|
};
|
|
@@ -26732,7 +26732,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
26732
26732
|
];
|
|
26733
26733
|
//#endregion
|
|
26734
26734
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
26735
|
-
const MESSAGE$
|
|
26735
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
26736
26736
|
const isConfigObjectPropName = (propName) => {
|
|
26737
26737
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
26738
26738
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -26821,7 +26821,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26821
26821
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
26822
26822
|
context.report({
|
|
26823
26823
|
node,
|
|
26824
|
-
message: MESSAGE$
|
|
26824
|
+
message: MESSAGE$44
|
|
26825
26825
|
});
|
|
26826
26826
|
}
|
|
26827
26827
|
};
|
|
@@ -26829,7 +26829,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26829
26829
|
});
|
|
26830
26830
|
//#endregion
|
|
26831
26831
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
26832
|
-
const MESSAGE$
|
|
26832
|
+
const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
26833
26833
|
const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
|
|
26834
26834
|
const resolveSettings$28 = (settings) => {
|
|
26835
26835
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -26867,7 +26867,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26867
26867
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
26868
26868
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
26869
26869
|
node: attribute,
|
|
26870
|
-
message: MESSAGE$
|
|
26870
|
+
message: MESSAGE$43
|
|
26871
26871
|
});
|
|
26872
26872
|
}
|
|
26873
26873
|
} };
|
|
@@ -27210,7 +27210,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
27210
27210
|
});
|
|
27211
27211
|
//#endregion
|
|
27212
27212
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
27213
|
-
const MESSAGE$
|
|
27213
|
+
const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
|
|
27214
27214
|
const resolveSettings$25 = (settings) => {
|
|
27215
27215
|
const reactDoctor = settings?.["react-doctor"];
|
|
27216
27216
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -27253,7 +27253,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
27253
27253
|
}
|
|
27254
27254
|
context.report({
|
|
27255
27255
|
node: attribute,
|
|
27256
|
-
message: MESSAGE$
|
|
27256
|
+
message: MESSAGE$42
|
|
27257
27257
|
});
|
|
27258
27258
|
didReportInFile = true;
|
|
27259
27259
|
return;
|
|
@@ -27529,7 +27529,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
27529
27529
|
});
|
|
27530
27530
|
//#endregion
|
|
27531
27531
|
//#region src/plugin/rules/a11y/lang.ts
|
|
27532
|
-
const MESSAGE$
|
|
27532
|
+
const MESSAGE$41 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
|
|
27533
27533
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
27534
27534
|
"aa",
|
|
27535
27535
|
"ab",
|
|
@@ -28009,7 +28009,7 @@ const lang = defineRule({
|
|
|
28009
28009
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
28010
28010
|
context.report({
|
|
28011
28011
|
node: langAttr,
|
|
28012
|
-
message: MESSAGE$
|
|
28012
|
+
message: MESSAGE$41
|
|
28013
28013
|
});
|
|
28014
28014
|
return;
|
|
28015
28015
|
}
|
|
@@ -28018,7 +28018,7 @@ const lang = defineRule({
|
|
|
28018
28018
|
if (value === null) return;
|
|
28019
28019
|
if (!isValidLangTag(value)) context.report({
|
|
28020
28020
|
node: langAttr,
|
|
28021
|
-
message: MESSAGE$
|
|
28021
|
+
message: MESSAGE$41
|
|
28022
28022
|
});
|
|
28023
28023
|
} })
|
|
28024
28024
|
});
|
|
@@ -28063,13 +28063,13 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
28063
28063
|
recommendation: "Use a constrained compiler for untrusted content, disable expressions/raw HTML, sandbox renderers, and avoid caching attacker-controlled output across tenants.",
|
|
28064
28064
|
scan: scanByPattern({
|
|
28065
28065
|
shouldScan: (file) => isProductionSourcePath(file.relativePath),
|
|
28066
|
-
pattern: /(?:@mdx-js\/mdx|next-mdx-remote|\b(?:MDXRemote|compileMDX|evaluateMdx)\b)[\s\S]{0,700}\b(?:repo
|
|
28066
|
+
pattern: /(?:@mdx-js\/mdx|next-mdx-remote|\b(?:MDXRemote|compileMDX|evaluateMdx)\b)[\s\S]{0,700}\b(?:repo|customer|tenant|user[-_]?(?:content|markdown|mdx|input|provided|generated|submitted)|untrusted|searchParams|req\.|request\.|fetch\s*\(|prisma\.|db\.|database|rehypeRaw|allowDangerousHtml)/i,
|
|
28067
28067
|
message: "MDX/markdown rendering code may evaluate user or repository content during SSR or static generation."
|
|
28068
28068
|
})
|
|
28069
28069
|
});
|
|
28070
28070
|
//#endregion
|
|
28071
28071
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
28072
|
-
const MESSAGE$
|
|
28072
|
+
const MESSAGE$40 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
28073
28073
|
const DEFAULT_AUDIO = ["audio"];
|
|
28074
28074
|
const DEFAULT_VIDEO = ["video"];
|
|
28075
28075
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -28159,7 +28159,7 @@ const mediaHasCaption = defineRule({
|
|
|
28159
28159
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
28160
28160
|
context.report({
|
|
28161
28161
|
node: node.name,
|
|
28162
|
-
message: MESSAGE$
|
|
28162
|
+
message: MESSAGE$40
|
|
28163
28163
|
});
|
|
28164
28164
|
return;
|
|
28165
28165
|
}
|
|
@@ -28178,553 +28178,12 @@ const mediaHasCaption = defineRule({
|
|
|
28178
28178
|
return kindValue.value.toLowerCase() === "captions";
|
|
28179
28179
|
})) context.report({
|
|
28180
28180
|
node: node.name,
|
|
28181
|
-
message: MESSAGE$
|
|
28181
|
+
message: MESSAGE$40
|
|
28182
28182
|
});
|
|
28183
28183
|
} };
|
|
28184
28184
|
}
|
|
28185
28185
|
});
|
|
28186
28186
|
//#endregion
|
|
28187
|
-
//#region src/plugin/utils/get-class-binding-symbol.ts
|
|
28188
|
-
const getClassBindingSymbol = (classNode, scopes) => {
|
|
28189
|
-
if (isNodeOfType(classNode.id, "Identifier")) return scopes.symbolFor(classNode.id);
|
|
28190
|
-
const parent = classNode.parent;
|
|
28191
|
-
return isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier") ? scopes.symbolFor(parent.id) : null;
|
|
28192
|
-
};
|
|
28193
|
-
//#endregion
|
|
28194
|
-
//#region src/plugin/utils/mobx-rule-gates.ts
|
|
28195
|
-
const MOBX_RULE_GATES = {
|
|
28196
|
-
"mobx-reaction-disposer-discarded": { requires: ["mobx:4"] },
|
|
28197
|
-
"mobx-no-make-auto-observable-in-inheritance": { requires: ["mobx:6"] },
|
|
28198
|
-
"mobx-no-computed-side-effects": { requires: ["mobx:4"] },
|
|
28199
|
-
"mobx-async-action-requires-action": { requires: ["mobx:4"] },
|
|
28200
|
-
"mobx-no-observer-wrapped-memo": { requires: [
|
|
28201
|
-
"mobx:4",
|
|
28202
|
-
"mobx-react-binding",
|
|
28203
|
-
"react"
|
|
28204
|
-
] },
|
|
28205
|
-
"mobx-make-observable-unconditional": { requires: ["mobx:6"] },
|
|
28206
|
-
"mobx-legacy-decorator-needs-make-observable": { requires: ["mobx:6"] },
|
|
28207
|
-
"mobx-initialize-before-make-auto-observable": { requires: ["mobx:6"] },
|
|
28208
|
-
"mobx-observable-read-needs-observer": {
|
|
28209
|
-
requires: [
|
|
28210
|
-
"mobx:4",
|
|
28211
|
-
"mobx-react-binding",
|
|
28212
|
-
"react"
|
|
28213
|
-
],
|
|
28214
|
-
disabledWhen: ["mobx-react-observer"]
|
|
28215
|
-
},
|
|
28216
|
-
"mobx-observer-before-inject": { requires: [
|
|
28217
|
-
"mobx:4",
|
|
28218
|
-
"mobx-react",
|
|
28219
|
-
"react"
|
|
28220
|
-
] },
|
|
28221
|
-
"mobx-reaction-requires-observable": { requires: ["mobx:4"] },
|
|
28222
|
-
"mobx-no-invalid-observable-override": { requires: ["mobx:6"] },
|
|
28223
|
-
"mobx-no-observable-prop-to-untracked-child": { requires: [
|
|
28224
|
-
"mobx:4",
|
|
28225
|
-
"mobx-react-binding",
|
|
28226
|
-
"react"
|
|
28227
|
-
] },
|
|
28228
|
-
"mobx-no-stale-observable-snapshot-after-await": { requires: ["mobx:4"] },
|
|
28229
|
-
"mobx-no-reaction-comparison-value-mutation": { requires: ["mobx:4"] },
|
|
28230
|
-
"mobx-observer-class-no-should-component-update": { requires: [
|
|
28231
|
-
"mobx:4",
|
|
28232
|
-
"mobx-react",
|
|
28233
|
-
"react"
|
|
28234
|
-
] },
|
|
28235
|
-
"mobx-enable-static-rendering-for-ssr": { requires: [
|
|
28236
|
-
"mobx:4",
|
|
28237
|
-
"mobx-react-binding",
|
|
28238
|
-
"react",
|
|
28239
|
-
"ssr"
|
|
28240
|
-
] },
|
|
28241
|
-
"mobx-no-rest-destructure-observable": { requires: [
|
|
28242
|
-
"mobx:4",
|
|
28243
|
-
"mobx-react-binding",
|
|
28244
|
-
"react"
|
|
28245
|
-
] },
|
|
28246
|
-
"mobx-computed-depends-on-non-observable": { requires: ["mobx:4"] },
|
|
28247
|
-
"mobx-no-keepalive-computed-without-disposal": { requires: ["mobx:4"] }
|
|
28248
|
-
};
|
|
28249
|
-
//#endregion
|
|
28250
|
-
//#region src/plugin/utils/resolve-imported-api-reference.ts
|
|
28251
|
-
const resolveImportSymbol = (symbol) => {
|
|
28252
|
-
const importDeclaration = getImportDeclarationForSymbol(symbol);
|
|
28253
|
-
if (!importDeclaration || typeof importDeclaration.source.value !== "string") return null;
|
|
28254
|
-
if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return {
|
|
28255
|
-
source: importDeclaration.source.value,
|
|
28256
|
-
importedName: null,
|
|
28257
|
-
isNamespace: true
|
|
28258
|
-
};
|
|
28259
|
-
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return {
|
|
28260
|
-
source: importDeclaration.source.value,
|
|
28261
|
-
importedName: "default",
|
|
28262
|
-
isNamespace: false
|
|
28263
|
-
};
|
|
28264
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
28265
|
-
return importedName ? {
|
|
28266
|
-
source: importDeclaration.source.value,
|
|
28267
|
-
importedName,
|
|
28268
|
-
isNamespace: false
|
|
28269
|
-
} : null;
|
|
28270
|
-
};
|
|
28271
|
-
const resolveIdentifierReference = (identifier, scopes, visitedSymbolIds) => {
|
|
28272
|
-
const symbol = scopes.symbolFor(identifier);
|
|
28273
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
28274
|
-
if (symbol.kind === "import") return resolveImportSymbol(symbol);
|
|
28275
|
-
if (symbol.kind !== "const" || !symbol.initializer) return null;
|
|
28276
|
-
visitedSymbolIds.add(symbol.id);
|
|
28277
|
-
if (isNodeOfType(symbol.declarationNode, "VariableDeclarator") && isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) {
|
|
28278
|
-
const receiver = resolveImportedApiReference(symbol.initializer, scopes, visitedSymbolIds);
|
|
28279
|
-
if (!receiver || !receiver.isNamespace && receiver.importedName !== "default") return null;
|
|
28280
|
-
for (const property of symbol.declarationNode.id.properties) {
|
|
28281
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
28282
|
-
if ((isNodeOfType(property.value, "AssignmentPattern") ? property.value.left : property.value) !== symbol.bindingIdentifier) continue;
|
|
28283
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
28284
|
-
return propertyName ? {
|
|
28285
|
-
source: receiver.source,
|
|
28286
|
-
importedName: propertyName,
|
|
28287
|
-
isNamespace: false
|
|
28288
|
-
} : null;
|
|
28289
|
-
}
|
|
28290
|
-
return null;
|
|
28291
|
-
}
|
|
28292
|
-
return resolveImportedApiReference(symbol.initializer, scopes, visitedSymbolIds);
|
|
28293
|
-
};
|
|
28294
|
-
const resolveImportedApiReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
28295
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
28296
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) return resolveIdentifierReference(unwrappedExpression, scopes, visitedSymbolIds);
|
|
28297
|
-
if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
|
|
28298
|
-
const propertyName = getStaticPropertyName(unwrappedExpression);
|
|
28299
|
-
if (!propertyName) return null;
|
|
28300
|
-
const receiver = resolveImportedApiReference(unwrappedExpression.object, scopes, visitedSymbolIds);
|
|
28301
|
-
if (!receiver || !receiver.isNamespace && receiver.importedName !== "default") return null;
|
|
28302
|
-
return {
|
|
28303
|
-
source: receiver.source,
|
|
28304
|
-
importedName: propertyName,
|
|
28305
|
-
isNamespace: false
|
|
28306
|
-
};
|
|
28307
|
-
};
|
|
28308
|
-
//#endregion
|
|
28309
|
-
//#region src/plugin/rules/mobx/mobx-no-make-auto-observable-in-inheritance.ts
|
|
28310
|
-
const MESSAGE$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
|
|
28728
28187
|
//#region src/plugin/utils/has-jsx-prop.ts
|
|
28729
28188
|
const hasJsxProp = (attributes, targetProp) => {
|
|
28730
28189
|
for (const attribute of attributes) {
|
|
@@ -29515,11 +28974,11 @@ const hasEmailTemplateImport = (programRoot) => {
|
|
|
29515
28974
|
return found;
|
|
29516
28975
|
};
|
|
29517
28976
|
//#endregion
|
|
29518
|
-
//#region src/plugin/utils/build-
|
|
29519
|
-
const
|
|
29520
|
-
const
|
|
29521
|
-
const
|
|
29522
|
-
const
|
|
28977
|
+
//#region src/plugin/utils/build-generated-image-project-index.ts
|
|
28978
|
+
const GENERATED_IMAGE_SOURCE_FILE_PATTERN = /\.[cm]?[jt]sx?$/i;
|
|
28979
|
+
const GENERATED_IMAGE_DECLARATION_FILE_PATTERN = /\.d\.[cm]?[jt]s$/i;
|
|
28980
|
+
const GENERATED_IMAGE_MDX_FILE_PATTERN = /\.mdx$/i;
|
|
28981
|
+
const GENERATED_IMAGE_IGNORED_DIRECTORY_NAMES = new Set([
|
|
29523
28982
|
".angular",
|
|
29524
28983
|
".astro",
|
|
29525
28984
|
".cache",
|
|
@@ -29540,12 +28999,12 @@ const SOURCE_PROJECT_IGNORED_DIRECTORY_NAMES = new Set([
|
|
|
29540
28999
|
"out",
|
|
29541
29000
|
"storybook-static"
|
|
29542
29001
|
]);
|
|
29543
|
-
const
|
|
29544
|
-
const
|
|
29545
|
-
const cachedScopes =
|
|
29002
|
+
const generatedImageScopeCache = /* @__PURE__ */ new WeakMap();
|
|
29003
|
+
const getGeneratedImageModuleScopes = (programNode) => {
|
|
29004
|
+
const cachedScopes = generatedImageScopeCache.get(programNode);
|
|
29546
29005
|
if (cachedScopes) return cachedScopes;
|
|
29547
29006
|
const scopes = analyzeScopes(programNode);
|
|
29548
|
-
|
|
29007
|
+
generatedImageScopeCache.set(programNode, scopes);
|
|
29549
29008
|
return scopes;
|
|
29550
29009
|
};
|
|
29551
29010
|
const listProductionSourceFiles = (rootDirectory) => {
|
|
@@ -29563,7 +29022,7 @@ const listProductionSourceFiles = (rootDirectory) => {
|
|
|
29563
29022
|
}
|
|
29564
29023
|
for (const entry of entries) {
|
|
29565
29024
|
const absolutePath = path.join(currentDirectory, entry.name);
|
|
29566
|
-
const isIgnoredDirectoryName =
|
|
29025
|
+
const isIgnoredDirectoryName = GENERATED_IMAGE_IGNORED_DIRECTORY_NAMES.has(entry.name) || entry.name.startsWith(".") && entry.name !== ".dumi" && entry.name !== ".storybook";
|
|
29567
29026
|
if (entry.isSymbolicLink() && isIgnoredDirectoryName) continue;
|
|
29568
29027
|
if (entry.isSymbolicLink()) return null;
|
|
29569
29028
|
if (entry.isDirectory()) {
|
|
@@ -29572,12 +29031,12 @@ const listProductionSourceFiles = (rootDirectory) => {
|
|
|
29572
29031
|
continue;
|
|
29573
29032
|
}
|
|
29574
29033
|
if (!entry.isFile() || isTestlikeFilename(absolutePath)) continue;
|
|
29575
|
-
if (
|
|
29034
|
+
if (GENERATED_IMAGE_MDX_FILE_PATTERN.test(entry.name)) {
|
|
29576
29035
|
hasOpaqueMdxConsumerSurface = true;
|
|
29577
29036
|
continue;
|
|
29578
29037
|
}
|
|
29579
|
-
if (!
|
|
29580
|
-
if (
|
|
29038
|
+
if (!GENERATED_IMAGE_SOURCE_FILE_PATTERN.test(entry.name)) continue;
|
|
29039
|
+
if (GENERATED_IMAGE_DECLARATION_FILE_PATTERN.test(entry.name)) continue;
|
|
29581
29040
|
sourceFilePaths.push(normalizeFilename(absolutePath));
|
|
29582
29041
|
}
|
|
29583
29042
|
}
|
|
@@ -29622,7 +29081,7 @@ const indexModuleSources = (module, consumerModulesByFilePath, resolvedSourcePat
|
|
|
29622
29081
|
consumerModulesByFilePath.set(normalizedSourcePath, consumerModules);
|
|
29623
29082
|
});
|
|
29624
29083
|
};
|
|
29625
|
-
const
|
|
29084
|
+
const buildGeneratedImageProjectIndex = (rootDirectory, currentFilePath, currentProgramNode, currentScopes) => {
|
|
29626
29085
|
const productionSourceFiles = listProductionSourceFiles(rootDirectory);
|
|
29627
29086
|
if (!productionSourceFiles) return null;
|
|
29628
29087
|
const modulesByFilePath = /* @__PURE__ */ new Map();
|
|
@@ -29640,7 +29099,7 @@ const buildSourceProjectIndex = (rootDirectory, currentFilePath, currentProgramN
|
|
|
29640
29099
|
modulesByFilePath.set(filePath, {
|
|
29641
29100
|
filePath,
|
|
29642
29101
|
programNode: parsedProgram,
|
|
29643
|
-
scopes:
|
|
29102
|
+
scopes: getGeneratedImageModuleScopes(parsedProgram)
|
|
29644
29103
|
});
|
|
29645
29104
|
}
|
|
29646
29105
|
const consumerModulesByFilePath = /* @__PURE__ */ new Map();
|
|
@@ -29656,51 +29115,6 @@ const buildSourceProjectIndex = (rootDirectory, currentFilePath, currentProgramN
|
|
|
29656
29115
|
};
|
|
29657
29116
|
};
|
|
29658
29117
|
//#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
|
|
29704
29118
|
//#region src/plugin/utils/read-nearest-package-manifest.ts
|
|
29705
29119
|
const cachedPackageDirectoryByFilename = /* @__PURE__ */ new Map();
|
|
29706
29120
|
const cachedManifestByPackageDirectory = /* @__PURE__ */ new Map();
|
|
@@ -29785,6 +29199,37 @@ const getImportSpecifierName = (specifier) => {
|
|
|
29785
29199
|
if (isNodeOfType(imported, "Identifier")) return imported.name;
|
|
29786
29200
|
return isNodeOfType(imported, "Literal") && typeof imported.value === "string" ? imported.value : null;
|
|
29787
29201
|
};
|
|
29202
|
+
const getDirectFunctionBindingIdentifier = (functionNode) => {
|
|
29203
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
29204
|
+
const functionValueRoot = findTransparentExpressionRoot(functionNode);
|
|
29205
|
+
const parent = functionValueRoot.parent;
|
|
29206
|
+
return isNodeOfType(parent, "VariableDeclarator") && parent.init === functionValueRoot && isNodeOfType(parent.id, "Identifier") ? parent.id : null;
|
|
29207
|
+
};
|
|
29208
|
+
const getExportNamesForFunction = (programNode, functionNode) => {
|
|
29209
|
+
const functionValueRoot = findTransparentExpressionRoot(functionNode);
|
|
29210
|
+
const bindingName = getDirectFunctionBindingIdentifier(functionNode)?.name ?? null;
|
|
29211
|
+
const exportedNames = /* @__PURE__ */ new Set();
|
|
29212
|
+
for (const statement of programNode.body) {
|
|
29213
|
+
if (isNodeOfType(statement, "ExportDefaultDeclaration")) {
|
|
29214
|
+
if (statement.declaration === functionValueRoot || bindingName && isNodeOfType(statement.declaration, "Identifier") && statement.declaration.name === bindingName) exportedNames.add("default");
|
|
29215
|
+
continue;
|
|
29216
|
+
}
|
|
29217
|
+
if (!isNodeOfType(statement, "ExportNamedDeclaration")) continue;
|
|
29218
|
+
const declaration = statement.declaration;
|
|
29219
|
+
if (declaration === functionValueRoot && bindingName) exportedNames.add(bindingName);
|
|
29220
|
+
if (declaration && isNodeOfType(declaration, "VariableDeclaration")) {
|
|
29221
|
+
for (const declarator of declaration.declarations) if (declarator.init === functionValueRoot && isNodeOfType(declarator.id, "Identifier")) exportedNames.add(declarator.id.name);
|
|
29222
|
+
}
|
|
29223
|
+
if (!bindingName || statement.source) continue;
|
|
29224
|
+
for (const specifier of statement.specifiers) {
|
|
29225
|
+
if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
|
|
29226
|
+
if (getImportedSpecifierName(specifier) !== bindingName) continue;
|
|
29227
|
+
const exportedName = getExportedSpecifierName(specifier);
|
|
29228
|
+
if (exportedName) exportedNames.add(exportedName);
|
|
29229
|
+
}
|
|
29230
|
+
}
|
|
29231
|
+
return [...exportedNames];
|
|
29232
|
+
};
|
|
29788
29233
|
const isTransparentGeneratedImageValueFlow = (expression, target) => {
|
|
29789
29234
|
let current = findTransparentExpressionRoot(expression);
|
|
29790
29235
|
while (current !== target) {
|
|
@@ -29843,7 +29288,7 @@ const classifyInvokedExpression = (module, expression, state) => {
|
|
|
29843
29288
|
}
|
|
29844
29289
|
const forwardingFunction = getForwardingFunction(expression);
|
|
29845
29290
|
if (!forwardingFunction) return false;
|
|
29846
|
-
const exportedNames =
|
|
29291
|
+
const exportedNames = getExportNamesForFunction(module.programNode, forwardingFunction);
|
|
29847
29292
|
if (exportedNames.length === 0) return false;
|
|
29848
29293
|
for (const exportedName of exportedNames) enqueueExport(state, module.filePath, exportedName);
|
|
29849
29294
|
return true;
|
|
@@ -29976,9 +29421,9 @@ const createExportedJsxGeneratedImageOwnershipAnalyzer = (context) => {
|
|
|
29976
29421
|
const programNode = findProgramRoot(jsxNode);
|
|
29977
29422
|
const enclosingFunction = findEnclosingFunction$1(jsxNode);
|
|
29978
29423
|
if (!programNode || !enclosingFunction) return false;
|
|
29979
|
-
const initialExportNames =
|
|
29424
|
+
const initialExportNames = getExportNamesForFunction(programNode, enclosingFunction);
|
|
29980
29425
|
if (initialExportNames.length === 0) return false;
|
|
29981
|
-
if (projectIndex === void 0) projectIndex =
|
|
29426
|
+
if (projectIndex === void 0) projectIndex = buildGeneratedImageProjectIndex(rootDirectory, filename, programNode, context.scopes);
|
|
29982
29427
|
if (!projectIndex || projectIndex.hasOpaqueMdxConsumerSurface) return false;
|
|
29983
29428
|
const state = {
|
|
29984
29429
|
projectIndex,
|
|
@@ -30225,20 +29670,17 @@ const catchClauseRethrowsCaught = (handler) => {
|
|
|
30225
29670
|
return didRethrow;
|
|
30226
29671
|
};
|
|
30227
29672
|
//#endregion
|
|
30228
|
-
//#region src/plugin/utils/
|
|
30229
|
-
const
|
|
29673
|
+
//#region src/plugin/utils/is-immediately-invoked-function.ts
|
|
29674
|
+
const isImmediatelyInvokedFunction = (functionNode) => {
|
|
30230
29675
|
let wrappedCallee = functionNode;
|
|
30231
29676
|
let enclosing = functionNode.parent;
|
|
30232
29677
|
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
30233
29678
|
wrappedCallee = enclosing;
|
|
30234
29679
|
enclosing = enclosing.parent ?? null;
|
|
30235
29680
|
}
|
|
30236
|
-
return enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee
|
|
29681
|
+
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
30237
29682
|
};
|
|
30238
29683
|
//#endregion
|
|
30239
|
-
//#region src/plugin/utils/is-immediately-invoked-function.ts
|
|
30240
|
-
const isImmediatelyInvokedFunction = (functionNode) => Boolean(findImmediatelyInvokedCallExpression(functionNode));
|
|
30241
|
-
//#endregion
|
|
30242
29684
|
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
30243
29685
|
const findGuardingTryStatement = (node) => {
|
|
30244
29686
|
let child = node;
|
|
@@ -30789,7 +30231,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
30789
30231
|
});
|
|
30790
30232
|
//#endregion
|
|
30791
30233
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
30792
|
-
const MESSAGE$
|
|
30234
|
+
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
30793
30235
|
const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
30794
30236
|
const noAccessKey = defineRule({
|
|
30795
30237
|
id: "no-access-key",
|
|
@@ -30808,7 +30250,7 @@ const noAccessKey = defineRule({
|
|
|
30808
30250
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
30809
30251
|
context.report({
|
|
30810
30252
|
node: accessKey,
|
|
30811
|
-
message: MESSAGE$
|
|
30253
|
+
message: MESSAGE$39
|
|
30812
30254
|
});
|
|
30813
30255
|
return;
|
|
30814
30256
|
}
|
|
@@ -30818,7 +30260,7 @@ const noAccessKey = defineRule({
|
|
|
30818
30260
|
if (isUndefinedIdentifier$1(expression)) return;
|
|
30819
30261
|
context.report({
|
|
30820
30262
|
node: accessKey,
|
|
30821
|
-
message: MESSAGE$
|
|
30263
|
+
message: MESSAGE$39
|
|
30822
30264
|
});
|
|
30823
30265
|
}
|
|
30824
30266
|
} };
|
|
@@ -33161,7 +32603,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
33161
32603
|
});
|
|
33162
32604
|
//#endregion
|
|
33163
32605
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
33164
|
-
const MESSAGE$
|
|
32606
|
+
const MESSAGE$38 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
|
|
33165
32607
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
33166
32608
|
"button",
|
|
33167
32609
|
"embed",
|
|
@@ -33273,7 +32715,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
33273
32715
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
33274
32716
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
33275
32717
|
node: ariaHidden,
|
|
33276
|
-
message: MESSAGE$
|
|
32718
|
+
message: MESSAGE$38
|
|
33277
32719
|
});
|
|
33278
32720
|
} })
|
|
33279
32721
|
});
|
|
@@ -34320,7 +33762,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
34320
33762
|
};
|
|
34321
33763
|
//#endregion
|
|
34322
33764
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
34323
|
-
const MESSAGE$
|
|
33765
|
+
const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
|
|
34324
33766
|
const SECOND_INDEX_METHODS = new Set([
|
|
34325
33767
|
"every",
|
|
34326
33768
|
"filter",
|
|
@@ -34439,14 +33881,14 @@ const noArrayIndexKey = defineRule({
|
|
|
34439
33881
|
if (propName !== "key") continue;
|
|
34440
33882
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
34441
33883
|
node: property,
|
|
34442
|
-
message: MESSAGE$
|
|
33884
|
+
message: MESSAGE$37
|
|
34443
33885
|
});
|
|
34444
33886
|
}
|
|
34445
33887
|
} })
|
|
34446
33888
|
});
|
|
34447
33889
|
//#endregion
|
|
34448
33890
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
34449
|
-
const MESSAGE$
|
|
33891
|
+
const MESSAGE$36 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
|
|
34450
33892
|
const noAsyncEffectCallback = defineRule({
|
|
34451
33893
|
id: "no-async-effect-callback",
|
|
34452
33894
|
title: "Async effect callback",
|
|
@@ -34464,13 +33906,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
34464
33906
|
if (!callback.async) return;
|
|
34465
33907
|
context.report({
|
|
34466
33908
|
node: callback,
|
|
34467
|
-
message: MESSAGE$
|
|
33909
|
+
message: MESSAGE$36
|
|
34468
33910
|
});
|
|
34469
33911
|
} })
|
|
34470
33912
|
});
|
|
34471
33913
|
//#endregion
|
|
34472
33914
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
34473
|
-
const MESSAGE$
|
|
33915
|
+
const MESSAGE$35 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
34474
33916
|
const resolveSettings$21 = (settings) => {
|
|
34475
33917
|
const reactDoctor = settings?.["react-doctor"];
|
|
34476
33918
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -34567,7 +34009,7 @@ const noAutofocus = defineRule({
|
|
|
34567
34009
|
if (isConditionallyRendered(node)) return;
|
|
34568
34010
|
context.report({
|
|
34569
34011
|
node: autoFocusAttribute,
|
|
34570
|
-
message: MESSAGE$
|
|
34012
|
+
message: MESSAGE$35
|
|
34571
34013
|
});
|
|
34572
34014
|
} };
|
|
34573
34015
|
}
|
|
@@ -35896,7 +35338,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35896
35338
|
});
|
|
35897
35339
|
//#endregion
|
|
35898
35340
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
35899
|
-
const MESSAGE$
|
|
35341
|
+
const MESSAGE$34 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
35900
35342
|
const noChildrenProp = defineRule({
|
|
35901
35343
|
id: "no-children-prop",
|
|
35902
35344
|
title: "Children passed as a prop",
|
|
@@ -35908,7 +35350,7 @@ const noChildrenProp = defineRule({
|
|
|
35908
35350
|
if (node.name.name !== "children") return;
|
|
35909
35351
|
context.report({
|
|
35910
35352
|
node: node.name,
|
|
35911
|
-
message: MESSAGE$
|
|
35353
|
+
message: MESSAGE$34
|
|
35912
35354
|
});
|
|
35913
35355
|
},
|
|
35914
35356
|
CallExpression(node) {
|
|
@@ -35921,7 +35363,7 @@ const noChildrenProp = defineRule({
|
|
|
35921
35363
|
const propertyKey = property.key;
|
|
35922
35364
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
35923
35365
|
node: propertyKey,
|
|
35924
|
-
message: MESSAGE$
|
|
35366
|
+
message: MESSAGE$34
|
|
35925
35367
|
});
|
|
35926
35368
|
}
|
|
35927
35369
|
}
|
|
@@ -35929,7 +35371,7 @@ const noChildrenProp = defineRule({
|
|
|
35929
35371
|
});
|
|
35930
35372
|
//#endregion
|
|
35931
35373
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
35932
|
-
const MESSAGE$
|
|
35374
|
+
const MESSAGE$33 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
35933
35375
|
const noCloneElement = defineRule({
|
|
35934
35376
|
id: "no-clone-element",
|
|
35935
35377
|
title: "cloneElement makes child props fragile",
|
|
@@ -35942,7 +35384,7 @@ const noCloneElement = defineRule({
|
|
|
35942
35384
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
35943
35385
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
35944
35386
|
node: callee,
|
|
35945
|
-
message: MESSAGE$
|
|
35387
|
+
message: MESSAGE$33
|
|
35946
35388
|
});
|
|
35947
35389
|
return;
|
|
35948
35390
|
}
|
|
@@ -35955,7 +35397,7 @@ const noCloneElement = defineRule({
|
|
|
35955
35397
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
35956
35398
|
context.report({
|
|
35957
35399
|
node: callee,
|
|
35958
|
-
message: MESSAGE$
|
|
35400
|
+
message: MESSAGE$33
|
|
35959
35401
|
});
|
|
35960
35402
|
}
|
|
35961
35403
|
} })
|
|
@@ -35975,7 +35417,7 @@ const getCallMethodName = (callee) => {
|
|
|
35975
35417
|
};
|
|
35976
35418
|
//#endregion
|
|
35977
35419
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
35978
|
-
const MESSAGE$
|
|
35420
|
+
const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
35979
35421
|
const CONTEXT_MODULES = [
|
|
35980
35422
|
"react",
|
|
35981
35423
|
"use-context-selector",
|
|
@@ -36023,7 +35465,7 @@ const noCreateContextInRender = defineRule({
|
|
|
36023
35465
|
if (!componentOrHookName) return;
|
|
36024
35466
|
context.report({
|
|
36025
35467
|
node,
|
|
36026
|
-
message: `${MESSAGE$
|
|
35468
|
+
message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
|
|
36027
35469
|
});
|
|
36028
35470
|
} })
|
|
36029
35471
|
});
|
|
@@ -36770,7 +36212,7 @@ const isProvenOneShotTestingLibraryComponent = (functionNode, filename, scopes)
|
|
|
36770
36212
|
};
|
|
36771
36213
|
//#endregion
|
|
36772
36214
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
36773
|
-
const MESSAGE$
|
|
36215
|
+
const MESSAGE$31 = "`createRef()` may escape or be observed beyond the render that created it, so a later render can replace the ref object and detach the observed one. Hoist a `useRef()` call to the component's unconditional top level instead.";
|
|
36774
36216
|
const isUseMemoCallbackArgument = (functionNode, scopes) => {
|
|
36775
36217
|
const parent = functionNode.parent;
|
|
36776
36218
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -36824,7 +36266,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
36824
36266
|
if (isCreateRefResultWriteOnly(node, context.filename, context.scopes)) return;
|
|
36825
36267
|
context.report({
|
|
36826
36268
|
node,
|
|
36827
|
-
message: MESSAGE$
|
|
36269
|
+
message: MESSAGE$31
|
|
36828
36270
|
});
|
|
36829
36271
|
} })
|
|
36830
36272
|
});
|
|
@@ -36964,7 +36406,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
36964
36406
|
});
|
|
36965
36407
|
//#endregion
|
|
36966
36408
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
36967
|
-
const MESSAGE$
|
|
36409
|
+
const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
36968
36410
|
const noDanger = defineRule({
|
|
36969
36411
|
id: "no-danger",
|
|
36970
36412
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -36978,7 +36420,7 @@ const noDanger = defineRule({
|
|
|
36978
36420
|
if (!propAttribute) return;
|
|
36979
36421
|
context.report({
|
|
36980
36422
|
node: propAttribute.name,
|
|
36981
|
-
message: MESSAGE$
|
|
36423
|
+
message: MESSAGE$30
|
|
36982
36424
|
});
|
|
36983
36425
|
},
|
|
36984
36426
|
CallExpression(node) {
|
|
@@ -36990,7 +36432,7 @@ const noDanger = defineRule({
|
|
|
36990
36432
|
const propertyKey = property.key;
|
|
36991
36433
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
36992
36434
|
node: propertyKey,
|
|
36993
|
-
message: MESSAGE$
|
|
36435
|
+
message: MESSAGE$30
|
|
36994
36436
|
});
|
|
36995
36437
|
}
|
|
36996
36438
|
}
|
|
@@ -37013,7 +36455,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
37013
36455
|
};
|
|
37014
36456
|
//#endregion
|
|
37015
36457
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
37016
|
-
const MESSAGE$
|
|
36458
|
+
const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
37017
36459
|
const mergePropsShape = (target, source) => {
|
|
37018
36460
|
target.hasDangerously ||= source.hasDangerously;
|
|
37019
36461
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -37088,7 +36530,7 @@ const noDangerWithChildren = defineRule({
|
|
|
37088
36530
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
37089
36531
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
37090
36532
|
node: opening,
|
|
37091
|
-
message: MESSAGE$
|
|
36533
|
+
message: MESSAGE$29
|
|
37092
36534
|
});
|
|
37093
36535
|
},
|
|
37094
36536
|
CallExpression(node) {
|
|
@@ -37101,7 +36543,7 @@ const noDangerWithChildren = defineRule({
|
|
|
37101
36543
|
const positionalChildren = node.arguments.slice(2);
|
|
37102
36544
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
37103
36545
|
node,
|
|
37104
|
-
message: MESSAGE$
|
|
36546
|
+
message: MESSAGE$29
|
|
37105
36547
|
});
|
|
37106
36548
|
}
|
|
37107
36549
|
})
|
|
@@ -38076,7 +37518,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
38076
37518
|
//#endregion
|
|
38077
37519
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
38078
37520
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
38079
|
-
const MESSAGE$
|
|
37521
|
+
const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
38080
37522
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
38081
37523
|
let ancestor = setStateCall.parent;
|
|
38082
37524
|
while (ancestor) {
|
|
@@ -38203,7 +37645,7 @@ const noDidMountSetState = defineRule({
|
|
|
38203
37645
|
}
|
|
38204
37646
|
context.report({
|
|
38205
37647
|
node: node.callee,
|
|
38206
|
-
message: MESSAGE$
|
|
37648
|
+
message: MESSAGE$28
|
|
38207
37649
|
});
|
|
38208
37650
|
} };
|
|
38209
37651
|
}
|
|
@@ -38221,7 +37663,7 @@ const findEnclosingClass = (node) => {
|
|
|
38221
37663
|
//#endregion
|
|
38222
37664
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
38223
37665
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
38224
|
-
const MESSAGE$
|
|
37666
|
+
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
38225
37667
|
const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
|
|
38226
37668
|
const EQUALITY_OPERATORS = new Set([
|
|
38227
37669
|
"==",
|
|
@@ -38615,7 +38057,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
38615
38057
|
if (isInsideDiffGuard(node, context.scopes)) return;
|
|
38616
38058
|
context.report({
|
|
38617
38059
|
node: node.callee,
|
|
38618
|
-
message: MESSAGE$
|
|
38060
|
+
message: MESSAGE$27
|
|
38619
38061
|
});
|
|
38620
38062
|
} };
|
|
38621
38063
|
}
|
|
@@ -38638,7 +38080,7 @@ const isStateMemberExpression = (node) => {
|
|
|
38638
38080
|
};
|
|
38639
38081
|
//#endregion
|
|
38640
38082
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
38641
|
-
const MESSAGE$
|
|
38083
|
+
const MESSAGE$26 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
|
|
38642
38084
|
const shouldIgnoreMutation = (node) => {
|
|
38643
38085
|
let isConstructor = false;
|
|
38644
38086
|
let isInsideCallExpression = false;
|
|
@@ -38660,7 +38102,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
38660
38102
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
38661
38103
|
context.report({
|
|
38662
38104
|
node: reportNode,
|
|
38663
|
-
message: MESSAGE$
|
|
38105
|
+
message: MESSAGE$26
|
|
38664
38106
|
});
|
|
38665
38107
|
};
|
|
38666
38108
|
const noDirectMutationState = defineRule({
|
|
@@ -39011,7 +38453,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
39011
38453
|
});
|
|
39012
38454
|
//#endregion
|
|
39013
38455
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
39014
|
-
const MESSAGE$
|
|
38456
|
+
const MESSAGE$25 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
39015
38457
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
39016
38458
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
39017
38459
|
const noDocumentWrite = defineRule({
|
|
@@ -39028,7 +38470,7 @@ const noDocumentWrite = defineRule({
|
|
|
39028
38470
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
39029
38471
|
context.report({
|
|
39030
38472
|
node,
|
|
39031
|
-
message: MESSAGE$
|
|
38473
|
+
message: MESSAGE$25
|
|
39032
38474
|
});
|
|
39033
38475
|
} })
|
|
39034
38476
|
});
|
|
@@ -42076,7 +41518,7 @@ const IMPORT_INITIALIZER_TYPES = new Set([
|
|
|
42076
41518
|
"ImportNamespaceSpecifier"
|
|
42077
41519
|
]);
|
|
42078
41520
|
const CANCELLATION_FLAG_NAME_PATTERN = /cancel|ignore/i;
|
|
42079
|
-
const PROMISE_CONTINUATION_METHOD_NAMES
|
|
41521
|
+
const PROMISE_CONTINUATION_METHOD_NAMES = new Set([
|
|
42080
41522
|
"then",
|
|
42081
41523
|
"catch",
|
|
42082
41524
|
"finally"
|
|
@@ -42289,10 +41731,18 @@ const isCompletionSinkAfterCancellationEarlyExit = (completionSink, cancellation
|
|
|
42289
41731
|
return false;
|
|
42290
41732
|
};
|
|
42291
41733
|
const isCompletionSinkGuardedByCancellationFlag = (completionSink, cancellationFlagKey, context) => isCompletionSinkInsideCancellationGuard(completionSink, cancellationFlagKey, context) || isCompletionSinkAfterCancellationEarlyExit(completionSink, cancellationFlagKey, context);
|
|
41734
|
+
const isDescendantOf = (node, ancestor) => {
|
|
41735
|
+
let currentNode = node;
|
|
41736
|
+
while (currentNode) {
|
|
41737
|
+
if (currentNode === ancestor) return true;
|
|
41738
|
+
currentNode = currentNode.parent ?? null;
|
|
41739
|
+
}
|
|
41740
|
+
return false;
|
|
41741
|
+
};
|
|
42292
41742
|
const isPromiseContinuationForRequest = (functionNode, request) => {
|
|
42293
41743
|
const callNode = functionNode.parent;
|
|
42294
|
-
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
|
|
42295
|
-
return
|
|
41744
|
+
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.has(callNode.callee.property.name)) return false;
|
|
41745
|
+
return isDescendantOf(request, callNode.callee.object);
|
|
42296
41746
|
};
|
|
42297
41747
|
const isAwaitedInFunction = (request, functionNode) => {
|
|
42298
41748
|
let currentNode = request.parent;
|
|
@@ -42360,7 +41810,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
42360
41810
|
"ReactDOM",
|
|
42361
41811
|
"ReactDom"
|
|
42362
41812
|
]);
|
|
42363
|
-
const MESSAGE$
|
|
41813
|
+
const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
42364
41814
|
const noFindDomNode = defineRule({
|
|
42365
41815
|
id: "no-find-dom-node",
|
|
42366
41816
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -42371,7 +41821,7 @@ const noFindDomNode = defineRule({
|
|
|
42371
41821
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
42372
41822
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
42373
41823
|
node: callee,
|
|
42374
|
-
message: MESSAGE$
|
|
41824
|
+
message: MESSAGE$24
|
|
42375
41825
|
});
|
|
42376
41826
|
return;
|
|
42377
41827
|
}
|
|
@@ -42382,7 +41832,7 @@ const noFindDomNode = defineRule({
|
|
|
42382
41832
|
if (callee.property.name !== "findDOMNode") return;
|
|
42383
41833
|
context.report({
|
|
42384
41834
|
node: callee.property,
|
|
42385
|
-
message: MESSAGE$
|
|
41835
|
+
message: MESSAGE$24
|
|
42386
41836
|
});
|
|
42387
41837
|
}
|
|
42388
41838
|
} })
|
|
@@ -43520,7 +42970,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
43520
42970
|
});
|
|
43521
42971
|
//#endregion
|
|
43522
42972
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
43523
|
-
const MESSAGE$
|
|
42973
|
+
const MESSAGE$23 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
|
|
43524
42974
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
43525
42975
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
43526
42976
|
title: "Lazy image with high fetchPriority",
|
|
@@ -43534,7 +42984,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
43534
42984
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
43535
42985
|
context.report({
|
|
43536
42986
|
node: node.name,
|
|
43537
|
-
message: MESSAGE$
|
|
42987
|
+
message: MESSAGE$23
|
|
43538
42988
|
});
|
|
43539
42989
|
} })
|
|
43540
42990
|
});
|
|
@@ -43723,7 +43173,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
43723
43173
|
});
|
|
43724
43174
|
//#endregion
|
|
43725
43175
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
43726
|
-
const MESSAGE$
|
|
43176
|
+
const MESSAGE$22 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
43727
43177
|
const REACT_USE_REF_OPTIONS = {
|
|
43728
43178
|
allowGlobalReactNamespace: false,
|
|
43729
43179
|
allowUnboundBareCalls: false
|
|
@@ -43824,7 +43274,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
43824
43274
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
43825
43275
|
context.report({
|
|
43826
43276
|
node: indeterminateAttribute,
|
|
43827
|
-
message: MESSAGE$
|
|
43277
|
+
message: MESSAGE$22
|
|
43828
43278
|
});
|
|
43829
43279
|
},
|
|
43830
43280
|
CallExpression(node) {
|
|
@@ -43833,7 +43283,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
43833
43283
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
43834
43284
|
context.report({
|
|
43835
43285
|
node,
|
|
43836
|
-
message: MESSAGE$
|
|
43286
|
+
message: MESSAGE$22
|
|
43837
43287
|
});
|
|
43838
43288
|
}
|
|
43839
43289
|
};
|
|
@@ -44121,7 +43571,7 @@ const noIsMounted = defineRule({
|
|
|
44121
43571
|
});
|
|
44122
43572
|
//#endregion
|
|
44123
43573
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
44124
|
-
const MESSAGE$
|
|
43574
|
+
const MESSAGE$21 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
|
|
44125
43575
|
const isJsonMethodCall = (node, method) => {
|
|
44126
43576
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
44127
43577
|
const callee = node.callee;
|
|
@@ -44185,13 +43635,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
44185
43635
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
44186
43636
|
context.report({
|
|
44187
43637
|
node,
|
|
44188
|
-
message: MESSAGE$
|
|
43638
|
+
message: MESSAGE$21
|
|
44189
43639
|
});
|
|
44190
43640
|
} })
|
|
44191
43641
|
});
|
|
44192
43642
|
//#endregion
|
|
44193
43643
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
44194
|
-
const MESSAGE$
|
|
43644
|
+
const MESSAGE$20 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
44195
43645
|
const isJsxElementTypeReference = (node) => {
|
|
44196
43646
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
44197
43647
|
const typeName = node.typeName;
|
|
@@ -44243,7 +43693,7 @@ const noJsxElementType = defineRule({
|
|
|
44243
43693
|
if (isJsxImported) return;
|
|
44244
43694
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
44245
43695
|
node: typeAnnotation,
|
|
44246
|
-
message: MESSAGE$
|
|
43696
|
+
message: MESSAGE$20
|
|
44247
43697
|
});
|
|
44248
43698
|
}
|
|
44249
43699
|
};
|
|
@@ -45524,7 +44974,7 @@ const noMoment = defineRule({
|
|
|
45524
44974
|
});
|
|
45525
44975
|
//#endregion
|
|
45526
44976
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
45527
|
-
const MESSAGE$
|
|
44977
|
+
const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
45528
44978
|
const resolveSettings$16 = (settings) => {
|
|
45529
44979
|
const reactDoctor = settings?.["react-doctor"];
|
|
45530
44980
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -45876,7 +45326,7 @@ const noMultiComp = defineRule({
|
|
|
45876
45326
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
45877
45327
|
for (const component of flagged.slice(1)) context.report({
|
|
45878
45328
|
node: component.reportNode,
|
|
45879
|
-
message: MESSAGE$
|
|
45329
|
+
message: MESSAGE$19
|
|
45880
45330
|
});
|
|
45881
45331
|
} };
|
|
45882
45332
|
}
|
|
@@ -45964,6 +45414,41 @@ const noMutableInDeps = defineRule({
|
|
|
45964
45414
|
}
|
|
45965
45415
|
});
|
|
45966
45416
|
//#endregion
|
|
45417
|
+
//#region src/plugin/utils/is-result-discarded-call.ts
|
|
45418
|
+
const isResultDiscardedCall = (callExpression) => {
|
|
45419
|
+
let node = callExpression;
|
|
45420
|
+
let parent = node.parent;
|
|
45421
|
+
while (parent) {
|
|
45422
|
+
if (isNodeOfType(parent, "ExpressionStatement")) return true;
|
|
45423
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
|
|
45424
|
+
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
|
|
45425
|
+
if (isNodeOfType(parent, "ChainExpression")) {
|
|
45426
|
+
node = parent;
|
|
45427
|
+
parent = node.parent;
|
|
45428
|
+
continue;
|
|
45429
|
+
}
|
|
45430
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
|
|
45431
|
+
node = parent;
|
|
45432
|
+
parent = node.parent;
|
|
45433
|
+
continue;
|
|
45434
|
+
}
|
|
45435
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
|
|
45436
|
+
node = parent;
|
|
45437
|
+
parent = node.parent;
|
|
45438
|
+
continue;
|
|
45439
|
+
}
|
|
45440
|
+
if (isNodeOfType(parent, "SequenceExpression")) {
|
|
45441
|
+
const expressions = parent.expressions ?? [];
|
|
45442
|
+
if (expressions[expressions.length - 1] !== node) return true;
|
|
45443
|
+
node = parent;
|
|
45444
|
+
parent = node.parent;
|
|
45445
|
+
continue;
|
|
45446
|
+
}
|
|
45447
|
+
return false;
|
|
45448
|
+
}
|
|
45449
|
+
return false;
|
|
45450
|
+
};
|
|
45451
|
+
//#endregion
|
|
45967
45452
|
//#region src/plugin/rules/state-and-effects/utils/lodash-mutator-call.ts
|
|
45968
45453
|
const LODASH_MUTATOR_NAMES = new Set([
|
|
45969
45454
|
"set",
|
|
@@ -46054,7 +45539,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
46054
45539
|
};
|
|
46055
45540
|
//#endregion
|
|
46056
45541
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
46057
|
-
const MESSAGE$
|
|
45542
|
+
const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
|
|
46058
45543
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
46059
45544
|
"copyWithin",
|
|
46060
45545
|
"fill",
|
|
@@ -46258,7 +45743,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
46258
45743
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
46259
45744
|
context.report({
|
|
46260
45745
|
node: options.crossFileConsumerCallSite,
|
|
46261
|
-
message: `${MESSAGE$
|
|
45746
|
+
message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
46262
45747
|
});
|
|
46263
45748
|
return;
|
|
46264
45749
|
}
|
|
@@ -46267,7 +45752,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
46267
45752
|
reportedNodes.add(mutation.node);
|
|
46268
45753
|
context.report({
|
|
46269
45754
|
node: mutation.node,
|
|
46270
|
-
message: MESSAGE$
|
|
45755
|
+
message: MESSAGE$18
|
|
46271
45756
|
});
|
|
46272
45757
|
}
|
|
46273
45758
|
};
|
|
@@ -46674,7 +46159,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
46674
46159
|
});
|
|
46675
46160
|
//#endregion
|
|
46676
46161
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
46677
|
-
const MESSAGE$
|
|
46162
|
+
const MESSAGE$17 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
46678
46163
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
46679
46164
|
"onKeyDown",
|
|
46680
46165
|
"onKeyUp",
|
|
@@ -46793,7 +46278,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
46793
46278
|
if (numeric === null) {
|
|
46794
46279
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
46795
46280
|
node: tabIndex,
|
|
46796
|
-
message: MESSAGE$
|
|
46281
|
+
message: MESSAGE$17
|
|
46797
46282
|
});
|
|
46798
46283
|
return;
|
|
46799
46284
|
}
|
|
@@ -46815,7 +46300,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
46815
46300
|
if (!roleAttribute) {
|
|
46816
46301
|
context.report({
|
|
46817
46302
|
node: tabIndex,
|
|
46818
|
-
message: MESSAGE$
|
|
46303
|
+
message: MESSAGE$17
|
|
46819
46304
|
});
|
|
46820
46305
|
return;
|
|
46821
46306
|
}
|
|
@@ -46829,7 +46314,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
46829
46314
|
}
|
|
46830
46315
|
context.report({
|
|
46831
46316
|
node: tabIndex,
|
|
46832
|
-
message: MESSAGE$
|
|
46317
|
+
message: MESSAGE$17
|
|
46833
46318
|
});
|
|
46834
46319
|
} };
|
|
46835
46320
|
}
|
|
@@ -49035,7 +48520,7 @@ const noRandomKey = defineRule({
|
|
|
49035
48520
|
});
|
|
49036
48521
|
//#endregion
|
|
49037
48522
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
49038
|
-
const MESSAGE$
|
|
48523
|
+
const MESSAGE$16 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
49039
48524
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
49040
48525
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
49041
48526
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -49061,13 +48546,13 @@ const noReactChildren = defineRule({
|
|
|
49061
48546
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
49062
48547
|
context.report({
|
|
49063
48548
|
node: calleeOuter,
|
|
49064
|
-
message: MESSAGE$
|
|
48549
|
+
message: MESSAGE$16
|
|
49065
48550
|
});
|
|
49066
48551
|
return;
|
|
49067
48552
|
}
|
|
49068
48553
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
49069
48554
|
node: calleeOuter,
|
|
49070
|
-
message: MESSAGE$
|
|
48555
|
+
message: MESSAGE$16
|
|
49071
48556
|
});
|
|
49072
48557
|
} })
|
|
49073
48558
|
});
|
|
@@ -49949,7 +49434,7 @@ const noRenderPropChildren = defineRule({
|
|
|
49949
49434
|
});
|
|
49950
49435
|
//#endregion
|
|
49951
49436
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
49952
|
-
const MESSAGE$
|
|
49437
|
+
const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
49953
49438
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
49954
49439
|
const isUsedAsReturnValue = (parent) => {
|
|
49955
49440
|
if (!parent) return false;
|
|
@@ -49967,7 +49452,7 @@ const noRenderReturnValue = defineRule({
|
|
|
49967
49452
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
49968
49453
|
context.report({
|
|
49969
49454
|
node: node.callee,
|
|
49970
|
-
message: MESSAGE$
|
|
49455
|
+
message: MESSAGE$15
|
|
49971
49456
|
});
|
|
49972
49457
|
} })
|
|
49973
49458
|
});
|
|
@@ -51394,7 +50879,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
51394
50879
|
});
|
|
51395
50880
|
//#endregion
|
|
51396
50881
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
51397
|
-
const MESSAGE$
|
|
50882
|
+
const MESSAGE$14 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
51398
50883
|
const noSetState = defineRule({
|
|
51399
50884
|
id: "no-set-state",
|
|
51400
50885
|
title: "Local class state forbidden",
|
|
@@ -51409,7 +50894,7 @@ const noSetState = defineRule({
|
|
|
51409
50894
|
if (!getParentComponent(node)) return;
|
|
51410
50895
|
context.report({
|
|
51411
50896
|
node: node.callee,
|
|
51412
|
-
message: MESSAGE$
|
|
50897
|
+
message: MESSAGE$14
|
|
51413
50898
|
});
|
|
51414
50899
|
} })
|
|
51415
50900
|
});
|
|
@@ -51779,7 +51264,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
51779
51264
|
};
|
|
51780
51265
|
//#endregion
|
|
51781
51266
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
51782
|
-
const MESSAGE$
|
|
51267
|
+
const MESSAGE$13 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
|
|
51783
51268
|
const DEFAULT_HANDLERS = [
|
|
51784
51269
|
"onClick",
|
|
51785
51270
|
"onMouseDown",
|
|
@@ -51894,7 +51379,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51894
51379
|
if (!roleAttribute || !roleAttribute.value) {
|
|
51895
51380
|
context.report({
|
|
51896
51381
|
node: node.name,
|
|
51897
|
-
message: MESSAGE$
|
|
51382
|
+
message: MESSAGE$13
|
|
51898
51383
|
});
|
|
51899
51384
|
return;
|
|
51900
51385
|
}
|
|
@@ -51904,7 +51389,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51904
51389
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
51905
51390
|
context.report({
|
|
51906
51391
|
node: node.name,
|
|
51907
|
-
message: MESSAGE$
|
|
51392
|
+
message: MESSAGE$13
|
|
51908
51393
|
});
|
|
51909
51394
|
return;
|
|
51910
51395
|
}
|
|
@@ -51912,7 +51397,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51912
51397
|
if (!settings.allowExpressionValues) {
|
|
51913
51398
|
context.report({
|
|
51914
51399
|
node: node.name,
|
|
51915
|
-
message: MESSAGE$
|
|
51400
|
+
message: MESSAGE$13
|
|
51916
51401
|
});
|
|
51917
51402
|
return;
|
|
51918
51403
|
}
|
|
@@ -51920,7 +51405,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51920
51405
|
if (isStaticNullishExpression(expression)) {
|
|
51921
51406
|
context.report({
|
|
51922
51407
|
node: node.name,
|
|
51923
|
-
message: MESSAGE$
|
|
51408
|
+
message: MESSAGE$13
|
|
51924
51409
|
});
|
|
51925
51410
|
return;
|
|
51926
51411
|
}
|
|
@@ -51930,7 +51415,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51930
51415
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
51931
51416
|
context.report({
|
|
51932
51417
|
node: node.name,
|
|
51933
|
-
message: MESSAGE$
|
|
51418
|
+
message: MESSAGE$13
|
|
51934
51419
|
});
|
|
51935
51420
|
return;
|
|
51936
51421
|
}
|
|
@@ -51938,7 +51423,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
51938
51423
|
}
|
|
51939
51424
|
context.report({
|
|
51940
51425
|
node: node.name,
|
|
51941
|
-
message: MESSAGE$
|
|
51426
|
+
message: MESSAGE$13
|
|
51942
51427
|
});
|
|
51943
51428
|
} };
|
|
51944
51429
|
}
|
|
@@ -52044,7 +51529,7 @@ const noStringRefs = defineRule({
|
|
|
52044
51529
|
});
|
|
52045
51530
|
//#endregion
|
|
52046
51531
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
52047
|
-
const MESSAGE$
|
|
51532
|
+
const MESSAGE$12 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
|
|
52048
51533
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
52049
51534
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
52050
51535
|
const noSyncXhr = defineRule({
|
|
@@ -52065,7 +51550,7 @@ const noSyncXhr = defineRule({
|
|
|
52065
51550
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
52066
51551
|
context.report({
|
|
52067
51552
|
node,
|
|
52068
|
-
message: MESSAGE$
|
|
51553
|
+
message: MESSAGE$12
|
|
52069
51554
|
});
|
|
52070
51555
|
};
|
|
52071
51556
|
return {
|
|
@@ -52090,7 +51575,7 @@ const noSyncXhr = defineRule({
|
|
|
52090
51575
|
});
|
|
52091
51576
|
//#endregion
|
|
52092
51577
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
52093
|
-
const MESSAGE$
|
|
51578
|
+
const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
|
|
52094
51579
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
52095
51580
|
let ancestor = node.parent;
|
|
52096
51581
|
while (ancestor) {
|
|
@@ -52178,7 +51663,7 @@ const noThisInSfc = defineRule({
|
|
|
52178
51663
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
52179
51664
|
context.report({
|
|
52180
51665
|
node,
|
|
52181
|
-
message: MESSAGE$
|
|
51666
|
+
message: MESSAGE$11
|
|
52182
51667
|
});
|
|
52183
51668
|
} };
|
|
52184
51669
|
}
|
|
@@ -54201,7 +53686,7 @@ const isElementTypeJsxAttribute = (node) => {
|
|
|
54201
53686
|
const attributeName = node.name.name;
|
|
54202
53687
|
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
54203
53688
|
};
|
|
54204
|
-
const isReactUseMemoCallback
|
|
53689
|
+
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
54205
53690
|
allowGlobalReactNamespace: true,
|
|
54206
53691
|
resolveNamedAliases: true
|
|
54207
53692
|
});
|
|
@@ -54225,7 +53710,7 @@ const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @_
|
|
|
54225
53710
|
case "ArrowFunctionExpression": return false;
|
|
54226
53711
|
case "CallExpression":
|
|
54227
53712
|
if (parent.callee === valueNode) return false;
|
|
54228
|
-
if (isReactUseMemoCallback
|
|
53713
|
+
if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
|
|
54229
53714
|
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
54230
53715
|
valueNode = parent;
|
|
54231
53716
|
parent = parent.parent;
|
|
@@ -54548,7 +54033,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
54548
54033
|
//#endregion
|
|
54549
54034
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
54550
54035
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
54551
|
-
const MESSAGE$
|
|
54036
|
+
const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
54552
54037
|
const resolveSettings$7 = (settings) => {
|
|
54553
54038
|
const reactDoctor = settings?.["react-doctor"];
|
|
54554
54039
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -54582,7 +54067,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
54582
54067
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
54583
54068
|
context.report({
|
|
54584
54069
|
node: node.callee,
|
|
54585
|
-
message: MESSAGE$
|
|
54070
|
+
message: MESSAGE$10
|
|
54586
54071
|
});
|
|
54587
54072
|
} };
|
|
54588
54073
|
}
|
|
@@ -56382,7 +55867,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
56382
55867
|
});
|
|
56383
55868
|
//#endregion
|
|
56384
55869
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
56385
|
-
const MESSAGE$
|
|
55870
|
+
const MESSAGE$9 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
|
|
56386
55871
|
const preactPreferOndblclick = defineRule({
|
|
56387
55872
|
id: "preact-prefer-ondblclick",
|
|
56388
55873
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -56397,7 +55882,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
56397
55882
|
if (!onDoubleClickAttribute) return;
|
|
56398
55883
|
context.report({
|
|
56399
55884
|
node: onDoubleClickAttribute,
|
|
56400
|
-
message: MESSAGE$
|
|
55885
|
+
message: MESSAGE$9
|
|
56401
55886
|
});
|
|
56402
55887
|
} })
|
|
56403
55888
|
});
|
|
@@ -56637,7 +56122,7 @@ const preferExplicitVariants = defineRule({
|
|
|
56637
56122
|
});
|
|
56638
56123
|
//#endregion
|
|
56639
56124
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
56640
|
-
const MESSAGE$
|
|
56125
|
+
const MESSAGE$8 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
|
|
56641
56126
|
const resolveSettings$4 = (settings) => {
|
|
56642
56127
|
const reactDoctor = settings?.["react-doctor"];
|
|
56643
56128
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -56676,7 +56161,7 @@ const preferFunctionComponent = defineRule({
|
|
|
56676
56161
|
const reportNode = node.id ?? node;
|
|
56677
56162
|
context.report({
|
|
56678
56163
|
node: reportNode,
|
|
56679
|
-
message: MESSAGE$
|
|
56164
|
+
message: MESSAGE$8
|
|
56680
56165
|
});
|
|
56681
56166
|
};
|
|
56682
56167
|
return {
|
|
@@ -57997,8 +57482,7 @@ const preferUseReducer = defineRule({
|
|
|
57997
57482
|
//#endregion
|
|
57998
57483
|
//#region src/plugin/rules/security-scan/utils/is-public-debug-artifact-path.ts
|
|
57999
57484
|
const LOCALE_DIRECTORY_PATTERN = /(?:^|\/)(?:locales?|i18n|lang|langs|translations?)\//i;
|
|
58000
|
-
const
|
|
58001
|
-
const isPublicDebugArtifactPath = (relativePath) => isBrowserArtifactPath(relativePath, GENERATED_BUNDLE_FILE_PATTERN.test(relativePath)) && !LOCALE_DIRECTORY_PATTERN.test(relativePath) && PUBLIC_DEBUG_ARTIFACT_PATH_PATTERN.test(relativePath);
|
|
57485
|
+
const isPublicDebugArtifactPath = (relativePath) => isBrowserArtifactPath(relativePath, GENERATED_BUNDLE_FILE_PATTERN.test(relativePath)) && !LOCALE_DIRECTORY_PATTERN.test(relativePath) && /(?:^|\/)(?:\.env(?:\.[^/]*)?|[^/]*(?:debug|crash|trace|stack|report|dump|phpinfo)[^/]*\.(?:txt|log|json|html?)|[^/]+\.log)$/i.test(relativePath);
|
|
58002
57486
|
//#endregion
|
|
58003
57487
|
//#region src/plugin/rules/security-scan/public-debug-artifact.ts
|
|
58004
57488
|
const publicDebugArtifact = defineRule({
|
|
@@ -58877,7 +58361,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
58877
58361
|
});
|
|
58878
58362
|
//#endregion
|
|
58879
58363
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
58880
|
-
const MESSAGE$
|
|
58364
|
+
const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
|
|
58881
58365
|
const reactInJsxScope = defineRule({
|
|
58882
58366
|
id: "react-in-jsx-scope",
|
|
58883
58367
|
title: "React not in scope for JSX",
|
|
@@ -58889,21 +58373,21 @@ const reactInJsxScope = defineRule({
|
|
|
58889
58373
|
if (findVariableInitializer(node, "React")) return;
|
|
58890
58374
|
context.report({
|
|
58891
58375
|
node: node.name,
|
|
58892
|
-
message: MESSAGE$
|
|
58376
|
+
message: MESSAGE$7
|
|
58893
58377
|
});
|
|
58894
58378
|
},
|
|
58895
58379
|
JSXFragment(node) {
|
|
58896
58380
|
if (findVariableInitializer(node, "React")) return;
|
|
58897
58381
|
context.report({
|
|
58898
58382
|
node: node.openingFragment,
|
|
58899
|
-
message: MESSAGE$
|
|
58383
|
+
message: MESSAGE$7
|
|
58900
58384
|
});
|
|
58901
58385
|
}
|
|
58902
58386
|
})
|
|
58903
58387
|
});
|
|
58904
58388
|
//#endregion
|
|
58905
58389
|
//#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
|
|
58906
|
-
const MESSAGE$
|
|
58390
|
+
const MESSAGE$6 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
|
|
58907
58391
|
const REACT_MARKDOWN_MODULE = "react-markdown";
|
|
58908
58392
|
const REHYPE_RAW_MODULE = "rehype-raw";
|
|
58909
58393
|
const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
|
|
@@ -59055,7 +58539,7 @@ const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
|
59055
58539
|
if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
|
|
59056
58540
|
context.report({
|
|
59057
58541
|
node,
|
|
59058
|
-
message: MESSAGE$
|
|
58542
|
+
message: MESSAGE$6
|
|
59059
58543
|
});
|
|
59060
58544
|
} }))
|
|
59061
58545
|
});
|
|
@@ -59111,7 +58595,7 @@ const inlineUseSelectorFunction = (callNode, aliases) => {
|
|
|
59111
58595
|
};
|
|
59112
58596
|
//#endregion
|
|
59113
58597
|
//#region src/plugin/rules/state-and-effects/redux-useselector-inline-derivation.ts
|
|
59114
|
-
const ALLOCATING_ARRAY_METHODS
|
|
58598
|
+
const ALLOCATING_ARRAY_METHODS = new Set([
|
|
59115
58599
|
"filter",
|
|
59116
58600
|
"map",
|
|
59117
58601
|
"flatMap",
|
|
@@ -59147,7 +58631,7 @@ const getAllocatingCallSiteDescription = (expression) => {
|
|
|
59147
58631
|
method: methodName
|
|
59148
58632
|
};
|
|
59149
58633
|
}
|
|
59150
|
-
if (ALLOCATING_ARRAY_METHODS
|
|
58634
|
+
if (ALLOCATING_ARRAY_METHODS.has(methodName)) return {
|
|
59151
58635
|
kind: "method",
|
|
59152
58636
|
method: methodName
|
|
59153
58637
|
};
|
|
@@ -59210,7 +58694,7 @@ const reduxUseselectorInlineDerivation = defineRule({
|
|
|
59210
58694
|
});
|
|
59211
58695
|
//#endregion
|
|
59212
58696
|
//#region src/plugin/rules/state-and-effects/redux-useselector-returns-new-collection.ts
|
|
59213
|
-
const MESSAGE$
|
|
58697
|
+
const MESSAGE$5 = "Your component re-renders on every dispatched action when useSelector returns a new object or array.";
|
|
59214
58698
|
const isConciseBodyReturningCollection = (functionNode) => {
|
|
59215
58699
|
if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression")) return false;
|
|
59216
58700
|
const rawBody = functionNode.body;
|
|
@@ -59245,567 +58729,13 @@ const reduxUseselectorReturnsNewCollection = defineRule({
|
|
|
59245
58729
|
if (!isConciseBodyReturningCollection(selectorArgument)) return;
|
|
59246
58730
|
context.report({
|
|
59247
58731
|
node,
|
|
59248
|
-
message: MESSAGE$
|
|
58732
|
+
message: MESSAGE$5
|
|
59249
58733
|
});
|
|
59250
58734
|
}
|
|
59251
58735
|
};
|
|
59252
58736
|
}
|
|
59253
58737
|
});
|
|
59254
58738
|
//#endregion
|
|
59255
|
-
//#region src/plugin/utils/is-remotion-module-source.ts
|
|
59256
|
-
const isRemotionModuleSource = (moduleSource) => moduleSource === "remotion" || moduleSource.startsWith("@remotion/");
|
|
59257
|
-
//#endregion
|
|
59258
|
-
//#region src/plugin/utils/resolve-remotion-api.ts
|
|
59259
|
-
const resolveRemotionApi = (referenceNode, scopes) => {
|
|
59260
|
-
const candidate = stripParenExpression(referenceNode);
|
|
59261
|
-
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "JSXIdentifier")) {
|
|
59262
|
-
const symbol = scopes.symbolFor(candidate);
|
|
59263
|
-
if (symbol?.kind !== "import") return null;
|
|
59264
|
-
const importBinding = getImportBindingForName(candidate, symbol.name);
|
|
59265
|
-
if (!importBinding || importBinding.isNamespace || importBinding.exportedName === null || !isRemotionModuleSource(importBinding.source)) return null;
|
|
59266
|
-
return {
|
|
59267
|
-
apiName: importBinding.exportedName,
|
|
59268
|
-
moduleSource: importBinding.source
|
|
59269
|
-
};
|
|
59270
|
-
}
|
|
59271
|
-
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
59272
|
-
const apiName = getStaticPropertyKeyName(candidate, { allowComputedString: true });
|
|
59273
|
-
const namespaceObject = stripParenExpression(candidate.object);
|
|
59274
|
-
if (!apiName || !isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
59275
|
-
const symbol = scopes.symbolFor(namespaceObject);
|
|
59276
|
-
if (symbol?.kind !== "import") return null;
|
|
59277
|
-
const importBinding = getImportBindingForName(namespaceObject, symbol.name);
|
|
59278
|
-
if (!importBinding?.isNamespace || !isRemotionModuleSource(importBinding.source)) return null;
|
|
59279
|
-
return {
|
|
59280
|
-
apiName,
|
|
59281
|
-
moduleSource: importBinding.source
|
|
59282
|
-
};
|
|
59283
|
-
}
|
|
59284
|
-
if (!isNodeOfType(candidate, "JSXMemberExpression") || !isNodeOfType(candidate.object, "JSXIdentifier") || !isNodeOfType(candidate.property, "JSXIdentifier")) return null;
|
|
59285
|
-
const symbol = scopes.symbolFor(candidate.object);
|
|
59286
|
-
if (symbol?.kind !== "import") return null;
|
|
59287
|
-
const importBinding = getImportBindingForName(candidate.object, symbol.name);
|
|
59288
|
-
if (!importBinding?.isNamespace || !isRemotionModuleSource(importBinding.source)) return null;
|
|
59289
|
-
return {
|
|
59290
|
-
apiName: candidate.property.name,
|
|
59291
|
-
moduleSource: importBinding.source
|
|
59292
|
-
};
|
|
59293
|
-
};
|
|
59294
|
-
//#endregion
|
|
59295
|
-
//#region src/plugin/utils/create-remotion-composition-ownership-analyzer.ts
|
|
59296
|
-
const compositionAttributeFunctionCacheBySettings = /* @__PURE__ */ new WeakMap();
|
|
59297
|
-
const getFunctionExportKeys = (filePath, programNode, functionNode) => getFunctionExportNames(programNode, functionNode).map((exportedName) => `${normalizeFilename(filePath)}\0${exportedName}`);
|
|
59298
|
-
const resolveImportedCompositionFunction = (expression, module) => {
|
|
59299
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
59300
|
-
let importReference;
|
|
59301
|
-
let exportedName;
|
|
59302
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
59303
|
-
const symbol = module.scopes.symbolFor(unwrappedExpression);
|
|
59304
|
-
if (symbol?.kind !== "import") return null;
|
|
59305
|
-
importReference = unwrappedExpression;
|
|
59306
|
-
exportedName = getImportBindingForName(unwrappedExpression, symbol.name)?.exportedName ?? null;
|
|
59307
|
-
} else if (isNodeOfType(unwrappedExpression, "MemberExpression") && isNodeOfType(stripParenExpression(unwrappedExpression.object), "Identifier")) {
|
|
59308
|
-
const namespaceObject = stripParenExpression(unwrappedExpression.object);
|
|
59309
|
-
if (!isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
59310
|
-
const symbol = module.scopes.symbolFor(namespaceObject);
|
|
59311
|
-
if (symbol?.kind !== "import") return null;
|
|
59312
|
-
if (!getImportBindingForName(namespaceObject, symbol.name)?.isNamespace) return null;
|
|
59313
|
-
importReference = namespaceObject;
|
|
59314
|
-
exportedName = getStaticPropertyKeyName(unwrappedExpression, { allowComputedString: true });
|
|
59315
|
-
} else return null;
|
|
59316
|
-
if (!exportedName) return null;
|
|
59317
|
-
const importBinding = getImportBindingForName(importReference, importReference.name);
|
|
59318
|
-
if (!importBinding) return null;
|
|
59319
|
-
return resolveCrossFileFunctionExportWithFilePath(module.filePath, importBinding.source, exportedName);
|
|
59320
|
-
};
|
|
59321
|
-
const collectCompositionAttributeFunctionKeys = (context, currentProgram, attributeName) => {
|
|
59322
|
-
const filename = context.filename ? normalizeFilename(context.filename) : "";
|
|
59323
|
-
const rootDirectorySetting = getReactDoctorStringSetting(context.settings, "rootDirectory");
|
|
59324
|
-
const rootDirectory = rootDirectorySetting ? normalizeFilename(rootDirectorySetting).replace(/\/$/, "") : "";
|
|
59325
|
-
if (!filename || !rootDirectory || filename !== rootDirectory && !filename.startsWith(`${rootDirectory}/`)) return null;
|
|
59326
|
-
const projectIndex = buildSourceProjectIndex(rootDirectory, filename, currentProgram, context.scopes);
|
|
59327
|
-
if (!projectIndex) return null;
|
|
59328
|
-
const functionKeys = /* @__PURE__ */ new Set();
|
|
59329
|
-
for (const module of projectIndex.modulesByFilePath.values()) walkAst(module.programNode, (candidate) => {
|
|
59330
|
-
if (!isNodeOfType(candidate, "JSXOpeningElement")) return;
|
|
59331
|
-
const apiBinding = resolveRemotionApi(candidate.name, module.scopes);
|
|
59332
|
-
if (apiBinding?.apiName !== "Composition" || apiBinding.moduleSource !== "remotion") return;
|
|
59333
|
-
const functionAttribute = findJsxAttribute(candidate.attributes, attributeName);
|
|
59334
|
-
if (!functionAttribute?.value || !isNodeOfType(functionAttribute.value, "JSXExpressionContainer") || !functionAttribute.value.expression) return;
|
|
59335
|
-
const resolvedFunction = resolveImportedCompositionFunction(functionAttribute.value.expression, module);
|
|
59336
|
-
if (!resolvedFunction || !isNodeOfType(resolvedFunction.programNode, "Program")) return;
|
|
59337
|
-
for (const functionKey of getFunctionExportKeys(resolvedFunction.filePath, resolvedFunction.programNode, resolvedFunction.functionNode)) functionKeys.add(functionKey);
|
|
59338
|
-
});
|
|
59339
|
-
return functionKeys;
|
|
59340
|
-
};
|
|
59341
|
-
const createRemotionCompositionAttributeOwnershipAnalyzer = (context, attributeName) => {
|
|
59342
|
-
const settings = context.settings;
|
|
59343
|
-
return (functionNode) => {
|
|
59344
|
-
const currentProgram = findProgramRoot(functionNode);
|
|
59345
|
-
if (!currentProgram || !isNodeOfType(currentProgram, "Program") || !context.filename || !settings) return false;
|
|
59346
|
-
const currentFunctionKeys = getFunctionExportKeys(context.filename, currentProgram, functionNode);
|
|
59347
|
-
if (currentFunctionKeys.length === 0) return false;
|
|
59348
|
-
let cacheByAttributeName = compositionAttributeFunctionCacheBySettings.get(settings);
|
|
59349
|
-
if (!cacheByAttributeName) {
|
|
59350
|
-
cacheByAttributeName = /* @__PURE__ */ new Map();
|
|
59351
|
-
compositionAttributeFunctionCacheBySettings.set(settings, cacheByAttributeName);
|
|
59352
|
-
}
|
|
59353
|
-
let cache = cacheByAttributeName.get(attributeName);
|
|
59354
|
-
if (!cache) {
|
|
59355
|
-
cache = { failedFilenames: /* @__PURE__ */ new Set() };
|
|
59356
|
-
cacheByAttributeName.set(attributeName, cache);
|
|
59357
|
-
}
|
|
59358
|
-
let functionKeys = cache.functionKeys;
|
|
59359
|
-
if (!functionKeys) {
|
|
59360
|
-
const filename = normalizeFilename(context.filename);
|
|
59361
|
-
if (cache.failedFilenames.has(filename)) return false;
|
|
59362
|
-
const collectedFunctionKeys = collectCompositionAttributeFunctionKeys(context, currentProgram, attributeName);
|
|
59363
|
-
if (!collectedFunctionKeys) {
|
|
59364
|
-
cache.failedFilenames.add(filename);
|
|
59365
|
-
return false;
|
|
59366
|
-
}
|
|
59367
|
-
functionKeys = collectedFunctionKeys;
|
|
59368
|
-
cache.functionKeys = collectedFunctionKeys;
|
|
59369
|
-
}
|
|
59370
|
-
return currentFunctionKeys.some((functionKey) => functionKeys.has(functionKey));
|
|
59371
|
-
};
|
|
59372
|
-
};
|
|
59373
|
-
const createRemotionCompositionOwnershipAnalyzer = (context) => createRemotionCompositionAttributeOwnershipAnalyzer(context, "component");
|
|
59374
|
-
const createRemotionMetadataOwnershipAnalyzer = (context) => createRemotionCompositionAttributeOwnershipAnalyzer(context, "calculateMetadata");
|
|
59375
|
-
//#endregion
|
|
59376
|
-
//#region src/plugin/rules/correctness/remotion-calculate-metadata-fetch-signal.ts
|
|
59377
|
-
const getParameterIdentifier = (parameter) => {
|
|
59378
|
-
if (isNodeOfType(parameter, "Identifier")) return parameter;
|
|
59379
|
-
if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
|
|
59380
|
-
return null;
|
|
59381
|
-
};
|
|
59382
|
-
const getAbortSignalBinding = (functionNode) => {
|
|
59383
|
-
if (!isFunctionLike$1(functionNode)) return {
|
|
59384
|
-
parameterIdentifier: null,
|
|
59385
|
-
signalIdentifier: null
|
|
59386
|
-
};
|
|
59387
|
-
const firstParameter = functionNode.params[0];
|
|
59388
|
-
const parameterIdentifier = getParameterIdentifier(firstParameter);
|
|
59389
|
-
if (parameterIdentifier) return {
|
|
59390
|
-
parameterIdentifier,
|
|
59391
|
-
signalIdentifier: null
|
|
59392
|
-
};
|
|
59393
|
-
if (!isNodeOfType(firstParameter, "ObjectPattern")) return {
|
|
59394
|
-
parameterIdentifier: null,
|
|
59395
|
-
signalIdentifier: null
|
|
59396
|
-
};
|
|
59397
|
-
for (const property of firstParameter.properties) {
|
|
59398
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
59399
|
-
if (getStaticPropertyKeyName(property) !== "abortSignal") continue;
|
|
59400
|
-
const signalIdentifier = getParameterIdentifier(property.value);
|
|
59401
|
-
if (signalIdentifier) return {
|
|
59402
|
-
parameterIdentifier: null,
|
|
59403
|
-
signalIdentifier
|
|
59404
|
-
};
|
|
59405
|
-
}
|
|
59406
|
-
return {
|
|
59407
|
-
parameterIdentifier: null,
|
|
59408
|
-
signalIdentifier: null
|
|
59409
|
-
};
|
|
59410
|
-
};
|
|
59411
|
-
const identifiersResolveToSameSymbol = (leftIdentifier, rightIdentifier, scopes) => {
|
|
59412
|
-
if (!isNodeOfType(leftIdentifier, "Identifier") || !isNodeOfType(rightIdentifier, "Identifier")) return false;
|
|
59413
|
-
const leftSymbol = scopes.symbolFor(leftIdentifier);
|
|
59414
|
-
return Boolean(leftSymbol && leftSymbol === scopes.symbolFor(rightIdentifier));
|
|
59415
|
-
};
|
|
59416
|
-
const isMetadataAbortSignal = (expression, binding, scopes) => {
|
|
59417
|
-
const candidate = stripParenExpression(expression);
|
|
59418
|
-
if (binding.signalIdentifier && identifiersResolveToSameSymbol(candidate, binding.signalIdentifier, scopes)) return true;
|
|
59419
|
-
return Boolean(binding.parameterIdentifier && isNodeOfType(candidate, "MemberExpression") && getStaticPropertyKeyName(candidate, { allowComputedString: true }) === "abortSignal" && identifiersResolveToSameSymbol(candidate.object, binding.parameterIdentifier, scopes));
|
|
59420
|
-
};
|
|
59421
|
-
const fetchUsesMetadataAbortSignal = (fetchCall, binding, scopes) => {
|
|
59422
|
-
const optionsArgument = fetchCall.arguments[1];
|
|
59423
|
-
if (!optionsArgument) return false;
|
|
59424
|
-
const options = stripParenExpression(optionsArgument);
|
|
59425
|
-
if (!isNodeOfType(options, "ObjectExpression")) return null;
|
|
59426
|
-
if (options.properties.some((property) => isNodeOfType(property, "SpreadElement"))) return null;
|
|
59427
|
-
for (let propertyIndex = options.properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
|
|
59428
|
-
const property = options.properties[propertyIndex];
|
|
59429
|
-
if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "signal") return isMetadataAbortSignal(property.value, binding, scopes);
|
|
59430
|
-
}
|
|
59431
|
-
return false;
|
|
59432
|
-
};
|
|
59433
|
-
const reportFetchesWithoutAbortSignal = (functionNode, context) => {
|
|
59434
|
-
const binding = getAbortSignalBinding(functionNode);
|
|
59435
|
-
walkAst(functionNode, (candidate) => {
|
|
59436
|
-
if (candidate !== functionNode && isFunctionLike$1(candidate)) return false;
|
|
59437
|
-
if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "Identifier") || candidate.callee.name !== "fetch" || !context.scopes.isGlobalReference(candidate.callee) || fetchUsesMetadataAbortSignal(candidate, binding, context.scopes) !== false) return;
|
|
59438
|
-
context.report({
|
|
59439
|
-
node: candidate,
|
|
59440
|
-
message: "Pass Remotion's `abortSignal` to this fetch with `{signal: abortSignal}` so superseded metadata requests are cancelled."
|
|
59441
|
-
});
|
|
59442
|
-
});
|
|
59443
|
-
};
|
|
59444
|
-
const isCalculateMetadataFunctionType = (variableDeclarator) => {
|
|
59445
|
-
if (!isNodeOfType(variableDeclarator.id, "Identifier")) return false;
|
|
59446
|
-
const annotation = variableDeclarator.id.typeAnnotation;
|
|
59447
|
-
if (!isNodeOfType(annotation, "TSTypeAnnotation") || !isNodeOfType(annotation.typeAnnotation, "TSTypeReference")) return false;
|
|
59448
|
-
const typeName = annotation.typeAnnotation.typeName;
|
|
59449
|
-
if (!isNodeOfType(typeName, "Identifier")) return false;
|
|
59450
|
-
const apiBinding = getImportBindingForName(typeName, typeName.name);
|
|
59451
|
-
return Boolean(apiBinding?.exportedName === "CalculateMetadataFunction" && apiBinding.source === "remotion");
|
|
59452
|
-
};
|
|
59453
|
-
const remotionCalculateMetadataFetchSignal = defineRule({
|
|
59454
|
-
id: "remotion-calculate-metadata-fetch-signal",
|
|
59455
|
-
title: "calculateMetadata fetch ignores abortSignal",
|
|
59456
|
-
tags: ["react-jsx-only"],
|
|
59457
|
-
requires: ["remotion:4"],
|
|
59458
|
-
severity: "error",
|
|
59459
|
-
recommendation: "Destructure `abortSignal` from the calculateMetadata argument and pass it to direct fetch calls as `{signal: abortSignal}`.",
|
|
59460
|
-
create: (context) => {
|
|
59461
|
-
const analyzedFunctions = /* @__PURE__ */ new WeakSet();
|
|
59462
|
-
const isOwnedByCalculateMetadata = createRemotionMetadataOwnershipAnalyzer(context);
|
|
59463
|
-
const analyzeFunction = (functionNode) => {
|
|
59464
|
-
if (!functionNode || analyzedFunctions.has(functionNode)) return;
|
|
59465
|
-
analyzedFunctions.add(functionNode);
|
|
59466
|
-
reportFetchesWithoutAbortSignal(functionNode, context);
|
|
59467
|
-
};
|
|
59468
|
-
return {
|
|
59469
|
-
CallExpression(node) {
|
|
59470
|
-
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "fetch" || !context.scopes.isGlobalReference(node.callee)) return;
|
|
59471
|
-
const functionNode = findEnclosingFunction$1(node);
|
|
59472
|
-
if (functionNode && isOwnedByCalculateMetadata(functionNode)) analyzeFunction(functionNode);
|
|
59473
|
-
},
|
|
59474
|
-
JSXOpeningElement(node) {
|
|
59475
|
-
const apiBinding = resolveRemotionApi(node.name, context.scopes);
|
|
59476
|
-
if (apiBinding?.apiName !== "Composition" || apiBinding.moduleSource !== "remotion") return;
|
|
59477
|
-
const calculateMetadataAttribute = findJsxAttribute(node.attributes, "calculateMetadata");
|
|
59478
|
-
if (!calculateMetadataAttribute?.value || !isNodeOfType(calculateMetadataAttribute.value, "JSXExpressionContainer") || !calculateMetadataAttribute.value.expression) return;
|
|
59479
|
-
analyzeFunction(resolveExactLocalFunction(calculateMetadataAttribute.value.expression, context.scopes));
|
|
59480
|
-
},
|
|
59481
|
-
VariableDeclarator(node) {
|
|
59482
|
-
if (!node.init || !isCalculateMetadataFunctionType(node)) return;
|
|
59483
|
-
analyzeFunction(resolveExactLocalFunction(node.init, context.scopes));
|
|
59484
|
-
}
|
|
59485
|
-
};
|
|
59486
|
-
}
|
|
59487
|
-
});
|
|
59488
|
-
//#endregion
|
|
59489
|
-
//#region src/plugin/utils/create-remotion-render-evidence-checker.ts
|
|
59490
|
-
const REMOTION_RENDER_CALL_NAMES = new Set([
|
|
59491
|
-
"continueRender",
|
|
59492
|
-
"delayRender",
|
|
59493
|
-
"getInputProps",
|
|
59494
|
-
"random",
|
|
59495
|
-
"spring",
|
|
59496
|
-
"useCurrentFrame",
|
|
59497
|
-
"useDelayRender",
|
|
59498
|
-
"useVideoConfig"
|
|
59499
|
-
]);
|
|
59500
|
-
const REMOTION_RENDER_COMPONENT_MODULE_BY_NAME = new Map([
|
|
59501
|
-
["Audio", "@remotion/media"],
|
|
59502
|
-
["Freeze", "remotion"],
|
|
59503
|
-
["IFrame", "remotion"],
|
|
59504
|
-
["Img", "remotion"],
|
|
59505
|
-
["Loop", "remotion"],
|
|
59506
|
-
["OffthreadVideo", "remotion"],
|
|
59507
|
-
["Sequence", "remotion"],
|
|
59508
|
-
["Series", "remotion"],
|
|
59509
|
-
["Video", "@remotion/media"]
|
|
59510
|
-
]);
|
|
59511
|
-
const createRemotionRenderEvidenceChecker = (context) => {
|
|
59512
|
-
const scopes = context.scopes;
|
|
59513
|
-
const evidenceByFunction = /* @__PURE__ */ new WeakMap();
|
|
59514
|
-
const registeredCompositionFunctions = /* @__PURE__ */ new WeakSet();
|
|
59515
|
-
const inspectedPrograms = /* @__PURE__ */ new WeakSet();
|
|
59516
|
-
const isOwnedByRegisteredComposition = createRemotionCompositionOwnershipAnalyzer(context);
|
|
59517
|
-
const collectRegisteredCompositionFunctions = (functionNode) => {
|
|
59518
|
-
const program = findProgramRoot(functionNode);
|
|
59519
|
-
if (!program || inspectedPrograms.has(program)) return;
|
|
59520
|
-
inspectedPrograms.add(program);
|
|
59521
|
-
walkAst(program, (candidate) => {
|
|
59522
|
-
if (!isNodeOfType(candidate, "JSXOpeningElement")) return;
|
|
59523
|
-
const apiBinding = resolveRemotionApi(candidate.name, scopes);
|
|
59524
|
-
if (apiBinding?.apiName !== "Composition" || apiBinding.moduleSource !== "remotion") return;
|
|
59525
|
-
const componentAttribute = findJsxAttribute(candidate.attributes, "component");
|
|
59526
|
-
if (!componentAttribute?.value || !isNodeOfType(componentAttribute.value, "JSXExpressionContainer") || !componentAttribute.value.expression) return;
|
|
59527
|
-
const registeredFunction = resolveExactLocalFunction(componentAttribute.value.expression, scopes);
|
|
59528
|
-
if (registeredFunction) registeredCompositionFunctions.add(registeredFunction);
|
|
59529
|
-
});
|
|
59530
|
-
};
|
|
59531
|
-
const functionUsesRemotionRenderApi = (functionNode) => {
|
|
59532
|
-
let hasEvidence = false;
|
|
59533
|
-
walkAst(functionNode, (candidate) => {
|
|
59534
|
-
if (hasEvidence) return false;
|
|
59535
|
-
if (!isNodeOfType(candidate, "CallExpression") && !isNodeOfType(candidate, "JSXOpeningElement")) return;
|
|
59536
|
-
const apiBinding = resolveRemotionApi(isNodeOfType(candidate, "CallExpression") ? candidate.callee : candidate.name, scopes);
|
|
59537
|
-
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) {
|
|
59538
|
-
hasEvidence = true;
|
|
59539
|
-
return false;
|
|
59540
|
-
}
|
|
59541
|
-
});
|
|
59542
|
-
return hasEvidence;
|
|
59543
|
-
};
|
|
59544
|
-
return { functionHasEvidence: (functionNode) => {
|
|
59545
|
-
const cachedEvidence = evidenceByFunction.get(functionNode);
|
|
59546
|
-
if (cachedEvidence !== void 0) return cachedEvidence;
|
|
59547
|
-
collectRegisteredCompositionFunctions(functionNode);
|
|
59548
|
-
const hasEvidence = registeredCompositionFunctions.has(functionNode) || functionUsesRemotionRenderApi(functionNode) || isOwnedByRegisteredComposition(functionNode);
|
|
59549
|
-
evidenceByFunction.set(functionNode, hasEvidence);
|
|
59550
|
-
return hasEvidence;
|
|
59551
|
-
} };
|
|
59552
|
-
};
|
|
59553
|
-
//#endregion
|
|
59554
|
-
//#region src/plugin/rules/correctness/remotion-deterministic-randomness.ts
|
|
59555
|
-
const isGlobalMathObject = (node, scopes) => {
|
|
59556
|
-
const candidate = stripParenExpression(node);
|
|
59557
|
-
if (isNodeOfType(candidate, "Identifier")) return candidate.name === "Math" && scopes.isGlobalReference(candidate);
|
|
59558
|
-
return Boolean(isNodeOfType(candidate, "MemberExpression") && getStaticPropertyKeyName(candidate, { allowComputedString: true }) === "Math" && isNodeOfType(candidate.object, "Identifier") && candidate.object.name === "globalThis" && scopes.isGlobalReference(candidate.object));
|
|
59559
|
-
};
|
|
59560
|
-
const remotionDeterministicRandomness = defineRule({
|
|
59561
|
-
id: "remotion-deterministic-randomness",
|
|
59562
|
-
title: "Randomness changes between rendered frames",
|
|
59563
|
-
tags: ["react-jsx-only"],
|
|
59564
|
-
requires: ["remotion:4"],
|
|
59565
|
-
severity: "error",
|
|
59566
|
-
recommendation: "Use Remotion's seeded `random(seed)` helper so the same frame produces the same value in every render tab.",
|
|
59567
|
-
create: (context) => {
|
|
59568
|
-
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59569
|
-
return { CallExpression(node) {
|
|
59570
|
-
const callee = stripParenExpression(node.callee);
|
|
59571
|
-
if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyKeyName(callee, { allowComputedString: true }) !== "random" || !isGlobalMathObject(callee.object, context.scopes)) return;
|
|
59572
|
-
const componentOrHook = findRenderPhaseComponentOrHook(node, context.scopes) ?? findEnclosingFunction$1(node);
|
|
59573
|
-
if (!componentOrHook || !renderEvidence.functionHasEvidence(componentOrHook)) return;
|
|
59574
|
-
const displayName = componentOrHookDisplayNameForFunction(componentOrHook);
|
|
59575
|
-
if (displayName && !isReactHookName(displayName) && !functionHasReactComponentEvidence(componentOrHook, context.scopes, context.cfg)) return;
|
|
59576
|
-
context.report({
|
|
59577
|
-
node,
|
|
59578
|
-
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."
|
|
59579
|
-
});
|
|
59580
|
-
} };
|
|
59581
|
-
}
|
|
59582
|
-
});
|
|
59583
|
-
//#endregion
|
|
59584
|
-
//#region src/plugin/utils/create-remotion-css-time-rule-visitors.ts
|
|
59585
|
-
const createRemotionCssTimeRuleVisitors = (context, options) => {
|
|
59586
|
-
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59587
|
-
return {
|
|
59588
|
-
Property(node) {
|
|
59589
|
-
if (!options.stylePropertyNames.has(getStaticPropertyKeyName(node) ?? "")) return;
|
|
59590
|
-
const styleObject = node.parent;
|
|
59591
|
-
const expressionContainer = styleObject?.parent;
|
|
59592
|
-
const styleAttribute = expressionContainer?.parent;
|
|
59593
|
-
if (!isNodeOfType(styleObject, "ObjectExpression") || !isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== styleObject || !isNodeOfType(styleAttribute, "JSXAttribute") || !isNodeOfType(styleAttribute.name, "JSXIdentifier") || styleAttribute.name.name !== "style") return;
|
|
59594
|
-
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59595
|
-
if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
|
|
59596
|
-
context.report({
|
|
59597
|
-
node,
|
|
59598
|
-
message: options.styleMessage
|
|
59599
|
-
});
|
|
59600
|
-
},
|
|
59601
|
-
JSXAttribute(node) {
|
|
59602
|
-
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "className") return;
|
|
59603
|
-
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59604
|
-
if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
|
|
59605
|
-
const className = getJsxAttributeStaticString(node);
|
|
59606
|
-
if (!className) return;
|
|
59607
|
-
if (className.split(/\s+/).filter(Boolean).some((classToken) => options.classTokenIsForbidden(classToken.split(":").at(-1) ?? ""))) context.report({
|
|
59608
|
-
node,
|
|
59609
|
-
message: options.classMessage
|
|
59610
|
-
});
|
|
59611
|
-
}
|
|
59612
|
-
};
|
|
59613
|
-
};
|
|
59614
|
-
//#endregion
|
|
59615
|
-
//#region src/plugin/rules/correctness/remotion-no-css-animation.ts
|
|
59616
|
-
const ANIMATION_STYLE_PROPERTY_NAMES = new Set(["animation", "animationName"]);
|
|
59617
|
-
const remotionNoCssAnimation = defineRule({
|
|
59618
|
-
id: "remotion-no-css-animation",
|
|
59619
|
-
title: "CSS animation is not frame-driven",
|
|
59620
|
-
tags: ["react-jsx-only"],
|
|
59621
|
-
requires: ["remotion:4"],
|
|
59622
|
-
severity: "error",
|
|
59623
|
-
recommendation: "Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",
|
|
59624
|
-
create: (context) => createRemotionCssTimeRuleVisitors(context, {
|
|
59625
|
-
classTokenIsForbidden: (classToken) => classToken.startsWith("animate-") && classToken !== "animate-none",
|
|
59626
|
-
classMessage: "Tailwind animations advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
|
|
59627
|
-
styleMessage: "CSS animations advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
|
|
59628
|
-
stylePropertyNames: ANIMATION_STYLE_PROPERTY_NAMES
|
|
59629
|
-
})
|
|
59630
|
-
});
|
|
59631
|
-
//#endregion
|
|
59632
|
-
//#region src/plugin/rules/correctness/remotion-no-css-transition.ts
|
|
59633
|
-
const TRANSITION_STYLE_PROPERTY_NAMES = new Set(["transition", "transitionProperty"]);
|
|
59634
|
-
const remotionNoCssTransition = defineRule({
|
|
59635
|
-
id: "remotion-no-css-transition",
|
|
59636
|
-
title: "CSS transition is not frame-driven",
|
|
59637
|
-
tags: ["react-jsx-only"],
|
|
59638
|
-
requires: ["remotion:4"],
|
|
59639
|
-
severity: "error",
|
|
59640
|
-
recommendation: "Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",
|
|
59641
|
-
create: (context) => createRemotionCssTimeRuleVisitors(context, {
|
|
59642
|
-
classTokenIsForbidden: (classToken) => (classToken === "transition" || classToken.startsWith("transition-")) && classToken !== "transition-none",
|
|
59643
|
-
classMessage: "Tailwind transitions advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
|
|
59644
|
-
styleMessage: "CSS transitions advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
|
|
59645
|
-
stylePropertyNames: TRANSITION_STYLE_PROPERTY_NAMES
|
|
59646
|
-
})
|
|
59647
|
-
});
|
|
59648
|
-
//#endregion
|
|
59649
|
-
//#region src/plugin/rules/correctness/remotion-no-css-url-assets.ts
|
|
59650
|
-
const CSS_URL_ASSET_PROPERTY_NAMES = new Set([
|
|
59651
|
-
"backgroundImage",
|
|
59652
|
-
"maskImage",
|
|
59653
|
-
"WebkitMaskImage"
|
|
59654
|
-
]);
|
|
59655
|
-
const CSS_URL_PATTERN = /\burl\(\s*(["']?)([^"')]+)\1\s*\)/i;
|
|
59656
|
-
const isEmbeddedAssetSource = (assetSource) => assetSource.startsWith("data:") || assetSource.startsWith("#");
|
|
59657
|
-
const getStaticStringExpression = (node) => {
|
|
59658
|
-
if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
|
|
59659
|
-
if (isNodeOfType(node, "TemplateLiteral") && node.expressions.length === 0 && node.quasis.length === 1) return node.quasis[0].value.raw;
|
|
59660
|
-
return null;
|
|
59661
|
-
};
|
|
59662
|
-
const isReactUseMemoCallback = (node, scopes) => {
|
|
59663
|
-
const parent = node.parent;
|
|
59664
|
-
return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments[0] === node && isReactApiCall(parent, "useMemo", scopes, { resolveNamedAliases: true }));
|
|
59665
|
-
};
|
|
59666
|
-
const componentPreloadsStaticImage = (componentNode, assetSource, scopes) => {
|
|
59667
|
-
let hasPreload = false;
|
|
59668
|
-
walkAst(componentNode, (child) => {
|
|
59669
|
-
if (hasPreload) return false;
|
|
59670
|
-
if (child !== componentNode && isFunctionLike$1(child) && !isReactUseMemoCallback(child, scopes)) return false;
|
|
59671
|
-
if (!isNodeOfType(child, "JSXOpeningElement")) return;
|
|
59672
|
-
const apiBinding = resolveRemotionApi(child.name, scopes);
|
|
59673
|
-
if (apiBinding?.apiName !== "Img" || apiBinding.moduleSource !== "remotion") return;
|
|
59674
|
-
const sourceAttribute = findJsxAttribute(child.attributes, "src");
|
|
59675
|
-
if (sourceAttribute && getJsxAttributeStaticString(sourceAttribute) === assetSource) {
|
|
59676
|
-
hasPreload = true;
|
|
59677
|
-
return false;
|
|
59678
|
-
}
|
|
59679
|
-
});
|
|
59680
|
-
return hasPreload;
|
|
59681
|
-
};
|
|
59682
|
-
const remotionNoCssUrlAssets = defineRule({
|
|
59683
|
-
id: "remotion-no-css-url-assets",
|
|
59684
|
-
title: "CSS URL asset can flicker in Remotion",
|
|
59685
|
-
tags: ["react-jsx-only"],
|
|
59686
|
-
requires: ["remotion:4"],
|
|
59687
|
-
severity: "error",
|
|
59688
|
-
recommendation: "Render the asset with `Img` inside an `AbsoluteFill`, or preload the same source with a hidden `Img` when a CSS mask is required.",
|
|
59689
|
-
create: (context) => {
|
|
59690
|
-
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59691
|
-
return { Property(node) {
|
|
59692
|
-
if (!CSS_URL_ASSET_PROPERTY_NAMES.has(getStaticPropertyKeyName(node) ?? "")) return;
|
|
59693
|
-
const styleObject = node.parent;
|
|
59694
|
-
const expressionContainer = styleObject?.parent;
|
|
59695
|
-
const styleAttribute = expressionContainer?.parent;
|
|
59696
|
-
if (!isNodeOfType(styleObject, "ObjectExpression") || !isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== styleObject || !isNodeOfType(styleAttribute, "JSXAttribute") || !isNodeOfType(styleAttribute.name, "JSXIdentifier") || styleAttribute.name.name !== "style") return;
|
|
59697
|
-
const cssValue = getStaticStringExpression(node.value);
|
|
59698
|
-
const urlMatch = cssValue ? CSS_URL_PATTERN.exec(cssValue) : null;
|
|
59699
|
-
if (!urlMatch) return;
|
|
59700
|
-
const assetSource = urlMatch[2].trim();
|
|
59701
|
-
if (isEmbeddedAssetSource(assetSource)) return;
|
|
59702
|
-
const componentNode = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59703
|
-
if (!componentNode || !renderEvidence.functionHasEvidence(componentNode) || componentPreloadsStaticImage(componentNode, assetSource, context.scopes)) return;
|
|
59704
|
-
context.report({
|
|
59705
|
-
node,
|
|
59706
|
-
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."
|
|
59707
|
-
});
|
|
59708
|
-
} };
|
|
59709
|
-
}
|
|
59710
|
-
});
|
|
59711
|
-
//#endregion
|
|
59712
|
-
//#region src/plugin/rules/correctness/remotion-no-module-scope-delay-render.ts
|
|
59713
|
-
const remotionNoModuleScopeDelayRender = defineRule({
|
|
59714
|
-
id: "remotion-no-module-scope-delay-render",
|
|
59715
|
-
title: "Module-scoped delayRender blocks every composition",
|
|
59716
|
-
requires: ["remotion:4"],
|
|
59717
|
-
severity: "error",
|
|
59718
|
-
recommendation: "Create the handle once inside the component. Use `useDelayRender()` on Remotion 4.0.342 or newer, or lazy `useState(() => delayRender())` on earlier versions.",
|
|
59719
|
-
create: (context) => ({ CallExpression(node) {
|
|
59720
|
-
const apiBinding = resolveRemotionApi(node.callee, context.scopes);
|
|
59721
|
-
if (apiBinding?.apiName !== "delayRender" || apiBinding.moduleSource !== "remotion" || findEnclosingFunction$1(node)) return;
|
|
59722
|
-
context.report({
|
|
59723
|
-
node,
|
|
59724
|
-
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."
|
|
59725
|
-
});
|
|
59726
|
-
} })
|
|
59727
|
-
});
|
|
59728
|
-
//#endregion
|
|
59729
|
-
//#region src/plugin/rules/correctness/remotion-no-native-media-elements.ts
|
|
59730
|
-
const REMOTION_MEDIA_REPLACEMENT_BY_TAG = new Map([
|
|
59731
|
-
["audio", "`Audio` from `@remotion/media`"],
|
|
59732
|
-
["iframe", "`IFrame` from `remotion`"],
|
|
59733
|
-
["img", "`Img` from `remotion`"],
|
|
59734
|
-
["video", "`Video` from `@remotion/media`"]
|
|
59735
|
-
]);
|
|
59736
|
-
const remotionNoNativeMediaElements = defineRule({
|
|
59737
|
-
id: "remotion-no-native-media-elements",
|
|
59738
|
-
title: "Native media element bypasses Remotion loading",
|
|
59739
|
-
tags: ["react-jsx-only"],
|
|
59740
|
-
requires: ["remotion:4"],
|
|
59741
|
-
severity: "error",
|
|
59742
|
-
recommendation: "Use Remotion's media components so rendering waits for assets and seeks media to the requested frame.",
|
|
59743
|
-
create: (context) => {
|
|
59744
|
-
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59745
|
-
return { JSXOpeningElement(node) {
|
|
59746
|
-
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
59747
|
-
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59748
|
-
if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
|
|
59749
|
-
const replacement = REMOTION_MEDIA_REPLACEMENT_BY_TAG.get(node.name.name);
|
|
59750
|
-
if (!replacement) return;
|
|
59751
|
-
context.report({
|
|
59752
|
-
node,
|
|
59753
|
-
message: `Native <${node.name.name}> does not let Remotion reliably wait for and synchronize the asset. Use ${replacement} instead.`
|
|
59754
|
-
});
|
|
59755
|
-
} };
|
|
59756
|
-
}
|
|
59757
|
-
});
|
|
59758
|
-
//#endregion
|
|
59759
|
-
//#region src/plugin/rules/correctness/remotion-no-next-image.ts
|
|
59760
|
-
const remotionNoNextImage = defineRule({
|
|
59761
|
-
id: "remotion-no-next-image",
|
|
59762
|
-
title: "Next.js Image can flicker in Remotion",
|
|
59763
|
-
tags: ["react-jsx-only"],
|
|
59764
|
-
requires: ["remotion:4"],
|
|
59765
|
-
severity: "error",
|
|
59766
|
-
recommendation: "Use `Img` from `remotion`, which delays rendering until the image is loaded.",
|
|
59767
|
-
create: (context) => {
|
|
59768
|
-
const renderEvidence = createRemotionRenderEvidenceChecker(context);
|
|
59769
|
-
return { JSXOpeningElement(node) {
|
|
59770
|
-
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
59771
|
-
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
59772
|
-
if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
|
|
59773
|
-
const symbol = context.scopes.symbolFor(node.name);
|
|
59774
|
-
if (symbol?.kind !== "import") return;
|
|
59775
|
-
const importBinding = getImportBindingForName(node.name, symbol.name);
|
|
59776
|
-
if (importBinding?.source !== "next/image" || importBinding.exportedName !== "default") return;
|
|
59777
|
-
context.report({
|
|
59778
|
-
node,
|
|
59779
|
-
message: "Next.js <Image> does not expose a reliable loading signal to Remotion, so rendered frames can flicker. Use <Img> from `remotion` instead."
|
|
59780
|
-
});
|
|
59781
|
-
} };
|
|
59782
|
-
}
|
|
59783
|
-
});
|
|
59784
|
-
//#endregion
|
|
59785
|
-
//#region src/plugin/rules/correctness/remotion-stable-delay-render-handle.ts
|
|
59786
|
-
const isUseStateLazyInitializer = (node, scopes) => {
|
|
59787
|
-
const enclosingFunction = findEnclosingFunction$1(node);
|
|
59788
|
-
if (!enclosingFunction) return false;
|
|
59789
|
-
const parent = enclosingFunction.parent;
|
|
59790
|
-
return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments[0] === enclosingFunction && isReactApiCall(parent, "useState", scopes, { resolveNamedAliases: true }));
|
|
59791
|
-
};
|
|
59792
|
-
const remotionStableDelayRenderHandle = defineRule({
|
|
59793
|
-
id: "remotion-stable-delay-render-handle",
|
|
59794
|
-
title: "delayRender handle is recreated during render",
|
|
59795
|
-
tags: ["react-jsx-only"],
|
|
59796
|
-
requires: ["remotion:4"],
|
|
59797
|
-
severity: "error",
|
|
59798
|
-
recommendation: "Prefer `useDelayRender()`, or initialize `delayRender()` once with `useState(() => delayRender())`.",
|
|
59799
|
-
create: (context) => ({ CallExpression(node) {
|
|
59800
|
-
const apiBinding = resolveRemotionApi(node.callee, context.scopes);
|
|
59801
|
-
if (apiBinding?.apiName !== "delayRender" || apiBinding.moduleSource !== "remotion" || !findRenderPhaseComponentOrHook(node, context.scopes) || isUseStateLazyInitializer(node, context.scopes)) return;
|
|
59802
|
-
context.report({
|
|
59803
|
-
node,
|
|
59804
|
-
message: "Calling `delayRender()` during every component render creates another outstanding handle and can make rendering time out. Use `useDelayRender()` or a lazy `useState` initializer."
|
|
59805
|
-
});
|
|
59806
|
-
} })
|
|
59807
|
-
});
|
|
59808
|
-
//#endregion
|
|
59809
58739
|
//#region src/plugin/rules/performance/rendering-animate-svg-wrapper.ts
|
|
59810
58740
|
const renderingAnimateSvgWrapper = defineRule({
|
|
59811
58741
|
id: "rendering-animate-svg-wrapper",
|
|
@@ -60926,7 +59856,7 @@ const functionBodyHasReturnWithValue = (functionNode) => {
|
|
|
60926
59856
|
//#endregion
|
|
60927
59857
|
//#region src/plugin/rules/react-builtins/require-render-return.ts
|
|
60928
59858
|
const RENDER_METHOD_NAME = "render";
|
|
60929
|
-
const MESSAGE$
|
|
59859
|
+
const MESSAGE$4 = "Your users see nothing because this `render` method returns nothing.";
|
|
60930
59860
|
const isStaticRenderKey = (key) => {
|
|
60931
59861
|
if (isNodeOfType(key, "Identifier")) return key.name === RENDER_METHOD_NAME;
|
|
60932
59862
|
if (isNodeOfType(key, "Literal")) return key.value === RENDER_METHOD_NAME;
|
|
@@ -60986,7 +59916,7 @@ const requireRenderReturn = defineRule({
|
|
|
60986
59916
|
if (functionBodyHasReturnWithValue(functionNode)) return;
|
|
60987
59917
|
context.report({
|
|
60988
59918
|
node: host.reportNode,
|
|
60989
|
-
message: MESSAGE$
|
|
59919
|
+
message: MESSAGE$4
|
|
60990
59920
|
});
|
|
60991
59921
|
};
|
|
60992
59922
|
return {
|
|
@@ -62235,85 +61165,6 @@ const isSetterCalledDuringRender = (root, setterName) => {
|
|
|
62235
61165
|
return found;
|
|
62236
61166
|
};
|
|
62237
61167
|
//#endregion
|
|
62238
|
-
//#region src/plugin/utils/is-descendant-without-function-boundary.ts
|
|
62239
|
-
const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
|
|
62240
|
-
let current = descendant;
|
|
62241
|
-
while (current && current !== ancestor) {
|
|
62242
|
-
if (current !== descendant && isFunctionLike$1(current)) return false;
|
|
62243
|
-
current = current.parent;
|
|
62244
|
-
}
|
|
62245
|
-
return current === ancestor;
|
|
62246
|
-
};
|
|
62247
|
-
//#endregion
|
|
62248
|
-
//#region src/plugin/utils/can-execute-before-async-suspension.ts
|
|
62249
|
-
const collectSuspensionNodes = (functionNode, options) => {
|
|
62250
|
-
const suspensionNodes = new Set(options.suspensionNodes);
|
|
62251
|
-
walkAst(functionNode, (node) => {
|
|
62252
|
-
if (node !== functionNode && isFunctionLike$1(node)) return false;
|
|
62253
|
-
if (!options.suspensionNodes && isNodeOfType(node, "AwaitExpression") || isNodeOfType(node, "ForOfStatement") && node.await) suspensionNodes.add(node);
|
|
62254
|
-
});
|
|
62255
|
-
return suspensionNodes;
|
|
62256
|
-
};
|
|
62257
|
-
const isInsideForAwaitPostSuspensionRegion = (node, functionNode) => {
|
|
62258
|
-
let current = node;
|
|
62259
|
-
while (current && current !== functionNode) {
|
|
62260
|
-
const parent = current.parent;
|
|
62261
|
-
if (parent && isNodeOfType(parent, "ForOfStatement") && parent.await && (parent.left === current || parent.body === current)) return true;
|
|
62262
|
-
current = parent;
|
|
62263
|
-
}
|
|
62264
|
-
return false;
|
|
62265
|
-
};
|
|
62266
|
-
const suspensionBlockNode = (suspensionNode) => isNodeOfType(suspensionNode, "ForOfStatement") ? suspensionNode.right : suspensionNode;
|
|
62267
|
-
const doesSuspensionOccurBeforeNode = (suspensionNode, node) => {
|
|
62268
|
-
if (isNodeOfType(suspensionNode, "AwaitExpression")) {
|
|
62269
|
-
if (isDescendantWithoutFunctionBoundary(node, suspensionNode.argument)) return false;
|
|
62270
|
-
} else if (isNodeOfType(suspensionNode, "ForOfStatement") && isDescendantWithoutFunctionBoundary(node, suspensionNode.right)) return false;
|
|
62271
|
-
if (isDescendantWithoutFunctionBoundary(suspensionNode, node)) return true;
|
|
62272
|
-
const suspensionStart = getRangeStart(suspensionBlockNode(suspensionNode));
|
|
62273
|
-
const nodeStart = getRangeStart(node);
|
|
62274
|
-
return suspensionStart !== null && nodeStart !== null && suspensionStart < nodeStart;
|
|
62275
|
-
};
|
|
62276
|
-
const findExecutionNodeWithinFunction = (node, functionNode, context) => {
|
|
62277
|
-
let executionNode = node;
|
|
62278
|
-
let executionOwner = context.cfg.enclosingFunction(executionNode);
|
|
62279
|
-
while (executionOwner && executionOwner !== functionNode) {
|
|
62280
|
-
if (!isFunctionLike$1(executionOwner) || executionOwner.async || executionOwner.generator) return null;
|
|
62281
|
-
const callExpression = findImmediatelyInvokedCallExpression(executionOwner);
|
|
62282
|
-
if (!callExpression) return null;
|
|
62283
|
-
executionNode = callExpression;
|
|
62284
|
-
executionOwner = context.cfg.enclosingFunction(callExpression);
|
|
62285
|
-
}
|
|
62286
|
-
return executionOwner === functionNode ? executionNode : null;
|
|
62287
|
-
};
|
|
62288
|
-
const canExecuteBeforeAsyncSuspension = (node, functionNode, context, options = {}) => {
|
|
62289
|
-
if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, context);
|
|
62290
|
-
const executionNode = findExecutionNodeWithinFunction(node, functionNode, context);
|
|
62291
|
-
if (!executionNode || isInsideForAwaitPostSuspensionRegion(executionNode, functionNode)) return false;
|
|
62292
|
-
const functionCfg = context.cfg.cfgFor(functionNode);
|
|
62293
|
-
const targetBlock = functionCfg?.blockOf(executionNode);
|
|
62294
|
-
if (!functionCfg || !targetBlock) return false;
|
|
62295
|
-
const suspensionsByBlock = /* @__PURE__ */ new Map();
|
|
62296
|
-
for (const suspensionNode of collectSuspensionNodes(functionNode, options)) {
|
|
62297
|
-
const suspensionBlock = functionCfg.blockOf(suspensionBlockNode(suspensionNode));
|
|
62298
|
-
if (!suspensionBlock) continue;
|
|
62299
|
-
const blockSuspensions = suspensionsByBlock.get(suspensionBlock) ?? [];
|
|
62300
|
-
blockSuspensions.push(suspensionNode);
|
|
62301
|
-
suspensionsByBlock.set(suspensionBlock, blockSuspensions);
|
|
62302
|
-
}
|
|
62303
|
-
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
62304
|
-
const pendingBlocks = [functionCfg.entry];
|
|
62305
|
-
while (pendingBlocks.length > 0) {
|
|
62306
|
-
const block = pendingBlocks.pop();
|
|
62307
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
62308
|
-
visitedBlocks.add(block);
|
|
62309
|
-
const blockSuspensions = suspensionsByBlock.get(block) ?? [];
|
|
62310
|
-
if (block === targetBlock) return !blockSuspensions.some((suspensionNode) => doesSuspensionOccurBeforeNode(suspensionNode, executionNode));
|
|
62311
|
-
if (blockSuspensions.length > 0) continue;
|
|
62312
|
-
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
62313
|
-
}
|
|
62314
|
-
return false;
|
|
62315
|
-
};
|
|
62316
|
-
//#endregion
|
|
62317
61168
|
//#region src/plugin/rules/state-and-effects/utils/create-external-location-invalidation-checker.ts
|
|
62318
61169
|
const HISTORY_LOCATION_MUTATION_METHOD_NAMES = new Set(["pushState", "replaceState"]);
|
|
62319
61170
|
const AGGREGATE_MUTATION_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
|
|
@@ -62506,6 +61357,14 @@ const buildLocationInvalidationIndex = (componentBody, componentFunction, contex
|
|
|
62506
61357
|
});
|
|
62507
61358
|
return index;
|
|
62508
61359
|
};
|
|
61360
|
+
const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
|
|
61361
|
+
let current = descendant;
|
|
61362
|
+
while (current && current !== ancestor) {
|
|
61363
|
+
if (current !== descendant && isFunctionLike$1(current)) return false;
|
|
61364
|
+
current = current.parent;
|
|
61365
|
+
}
|
|
61366
|
+
return current === ancestor;
|
|
61367
|
+
};
|
|
62509
61368
|
const areInMutuallyExclusiveConditionalBranches = (firstNode, secondNode) => {
|
|
62510
61369
|
const firstBranches = /* @__PURE__ */ new Map();
|
|
62511
61370
|
let current = firstNode;
|
|
@@ -62817,7 +61676,39 @@ const canNodeReachNormalFunctionExit = (node, functionNode, index) => {
|
|
|
62817
61676
|
}
|
|
62818
61677
|
return false;
|
|
62819
61678
|
};
|
|
62820
|
-
const
|
|
61679
|
+
const canExecuteBeforeAsyncSuspension = (node, functionNode, index) => {
|
|
61680
|
+
if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, index.context);
|
|
61681
|
+
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
61682
|
+
const targetBlock = functionCfg?.blockOf(node);
|
|
61683
|
+
if (!functionCfg || !targetBlock) return false;
|
|
61684
|
+
const awaitsByBlock = /* @__PURE__ */ new Map();
|
|
61685
|
+
for (const awaitExpression of index.awaitExpressionsByOwner.get(functionNode) ?? []) {
|
|
61686
|
+
const awaitBlock = functionCfg.blockOf(awaitExpression);
|
|
61687
|
+
if (!awaitBlock) continue;
|
|
61688
|
+
const blockAwaits = awaitsByBlock.get(awaitBlock) ?? [];
|
|
61689
|
+
blockAwaits.push(awaitExpression);
|
|
61690
|
+
awaitsByBlock.set(awaitBlock, blockAwaits);
|
|
61691
|
+
}
|
|
61692
|
+
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61693
|
+
const pendingBlocks = [functionCfg.entry];
|
|
61694
|
+
const targetStart = getRangeStart(node);
|
|
61695
|
+
while (pendingBlocks.length > 0) {
|
|
61696
|
+
const block = pendingBlocks.pop();
|
|
61697
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
61698
|
+
visitedBlocks.add(block);
|
|
61699
|
+
const blockAwaits = awaitsByBlock.get(block) ?? [];
|
|
61700
|
+
if (block === targetBlock) return !blockAwaits.some((awaitExpression) => {
|
|
61701
|
+
if (isNodeOfType(awaitExpression, "AwaitExpression") && isDescendantWithoutFunctionBoundary(node, awaitExpression.argument)) return false;
|
|
61702
|
+
if (isDescendantWithoutFunctionBoundary(awaitExpression, node)) return true;
|
|
61703
|
+
const awaitStart = getRangeStart(awaitExpression);
|
|
61704
|
+
return awaitStart !== null && targetStart !== null && awaitStart < targetStart;
|
|
61705
|
+
});
|
|
61706
|
+
if (blockAwaits.length > 0) continue;
|
|
61707
|
+
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
61708
|
+
}
|
|
61709
|
+
return false;
|
|
61710
|
+
};
|
|
61711
|
+
const canReactBatchMutationAfterExecution = (executionNode, mutationNode, owner, index) => (isExclusiveIntrinsicReactEventHandler(owner, index) || index.effectCallbacks.has(owner)) && canNodeReachNode(executionNode, mutationNode, index) && canExecuteBeforeAsyncSuspension(mutationNode, owner, index) && canNodeReachNormalFunctionExit(mutationNode, owner, index);
|
|
62821
61712
|
const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFunctions, cycleAffectedFunctions) => {
|
|
62822
61713
|
const cachedResult = index.synchronousMutationResultByFunction.get(functionNode);
|
|
62823
61714
|
if (cachedResult !== void 0) return cachedResult;
|
|
@@ -62831,7 +61722,7 @@ const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFun
|
|
|
62831
61722
|
return false;
|
|
62832
61723
|
}
|
|
62833
61724
|
visitingFunctions.add(functionNode);
|
|
62834
|
-
const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index
|
|
61725
|
+
const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index) && canNodeReachNormalFunctionExit(mutationExecution, functionNode, index));
|
|
62835
61726
|
visitingFunctions.delete(functionNode);
|
|
62836
61727
|
if (doesMutateSynchronously || !cycleAffectedFunctions.has(functionNode)) index.synchronousMutationResultByFunction.set(functionNode, doesMutateSynchronously);
|
|
62837
61728
|
return doesMutateSynchronously;
|
|
@@ -62858,20 +61749,20 @@ const functionMustSynchronouslyRemoveLocationListener = (functionNode, registrat
|
|
|
62858
61749
|
for (const removal of index.listenerRemovals) {
|
|
62859
61750
|
if (!isDefinitelyMatchingLocationListenerRemoval(registration, removal)) continue;
|
|
62860
61751
|
if (index.context.cfg.enclosingFunction(removal.callExpression) !== functionNode) continue;
|
|
62861
|
-
if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index
|
|
61752
|
+
if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.push(removal.callExpression);
|
|
62862
61753
|
}
|
|
62863
61754
|
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
62864
61755
|
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
62865
|
-
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index
|
|
61756
|
+
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, nextVisitingFunctions)) removalExecutions.push(expression);
|
|
62866
61757
|
}
|
|
62867
61758
|
return doNodesCoverEveryPathFromFunctionEntry(functionNode, removalExecutions, index.context);
|
|
62868
61759
|
};
|
|
62869
61760
|
const collectSynchronousLocationListenerRemovalExecutions = (functionNode, registration, index) => {
|
|
62870
61761
|
const removalExecutions = /* @__PURE__ */ new Set();
|
|
62871
|
-
for (const removal of index.listenerRemovals) if (isDefinitelyMatchingLocationListenerRemoval(registration, removal) && index.context.cfg.enclosingFunction(removal.callExpression) === functionNode && canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index
|
|
61762
|
+
for (const removal of index.listenerRemovals) if (isDefinitelyMatchingLocationListenerRemoval(registration, removal) && index.context.cfg.enclosingFunction(removal.callExpression) === functionNode && canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.add(removal.callExpression);
|
|
62872
61763
|
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
62873
61764
|
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
62874
|
-
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index
|
|
61765
|
+
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, /* @__PURE__ */ new Set())) removalExecutions.add(expression);
|
|
62875
61766
|
}
|
|
62876
61767
|
return removalExecutions;
|
|
62877
61768
|
};
|
|
@@ -69577,7 +68468,6 @@ const countOwnScopeHookCalls = (functionNode, scopes, settings, countedFunctionN
|
|
|
69577
68468
|
};
|
|
69578
68469
|
const MIN_HOOK_CALLS_FOR_RENDER_SCOPE = 2;
|
|
69579
68470
|
const RENDER_SCOPE_FACTORY_NAME_PATTERN = /^_?(?:init|create|make|build)(?:[A-Z0-9_]|$)/;
|
|
69580
|
-
const HOOK_NAMESPACE_NAME_PATTERN = /Hooks?$/;
|
|
69581
68471
|
const isLocalNonHookFunctionCallee = (call, scopes, settings, countedFunctionNodes) => {
|
|
69582
68472
|
const callee = call.callee;
|
|
69583
68473
|
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
@@ -69602,15 +68492,6 @@ const isPackageImportedNonReactHookCallee = (call) => {
|
|
|
69602
68492
|
if (PATH_ALIAS_IMPORT_PATTERN.test(importSource)) return false;
|
|
69603
68493
|
return !isReactEcosystemImportSource(importSource);
|
|
69604
68494
|
};
|
|
69605
|
-
const isDefaultImportedClassApiMemberCallee = (call) => {
|
|
69606
|
-
const callee = call.callee;
|
|
69607
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
69608
|
-
if (!isNodeOfType(callee.object, "Identifier")) return false;
|
|
69609
|
-
if (HOOK_NAMESPACE_NAME_PATTERN.test(callee.object.name)) return false;
|
|
69610
|
-
const importBinding = getImportBindingForName(call, callee.object.name);
|
|
69611
|
-
if (!importBinding || importBinding.exportedName !== "default") return false;
|
|
69612
|
-
return !REACT_RUNTIME_MODULE_SOURCES.has(importBinding.source);
|
|
69613
|
-
};
|
|
69614
68495
|
const isProjectOwnedMdxComponentsGetter = (call) => {
|
|
69615
68496
|
const callee = call.callee;
|
|
69616
68497
|
if (!isNodeOfType(callee, "Identifier") || callee.name !== "useMDXComponents") return false;
|
|
@@ -69760,7 +68641,6 @@ const rulesOfHooks = defineRule({
|
|
|
69760
68641
|
return;
|
|
69761
68642
|
}
|
|
69762
68643
|
if (isInsideClassComponent(node)) {
|
|
69763
|
-
if (isPackageImportedNonReactHookCallee(node) || isDefaultImportedClassApiMemberCallee(node)) return;
|
|
69764
68644
|
context.report({
|
|
69765
68645
|
node: node.callee,
|
|
69766
68646
|
message: buildClassComponentMessage(hookName)
|
|
@@ -69867,7 +68747,7 @@ const rulesOfHooks = defineRule({
|
|
|
69867
68747
|
});
|
|
69868
68748
|
//#endregion
|
|
69869
68749
|
//#region src/plugin/rules/a11y/scope.ts
|
|
69870
|
-
const MESSAGE$
|
|
68750
|
+
const MESSAGE$3 = "The `scope` attribute only works on `<th>` cells, so screen readers get no table-header help from it here.";
|
|
69871
68751
|
const scope = defineRule({
|
|
69872
68752
|
id: "scope",
|
|
69873
68753
|
title: "scope attribute on non-th element",
|
|
@@ -69882,7 +68762,7 @@ const scope = defineRule({
|
|
|
69882
68762
|
if (!HTML_TAGS.has(tag)) return;
|
|
69883
68763
|
if (tag !== "th") context.report({
|
|
69884
68764
|
node: scopeAttribute,
|
|
69885
|
-
message: MESSAGE$
|
|
68765
|
+
message: MESSAGE$3
|
|
69886
68766
|
});
|
|
69887
68767
|
} })
|
|
69888
68768
|
});
|
|
@@ -69893,13 +68773,13 @@ const secretInFallback = defineRule({
|
|
|
69893
68773
|
recommendation: "Remove the literal fallback and fail closed (throw when the variable is unset). The hardcoded value is a committed secret, and the `??`/`||` default makes the app run with it in any environment that forgot to set the var.",
|
|
69894
68774
|
scan: scanByPattern({
|
|
69895
68775
|
shouldScan: (file) => isProductionSourcePath(file.relativePath),
|
|
69896
|
-
pattern: /\bprocess\.env\.(?!(?:(?:(?:NEXT|EXPO|GATSBY|NUXT|REACT_APP|VITE)_)?PUBLIC_[A-Z0-9_]*(?<!_SECRET)(?<!_PRIVATE_KEY)(?<!_PASSWORD)(?<!_PASSWD)|[A-Z0-9_]*(?:PUBLISHABLE|ANON)_KEY)(?![A-Z0-9_]))[A-Z][A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE_KEY|API_?KEY|APIKEY|ACCESS_KEY|CLIENT_SECRET|CREDENTIAL|SIGNING_KEY|ENCRYPTION_KEY|WEBHOOK_SECRET|SERVICE_ROLE)[A-Z0-9_]*(?<!_(?:NAME|HEADER|ENDPOINT|URL|URI|ID|PREFIX|SUFFIX|PARAM|PARAMS|FIELD|ISSUER|AUDIENCE|ALGORITHM|ALG|REGION|BUCKET|HOST|HOSTNAME|PORT|PATH|VERSION|SCOPE|TYPE|FORMAT|EXPIRY|TTL))\s*(?:\?\?|\|\|)\s*(["'`])(?![0-9]+["'`])(?![a-z][a-z0-9]*(?:[_-][a-z0-9]+)*[_-](?:token|secret|key|password|passwd|credential)s?["'`])(?![\w-]{0,12}0{8,}["'`])(?!
|
|
68776
|
+
pattern: /\bprocess\.env\.(?!(?:(?:(?:NEXT|EXPO|GATSBY|NUXT|REACT_APP|VITE)_)?PUBLIC_[A-Z0-9_]*(?<!_SECRET)(?<!_PRIVATE_KEY)(?<!_PASSWORD)(?<!_PASSWD)|[A-Z0-9_]*(?:PUBLISHABLE|ANON)_KEY)(?![A-Z0-9_]))[A-Z][A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE_KEY|API_?KEY|APIKEY|ACCESS_KEY|CLIENT_SECRET|CREDENTIAL|SIGNING_KEY|ENCRYPTION_KEY|WEBHOOK_SECRET|SERVICE_ROLE)[A-Z0-9_]*(?<!_(?:NAME|HEADER|ENDPOINT|URL|URI|ID|PREFIX|SUFFIX|PARAM|PARAMS|FIELD|ISSUER|AUDIENCE|ALGORITHM|ALG|REGION|BUCKET|HOST|HOSTNAME|PORT|PATH|VERSION|SCOPE|TYPE|FORMAT|EXPIRY|TTL))\s*(?:\?\?|\|\|)\s*(["'`])(?![0-9]+["'`])(?![a-z][a-z0-9]*(?:[_-][a-z0-9]+)*[_-](?:token|secret|key|password|passwd|credential)s?["'`])(?![\w-]{0,12}0{8,}["'`])(?!(?:changeme|change[_-]?me|placeholder|your[_-]|example|sample|dummy|development|local|todo|replace[_-]?me|https?:\/\/|x{3,}|\*{3,}))[^"'`\n]{8,}\1/i,
|
|
69897
68777
|
message: "A secret env var has a hardcoded string fallback: the literal is a committed secret and the app fails open (uses it) when the variable is unset."
|
|
69898
68778
|
})
|
|
69899
68779
|
});
|
|
69900
68780
|
//#endregion
|
|
69901
68781
|
//#region src/plugin/rules/react-builtins/self-closing-comp.ts
|
|
69902
|
-
const MESSAGE$
|
|
68782
|
+
const MESSAGE$2 = "This tag has no children, so the closing tag adds noise without changing output.";
|
|
69903
68783
|
const resolveSettings$2 = (settings) => {
|
|
69904
68784
|
const reactDoctor = settings?.["react-doctor"];
|
|
69905
68785
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.selfClosingComp ?? {} : {};
|
|
@@ -69938,7 +68818,7 @@ const selfClosingComp = defineRule({
|
|
|
69938
68818
|
if (isHtmlTag && !settings.html) return;
|
|
69939
68819
|
context.report({
|
|
69940
68820
|
node: node.openingElement,
|
|
69941
|
-
message: MESSAGE$
|
|
68821
|
+
message: MESSAGE$2
|
|
69942
68822
|
});
|
|
69943
68823
|
} };
|
|
69944
68824
|
}
|
|
@@ -70163,30 +69043,7 @@ const isAuthGuardName = (calleeName) => {
|
|
|
70163
69043
|
//#endregion
|
|
70164
69044
|
//#region src/plugin/utils/is-non-privileged-server-action.ts
|
|
70165
69045
|
const NON_DATA_EFFECT_FUNCTION_NAMES = new Set([...CACHE_REVALIDATION_FUNCTION_NAMES, ...NEXTJS_NAVIGATION_FUNCTIONS]);
|
|
70166
|
-
const
|
|
70167
|
-
const LOCALE_PREFERENCE_NOUN_TOKENS = new Set(["language", "locale"]);
|
|
70168
|
-
const COOKIE_DELETION_METHOD_NAME = "delete";
|
|
70169
|
-
const isCacheOrNavigationCall = (node) => {
|
|
70170
|
-
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return false;
|
|
70171
|
-
return NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name) && getImportSourceForName(node, node.callee.name) !== null;
|
|
70172
|
-
};
|
|
70173
|
-
const isCallerScopedLocalePreferenceCall = (node) => {
|
|
70174
|
-
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.arguments?.length !== 1) return false;
|
|
70175
|
-
const nameTokens = tokenizeIdentifierWords(node.callee.name);
|
|
70176
|
-
if (!nameTokens.some((token) => LOCALE_PREFERENCE_VERB_TOKENS.has(token))) return false;
|
|
70177
|
-
if (!nameTokens.some((token) => LOCALE_PREFERENCE_NOUN_TOKENS.has(token))) return false;
|
|
70178
|
-
const importSource = getImportSourceForName(node, node.callee.name)?.toLowerCase();
|
|
70179
|
-
return Boolean(importSource?.includes("i18n") || importSource?.includes("locale"));
|
|
70180
|
-
};
|
|
70181
|
-
const isCallerScopedCookieCall = (node, cookieBindingNames) => {
|
|
70182
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
70183
|
-
if (isNodeOfType(node.callee, "Identifier")) return node.callee.name === "cookies" && getImportSourceForName(node, node.callee.name) === "next/headers";
|
|
70184
|
-
if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== COOKIE_DELETION_METHOD_NAME) return false;
|
|
70185
|
-
const receiver = stripParenExpression(node.callee.object);
|
|
70186
|
-
if (isNodeOfType(receiver, "Identifier")) return cookieBindingNames.has(receiver.name);
|
|
70187
|
-
return isCookiesOrAwaitedCookiesCall(receiver) && getImportSourceForName(receiver, "cookies") === "next/headers";
|
|
70188
|
-
};
|
|
70189
|
-
const isKnownNonDataEffectCall = (node, cookieBindingNames) => isCacheOrNavigationCall(node) || isCallerScopedLocalePreferenceCall(node) || isCallerScopedCookieCall(node, cookieBindingNames);
|
|
69046
|
+
const isCacheOrNavigationCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name) && getImportSourceForName(node, node.callee.name) !== null;
|
|
70190
69047
|
const unwrapExpression = (node) => {
|
|
70191
69048
|
let current = node;
|
|
70192
69049
|
while (current) {
|
|
@@ -70202,10 +69059,10 @@ const unwrapExpression = (node) => {
|
|
|
70202
69059
|
}
|
|
70203
69060
|
return null;
|
|
70204
69061
|
};
|
|
70205
|
-
const isDataExposingValue = (node
|
|
69062
|
+
const isDataExposingValue = (node) => {
|
|
70206
69063
|
const value = unwrapExpression(node);
|
|
70207
69064
|
if (!value) return false;
|
|
70208
|
-
if (
|
|
69065
|
+
if (isCacheOrNavigationCall(value)) return false;
|
|
70209
69066
|
return !isLiteralOnlyExpression(value);
|
|
70210
69067
|
};
|
|
70211
69068
|
const isLiteralOnlyExpression = (node) => {
|
|
@@ -70223,23 +69080,22 @@ const getReturnedOrThrownArgument = (node) => {
|
|
|
70223
69080
|
if (isNodeOfType(node, "ThrowStatement")) return node.argument ?? null;
|
|
70224
69081
|
return null;
|
|
70225
69082
|
};
|
|
70226
|
-
const isDataExposingReturnOrThrow = (node
|
|
70227
|
-
const isPrivilegedEffect = (node
|
|
69083
|
+
const isDataExposingReturnOrThrow = (node) => isDataExposingValue(getReturnedOrThrownArgument(node));
|
|
69084
|
+
const isPrivilegedEffect = (node) => isNodeOfType(node, "CallExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" || isDataExposingReturnOrThrow(node);
|
|
70228
69085
|
const isNonPrivilegedServerAction = (functionNode) => {
|
|
70229
69086
|
const functionBody = functionNode.body;
|
|
70230
69087
|
if (!functionBody) return false;
|
|
70231
|
-
|
|
70232
|
-
if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody, cookieBindingNames)) return false;
|
|
69088
|
+
if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody)) return false;
|
|
70233
69089
|
let hasNonDataEffectCall = false;
|
|
70234
69090
|
let hasPrivilegedEffect = false;
|
|
70235
69091
|
walkAst(functionBody, (child) => {
|
|
70236
69092
|
if (hasPrivilegedEffect) return false;
|
|
70237
69093
|
if (child !== functionBody && isFunctionLike$1(child)) return false;
|
|
70238
|
-
if (
|
|
69094
|
+
if (isCacheOrNavigationCall(child)) {
|
|
70239
69095
|
hasNonDataEffectCall = true;
|
|
70240
69096
|
return;
|
|
70241
69097
|
}
|
|
70242
|
-
if (isPrivilegedEffect(child
|
|
69098
|
+
if (isPrivilegedEffect(child)) {
|
|
70243
69099
|
hasPrivilegedEffect = true;
|
|
70244
69100
|
return false;
|
|
70245
69101
|
}
|
|
@@ -70318,34 +69174,6 @@ const getAuthScanRoots = (functionNode) => {
|
|
|
70318
69174
|
if (isNodeOfType(bodyNode, "BlockStatement")) return (bodyNode.body ?? []).slice(0, 10);
|
|
70319
69175
|
return [bodyNode];
|
|
70320
69176
|
};
|
|
70321
|
-
const COMPONENT_NAME_PATTERN = /^[A-Z]/;
|
|
70322
|
-
const TEST_APP_SOURCE_PATH_PATTERN = /(?:^|[/\\])test[/\\](?:app|src)[/\\]/;
|
|
70323
|
-
const containsJsxOutsideNestedFunctions = (rootNode) => {
|
|
70324
|
-
let containsJsx = false;
|
|
70325
|
-
walkAst(rootNode, (child) => {
|
|
70326
|
-
if (containsJsx) return false;
|
|
70327
|
-
if (child !== rootNode && isFunctionLike$1(child)) return false;
|
|
70328
|
-
if (isNodeOfType(child, "JSXElement") || isNodeOfType(child, "JSXFragment")) {
|
|
70329
|
-
containsJsx = true;
|
|
70330
|
-
return false;
|
|
70331
|
-
}
|
|
70332
|
-
});
|
|
70333
|
-
return containsJsx;
|
|
70334
|
-
};
|
|
70335
|
-
const isComponentLikeServerExport = (candidate) => {
|
|
70336
|
-
if (!COMPONENT_NAME_PATTERN.test(candidate.displayName)) return false;
|
|
70337
|
-
const functionBody = candidate.functionNode.body;
|
|
70338
|
-
if (!isNodeOfType(functionBody, "BlockStatement")) return containsJsxOutsideNestedFunctions(functionBody);
|
|
70339
|
-
let hasReturnedJsx = false;
|
|
70340
|
-
walkAst(functionBody, (child) => {
|
|
70341
|
-
if (hasReturnedJsx) return false;
|
|
70342
|
-
if (child !== functionBody && isFunctionLike$1(child)) return false;
|
|
70343
|
-
if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
|
|
70344
|
-
hasReturnedJsx = containsJsxOutsideNestedFunctions(child.argument);
|
|
70345
|
-
return false;
|
|
70346
|
-
});
|
|
70347
|
-
return hasReturnedJsx;
|
|
70348
|
-
};
|
|
70349
69177
|
const CREDENTIAL_MERGE_TAIL_TOKENS = {
|
|
70350
69178
|
sign: new Set([
|
|
70351
69179
|
"in",
|
|
@@ -70410,7 +69238,6 @@ const isCredentialEstablishingActionName = (actionName) => {
|
|
|
70410
69238
|
const hasPublicNameToken = (actionName) => tokenizeIdentifierWords(actionName).includes("public");
|
|
70411
69239
|
const inspectServerAction = (candidate, fileHasUseServerDirective, allowedFunctionNames, context) => {
|
|
70412
69240
|
if (!(fileHasUseServerDirective || hasUseServerDirective(candidate.functionNode))) return;
|
|
70413
|
-
if (isComponentLikeServerExport(candidate)) return;
|
|
70414
69241
|
if (isCredentialEstablishingActionName(candidate.displayName)) return;
|
|
70415
69242
|
if (hasPublicNameToken(candidate.displayName)) return;
|
|
70416
69243
|
if (containsAuthCheck(getAuthScanRoots(candidate.functionNode), allowedFunctionNames, GENERIC_AUTH_METHOD_NAMES)) return;
|
|
@@ -70448,42 +69275,18 @@ const serverAuthActions = defineRule({
|
|
|
70448
69275
|
title: "Unauthenticated server action can be called directly",
|
|
70449
69276
|
severity: "error",
|
|
70450
69277
|
recommendation: "Check auth before touching data because exported server actions can be called directly by unauthenticated clients.",
|
|
70451
|
-
create:
|
|
70452
|
-
const shouldSkipTestAppSource = Boolean(context.filename && TEST_APP_SOURCE_PATH_PATTERN.test(context.filename));
|
|
69278
|
+
create: (context) => {
|
|
70453
69279
|
let fileHasUseServerDirective = false;
|
|
70454
|
-
let programNode = null;
|
|
70455
|
-
const inspectedFunctions = /* @__PURE__ */ new Set();
|
|
70456
69280
|
const customAuthFunctionNames = getReactDoctorStringArraySetting(context.settings, "serverAuthFunctionNames");
|
|
70457
69281
|
const allowedFunctionNames = customAuthFunctionNames.length > 0 ? new Set([...AUTH_FUNCTION_NAMES, ...customAuthFunctionNames]) : AUTH_FUNCTION_NAMES;
|
|
70458
|
-
const inspect = (candidate) =>
|
|
70459
|
-
if (inspectedFunctions.has(candidate.functionNode)) return;
|
|
70460
|
-
inspectedFunctions.add(candidate.functionNode);
|
|
70461
|
-
inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
|
|
70462
|
-
};
|
|
69282
|
+
const inspect = (candidate) => inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
|
|
70463
69283
|
return {
|
|
70464
|
-
Program(
|
|
70465
|
-
|
|
70466
|
-
fileHasUseServerDirective = hasDirective(currentProgramNode, "use server");
|
|
69284
|
+
Program(programNode) {
|
|
69285
|
+
fileHasUseServerDirective = hasDirective(programNode, "use server");
|
|
70467
69286
|
},
|
|
70468
69287
|
ExportNamedDeclaration(node) {
|
|
70469
|
-
if (shouldSkipTestAppSource) return;
|
|
70470
69288
|
const declaration = node.declaration;
|
|
70471
|
-
if (!declaration)
|
|
70472
|
-
if (!programNode || node.source || node.exportKind === "type") return;
|
|
70473
|
-
for (const specifier of node.specifiers ?? []) {
|
|
70474
|
-
if (!isNodeOfType(specifier, "ExportSpecifier") || specifier.exportKind === "type") continue;
|
|
70475
|
-
const exportedName = isNodeOfType(specifier.exported, "Identifier") ? specifier.exported.name : isNodeOfType(specifier.exported, "Literal") && typeof specifier.exported.value === "string" ? specifier.exported.value : null;
|
|
70476
|
-
if (!exportedName) continue;
|
|
70477
|
-
const exportedValue = findExportedValue(programNode, exportedName);
|
|
70478
|
-
if (!isAsyncFunctionLikeNode(exportedValue)) continue;
|
|
70479
|
-
inspect({
|
|
70480
|
-
functionNode: exportedValue,
|
|
70481
|
-
displayName: isNodeOfType(specifier.local, "Identifier") ? specifier.local.name : exportedName,
|
|
70482
|
-
reportNode: specifier.local ?? specifier
|
|
70483
|
-
});
|
|
70484
|
-
}
|
|
70485
|
-
return;
|
|
70486
|
-
}
|
|
69289
|
+
if (!declaration) return;
|
|
70487
69290
|
if (isAsyncFunctionLikeNode(declaration)) {
|
|
70488
69291
|
if (!isNodeOfType(declaration, "FunctionDeclaration")) return;
|
|
70489
69292
|
inspect({
|
|
@@ -70496,23 +69299,11 @@ const serverAuthActions = defineRule({
|
|
|
70496
69299
|
if (isNodeOfType(declaration, "VariableDeclaration")) for (const candidate of collectCandidatesFromVariableDeclaration(declaration)) inspect(candidate);
|
|
70497
69300
|
},
|
|
70498
69301
|
ExportDefaultDeclaration(node) {
|
|
70499
|
-
|
|
70500
|
-
const directCandidate = getCandidateFromDefaultDeclaration(node);
|
|
70501
|
-
if (directCandidate) {
|
|
70502
|
-
inspect(directCandidate);
|
|
70503
|
-
return;
|
|
70504
|
-
}
|
|
70505
|
-
if (!programNode) return;
|
|
70506
|
-
const resolvedDefaultExport = findExportedValue(programNode, "default");
|
|
70507
|
-
const candidate = isAsyncFunctionLikeNode(resolvedDefaultExport) ? {
|
|
70508
|
-
functionNode: resolvedDefaultExport,
|
|
70509
|
-
displayName: isNodeOfType(node.declaration, "Identifier") ? node.declaration.name : "default",
|
|
70510
|
-
reportNode: node.declaration ?? node
|
|
70511
|
-
} : null;
|
|
69302
|
+
const candidate = getCandidateFromDefaultDeclaration(node);
|
|
70512
69303
|
if (candidate) inspect(candidate);
|
|
70513
69304
|
}
|
|
70514
69305
|
};
|
|
70515
|
-
}
|
|
69306
|
+
}
|
|
70516
69307
|
});
|
|
70517
69308
|
//#endregion
|
|
70518
69309
|
//#region src/plugin/rules/server/server-cache-with-object-literal.ts
|
|
@@ -70592,7 +69383,6 @@ const serverDedupProps = defineRule({
|
|
|
70592
69383
|
for (const attr of node.attributes ?? []) {
|
|
70593
69384
|
if (!isNodeOfType(attr, "JSXAttribute")) continue;
|
|
70594
69385
|
if (!isNodeOfType(attr.name, "JSXIdentifier")) continue;
|
|
70595
|
-
if (attr.name.name === "key") continue;
|
|
70596
69386
|
if (!isNodeOfType(attr.value, "JSXExpressionContainer")) continue;
|
|
70597
69387
|
const expression = attr.value.expression;
|
|
70598
69388
|
if (!expression) continue;
|
|
@@ -71322,7 +70112,7 @@ const collectJsxRuntimeImports = (program) => {
|
|
|
71322
70112
|
};
|
|
71323
70113
|
//#endregion
|
|
71324
70114
|
//#region src/plugin/rules/react-builtins/style-prop-object.ts
|
|
71325
|
-
const MESSAGE$
|
|
70115
|
+
const MESSAGE$1 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
|
|
71326
70116
|
const resolveSettings = (settings) => {
|
|
71327
70117
|
const reactDoctor = settings?.["react-doctor"];
|
|
71328
70118
|
return { allow: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.stylePropObject ?? {} : {}).allow ?? [] };
|
|
@@ -71429,7 +70219,7 @@ const stylePropObject = defineRule({
|
|
|
71429
70219
|
if (isNodeOfType(value, "Literal") && typeof value.value === "string") {
|
|
71430
70220
|
context.report({
|
|
71431
70221
|
node: attribute,
|
|
71432
|
-
message: MESSAGE$
|
|
70222
|
+
message: MESSAGE$1
|
|
71433
70223
|
});
|
|
71434
70224
|
return;
|
|
71435
70225
|
}
|
|
@@ -71437,7 +70227,7 @@ const stylePropObject = defineRule({
|
|
|
71437
70227
|
const innerExpression = value.expression;
|
|
71438
70228
|
if (innerExpression && innerExpression.type !== "JSXEmptyExpression" && isInvalidStyleExpression(innerExpression)) context.report({
|
|
71439
70229
|
node: attribute,
|
|
71440
|
-
message: MESSAGE$
|
|
70230
|
+
message: MESSAGE$1
|
|
71441
70231
|
});
|
|
71442
70232
|
}
|
|
71443
70233
|
}
|
|
@@ -71460,7 +70250,7 @@ const stylePropObject = defineRule({
|
|
|
71460
70250
|
if (!(isNodeOfType(key, "Identifier") && key.name === "style" || isNodeOfType(key, "Literal") && key.value === "style")) continue;
|
|
71461
70251
|
if (isInvalidStyleExpression(property.value)) context.report({
|
|
71462
70252
|
node: property,
|
|
71463
|
-
message: MESSAGE$
|
|
70253
|
+
message: MESSAGE$1
|
|
71464
70254
|
});
|
|
71465
70255
|
}
|
|
71466
70256
|
}
|
|
@@ -71796,7 +70586,7 @@ const svgFilterClickjackingRisk = defineRule({
|
|
|
71796
70586
|
});
|
|
71797
70587
|
//#endregion
|
|
71798
70588
|
//#region src/plugin/rules/a11y/tabindex-no-positive.ts
|
|
71799
|
-
const MESSAGE
|
|
70589
|
+
const MESSAGE = "Keyboard users get jumped out of the normal order by a positive `tabIndex`, so use `0` or `-1`.";
|
|
71800
70590
|
const tabindexNoPositive = defineRule({
|
|
71801
70591
|
id: "tabindex-no-positive",
|
|
71802
70592
|
title: "Positive tabindex value",
|
|
@@ -71816,7 +70606,7 @@ const tabindexNoPositive = defineRule({
|
|
|
71816
70606
|
} else if (tabIndex.value && isNodeOfType(tabIndex.value, "JSXExpressionContainer")) numericValue = parseJsxValue(tabIndex.value);
|
|
71817
70607
|
if (numericValue !== null && numericValue > 0) context.report({
|
|
71818
70608
|
node: tabIndex,
|
|
71819
|
-
message: MESSAGE
|
|
70609
|
+
message: MESSAGE
|
|
71820
70610
|
});
|
|
71821
70611
|
} })
|
|
71822
70612
|
});
|
|
@@ -72722,445 +71512,6 @@ const useLazyMotion = defineRule({
|
|
|
72722
71512
|
} })
|
|
72723
71513
|
});
|
|
72724
71514
|
//#endregion
|
|
72725
|
-
//#region src/plugin/rules/valtio/valtio-no-proxy-read-in-render.ts
|
|
72726
|
-
const VALTIO_REACT_MODULE_SOURCES$1 = new Set(["valtio", "valtio/react"]);
|
|
72727
|
-
const REACT_DEPENDENCY_ARRAY_HOOK_NAMES = new Set([
|
|
72728
|
-
"useCallback",
|
|
72729
|
-
"useEffect",
|
|
72730
|
-
"useInsertionEffect",
|
|
72731
|
-
"useLayoutEffect",
|
|
72732
|
-
"useMemo"
|
|
72733
|
-
]);
|
|
72734
|
-
const MESSAGE = "Read this Valtio value from the useSnapshot result during render. Keep proxy reads for callbacks so render uses the tracked, consistent snapshot.";
|
|
72735
|
-
const isValtioImportSymbol = (symbol) => {
|
|
72736
|
-
if (symbol.kind !== "import") return false;
|
|
72737
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
72738
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && VALTIO_REACT_MODULE_SOURCES$1.has(importDeclaration.source.value));
|
|
72739
|
-
};
|
|
72740
|
-
const isValtioUseSnapshotCall = (node, scopes) => {
|
|
72741
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
72742
|
-
const callee = stripParenExpression(node.callee);
|
|
72743
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
72744
|
-
const symbol = resolveConstIdentifierAlias(callee, scopes);
|
|
72745
|
-
return Boolean(symbol && isValtioImportSymbol(symbol) && getImportedName(symbol.declarationNode) === "useSnapshot");
|
|
72746
|
-
}
|
|
72747
|
-
if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyName(callee) !== "useSnapshot") return false;
|
|
72748
|
-
const receiver = stripParenExpression(callee.object);
|
|
72749
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
72750
|
-
const namespaceSymbol = resolveConstIdentifierAlias(receiver, scopes);
|
|
72751
|
-
return Boolean(namespaceSymbol && isValtioImportSymbol(namespaceSymbol) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
|
|
72752
|
-
};
|
|
72753
|
-
const collectPatternBindingIdentifiers = (pattern) => {
|
|
72754
|
-
if (isNodeOfType(pattern, "Identifier")) return [pattern];
|
|
72755
|
-
if (isNodeOfType(pattern, "AssignmentPattern")) return collectPatternBindingIdentifiers(pattern.left);
|
|
72756
|
-
if (isNodeOfType(pattern, "RestElement")) return collectPatternBindingIdentifiers(pattern.argument);
|
|
72757
|
-
if (isNodeOfType(pattern, "ArrayPattern")) return pattern.elements.flatMap((element) => element ? collectPatternBindingIdentifiers(element) : []);
|
|
72758
|
-
if (isNodeOfType(pattern, "ObjectPattern")) return pattern.properties.flatMap((property) => {
|
|
72759
|
-
if (isNodeOfType(property, "Property")) return collectPatternBindingIdentifiers(property.value);
|
|
72760
|
-
return isNodeOfType(property, "RestElement") ? collectPatternBindingIdentifiers(property.argument) : [];
|
|
72761
|
-
});
|
|
72762
|
-
return [];
|
|
72763
|
-
};
|
|
72764
|
-
const collectAssignmentWriteTargets = (target) => {
|
|
72765
|
-
const candidate = stripParenExpression(target);
|
|
72766
|
-
if (isNodeOfType(candidate, "AssignmentPattern")) return collectAssignmentWriteTargets(candidate.left);
|
|
72767
|
-
if (isNodeOfType(candidate, "RestElement")) return collectAssignmentWriteTargets(candidate.argument);
|
|
72768
|
-
if (isNodeOfType(candidate, "ArrayPattern")) return candidate.elements.flatMap((element) => element ? collectAssignmentWriteTargets(element) : []);
|
|
72769
|
-
if (isNodeOfType(candidate, "ObjectPattern")) return candidate.properties.flatMap((property) => isNodeOfType(property, "Property") ? collectAssignmentWriteTargets(property.value) : collectAssignmentWriteTargets(property.argument));
|
|
72770
|
-
return [candidate];
|
|
72771
|
-
};
|
|
72772
|
-
const findPatternPathToBinding = (pattern, bindingIdentifier) => {
|
|
72773
|
-
if (pattern === bindingIdentifier) return [];
|
|
72774
|
-
if (isNodeOfType(pattern, "AssignmentPattern")) return findPatternPathToBinding(pattern.left, bindingIdentifier);
|
|
72775
|
-
if (isNodeOfType(pattern, "RestElement")) return null;
|
|
72776
|
-
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
72777
|
-
for (const [elementIndex, element] of pattern.elements.entries()) {
|
|
72778
|
-
if (!element) continue;
|
|
72779
|
-
const nestedPath = findPatternPathToBinding(element, bindingIdentifier);
|
|
72780
|
-
if (nestedPath) return [String(elementIndex), ...nestedPath];
|
|
72781
|
-
}
|
|
72782
|
-
return null;
|
|
72783
|
-
}
|
|
72784
|
-
if (!isNodeOfType(pattern, "ObjectPattern")) return null;
|
|
72785
|
-
for (const property of pattern.properties) {
|
|
72786
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
72787
|
-
const nestedPath = findPatternPathToBinding(property.value, bindingIdentifier);
|
|
72788
|
-
if (!nestedPath) continue;
|
|
72789
|
-
const propertyName = getStaticPropertyKeyName(property);
|
|
72790
|
-
return propertyName === null ? null : [propertyName, ...nestedPath];
|
|
72791
|
-
}
|
|
72792
|
-
return null;
|
|
72793
|
-
};
|
|
72794
|
-
const resolveProxyPath = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), allowedAliasSymbolIds = null, collectedAliasCaptures = null) => {
|
|
72795
|
-
const candidate = stripParenExpression(expression);
|
|
72796
|
-
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
72797
|
-
const receiverPath = resolveProxyPath(candidate.object, scopes, visitedSymbolIds, allowedAliasSymbolIds, collectedAliasCaptures);
|
|
72798
|
-
if (!receiverPath) return null;
|
|
72799
|
-
return {
|
|
72800
|
-
...receiverPath,
|
|
72801
|
-
properties: [...receiverPath.properties, getStaticPropertyName(candidate)]
|
|
72802
|
-
};
|
|
72803
|
-
}
|
|
72804
|
-
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
72805
|
-
const symbol = scopes.symbolFor(candidate);
|
|
72806
|
-
if (!symbol) return scopes.isGlobalReference(candidate) ? {
|
|
72807
|
-
rootKey: `global:${candidate.name}`,
|
|
72808
|
-
properties: []
|
|
72809
|
-
} : null;
|
|
72810
|
-
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
72811
|
-
if (symbol.kind === "const" && symbol.initializer && isNodeOfType(symbol.declarationNode, "VariableDeclarator")) {
|
|
72812
|
-
const directAlias = symbol.declarationNode.id === symbol.bindingIdentifier;
|
|
72813
|
-
const initializerNode = directAlias ? symbol.initializer : symbol.declarationNode.init;
|
|
72814
|
-
if (!initializerNode) return {
|
|
72815
|
-
rootKey: `symbol:${symbol.id}`,
|
|
72816
|
-
properties: []
|
|
72817
|
-
};
|
|
72818
|
-
const initializer = stripParenExpression(initializerNode);
|
|
72819
|
-
const canResolveDirectAlias = directAlias && (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression"));
|
|
72820
|
-
const patternPath = directAlias ? null : findPatternPathToBinding(symbol.declarationNode.id, symbol.bindingIdentifier);
|
|
72821
|
-
if ((canResolveDirectAlias || patternPath) && (allowedAliasSymbolIds === null || allowedAliasSymbolIds.has(symbol.id))) {
|
|
72822
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
72823
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
72824
|
-
const initializerPath = resolveProxyPath(initializer, scopes, nextVisitedSymbolIds, allowedAliasSymbolIds, collectedAliasCaptures);
|
|
72825
|
-
if (initializerPath) {
|
|
72826
|
-
const aliasPath = {
|
|
72827
|
-
...initializerPath,
|
|
72828
|
-
properties: [...initializerPath.properties, ...patternPath ?? []]
|
|
72829
|
-
};
|
|
72830
|
-
const declarationEnd = symbol.declarationNode.range?.[1];
|
|
72831
|
-
if (typeof declarationEnd === "number") collectedAliasCaptures?.set(symbol.id, {
|
|
72832
|
-
declarationEnd,
|
|
72833
|
-
path: aliasPath
|
|
72834
|
-
});
|
|
72835
|
-
return aliasPath;
|
|
72836
|
-
}
|
|
72837
|
-
}
|
|
72838
|
-
}
|
|
72839
|
-
return {
|
|
72840
|
-
rootKey: `symbol:${symbol.id}`,
|
|
72841
|
-
properties: []
|
|
72842
|
-
};
|
|
72843
|
-
};
|
|
72844
|
-
const isScopeWithin = (candidateScope, ancestorScope) => {
|
|
72845
|
-
let currentScope = candidateScope;
|
|
72846
|
-
while (currentScope) {
|
|
72847
|
-
if (currentScope === ancestorScope) return true;
|
|
72848
|
-
currentScope = currentScope.parent;
|
|
72849
|
-
}
|
|
72850
|
-
return false;
|
|
72851
|
-
};
|
|
72852
|
-
const isPathPrefix = (prefix, candidate) => {
|
|
72853
|
-
if (prefix.rootKey !== candidate.rootKey || prefix.properties.length > candidate.properties.length) return false;
|
|
72854
|
-
return prefix.properties.every((propertyName, propertyIndex) => propertyName !== null && propertyName === candidate.properties[propertyIndex]);
|
|
72855
|
-
};
|
|
72856
|
-
const findOutermostMemberRead = (identifier) => {
|
|
72857
|
-
let currentExpression = findTransparentExpressionRoot(identifier);
|
|
72858
|
-
for (;;) {
|
|
72859
|
-
const parent = currentExpression.parent;
|
|
72860
|
-
if (!isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== stripParenExpression(currentExpression)) return currentExpression;
|
|
72861
|
-
currentExpression = findTransparentExpressionRoot(parent);
|
|
72862
|
-
}
|
|
72863
|
-
};
|
|
72864
|
-
const isReadPositionWithinAssignmentTarget = (expression) => {
|
|
72865
|
-
let currentNode = expression;
|
|
72866
|
-
let parentNode = currentNode.parent;
|
|
72867
|
-
while (parentNode) {
|
|
72868
|
-
if (isNodeOfType(parentNode, "MemberExpression") && parentNode.computed && parentNode.property === currentNode || isNodeOfType(parentNode, "Property") && parentNode.computed && parentNode.key === currentNode || isNodeOfType(parentNode, "AssignmentPattern") && parentNode.right === currentNode) return true;
|
|
72869
|
-
if (isNodeOfType(parentNode, "AssignmentExpression") || isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete" || isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return false;
|
|
72870
|
-
currentNode = parentNode;
|
|
72871
|
-
parentNode = currentNode.parent;
|
|
72872
|
-
}
|
|
72873
|
-
return false;
|
|
72874
|
-
};
|
|
72875
|
-
const isSnapshotArgument = (expression, scopes) => {
|
|
72876
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
72877
|
-
const parent = expressionRoot.parent;
|
|
72878
|
-
return Boolean(parent && isNodeOfType(parent, "CallExpression") && isValtioUseSnapshotCall(parent, scopes) && parent.arguments[0] === expressionRoot);
|
|
72879
|
-
};
|
|
72880
|
-
const isStableProxyDependency = (expression, readPath, matchingTarget, scopes) => {
|
|
72881
|
-
if (readPath.properties.length !== matchingTarget.path.properties.length || !isPathPrefix(matchingTarget.path, readPath)) return false;
|
|
72882
|
-
const dependencyArray = findTransparentExpressionRoot(expression).parent;
|
|
72883
|
-
if (!isNodeOfType(dependencyArray, "ArrayExpression")) return false;
|
|
72884
|
-
const hookCall = dependencyArray.parent;
|
|
72885
|
-
return Boolean(isNodeOfType(hookCall, "CallExpression") && hookCall.arguments[1] === dependencyArray && isReactApiCall(hookCall, REACT_DEPENDENCY_ARRAY_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }));
|
|
72886
|
-
};
|
|
72887
|
-
const getSnapshotTarget = (callExpression, context) => {
|
|
72888
|
-
if (!isValtioUseSnapshotCall(callExpression, context.scopes)) return null;
|
|
72889
|
-
const proxyArgument = callExpression.arguments[0];
|
|
72890
|
-
if (!proxyArgument || isNodeOfType(proxyArgument, "SpreadElement")) return null;
|
|
72891
|
-
const path = resolveProxyPath(proxyArgument, context.scopes);
|
|
72892
|
-
if (!path || path.properties.some((propertyName) => propertyName === null)) return null;
|
|
72893
|
-
const callExpressionRoot = findTransparentExpressionRoot(callExpression);
|
|
72894
|
-
const declarator = callExpressionRoot.parent;
|
|
72895
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== callExpressionRoot || !isNodeOfType(declarator.parent, "VariableDeclaration")) return null;
|
|
72896
|
-
const ownerFunction = findRenderPhaseComponentOrHook(callExpression, context.scopes);
|
|
72897
|
-
if (!ownerFunction) return null;
|
|
72898
|
-
const snapshotBindingScopes = collectPatternBindingIdentifiers(declarator.id).flatMap((bindingIdentifier) => {
|
|
72899
|
-
const bindingSymbol = context.scopes.symbolFor(bindingIdentifier);
|
|
72900
|
-
return bindingSymbol ? [bindingSymbol.scope] : [];
|
|
72901
|
-
});
|
|
72902
|
-
const declarationEnd = declarator.range?.[1];
|
|
72903
|
-
if (snapshotBindingScopes.length === 0 || typeof declarationEnd !== "number") return null;
|
|
72904
|
-
return {
|
|
72905
|
-
declarationEnd,
|
|
72906
|
-
ownerFunction,
|
|
72907
|
-
path,
|
|
72908
|
-
snapshotBindingScopes
|
|
72909
|
-
};
|
|
72910
|
-
};
|
|
72911
|
-
const wasTargetReplacedBeforeRead = (target, readPosition, writeTargets, readAliasCaptures, context) => writeTargets.some((writeTarget) => {
|
|
72912
|
-
const writeNode = findTransparentExpressionRoot(writeTarget).parent;
|
|
72913
|
-
const writePosition = writeNode?.range?.[0];
|
|
72914
|
-
if (!writeNode || typeof writePosition !== "number" || writePosition <= target.declarationEnd || writePosition >= readPosition || findRenderPhaseComponentOrHook(writeNode, context.scopes) !== target.ownerFunction) return false;
|
|
72915
|
-
const writePath = resolveProxyPath(writeTarget, context.scopes, /* @__PURE__ */ new Set(), null);
|
|
72916
|
-
if (!writePath || !isPathPrefix(writePath, target.path)) return false;
|
|
72917
|
-
return ![...readAliasCaptures.values()].some((aliasCapture) => aliasCapture.declarationEnd < writePosition && isPathPrefix(writePath, aliasCapture.path));
|
|
72918
|
-
});
|
|
72919
|
-
const valtioNoProxyReadInRender = defineRule({
|
|
72920
|
-
id: "valtio-no-proxy-read-in-render",
|
|
72921
|
-
title: "Valtio proxy read during render",
|
|
72922
|
-
severity: "warn",
|
|
72923
|
-
recommendation: "Read reactive render values from useSnapshot. Reserve the mutable proxy for event handlers, effects, and other callbacks that need the latest value.",
|
|
72924
|
-
requires: ["valtio:1"],
|
|
72925
|
-
create: (context) => {
|
|
72926
|
-
const snapshotTargets = [];
|
|
72927
|
-
const identifierCandidates = [];
|
|
72928
|
-
const writeTargets = [];
|
|
72929
|
-
return {
|
|
72930
|
-
CallExpression(node) {
|
|
72931
|
-
const snapshotTarget = getSnapshotTarget(node, context);
|
|
72932
|
-
if (snapshotTarget) snapshotTargets.push(snapshotTarget);
|
|
72933
|
-
},
|
|
72934
|
-
Identifier(node) {
|
|
72935
|
-
if (context.scopes.referenceFor(node)) identifierCandidates.push(node);
|
|
72936
|
-
},
|
|
72937
|
-
AssignmentExpression(node) {
|
|
72938
|
-
writeTargets.push(...collectAssignmentWriteTargets(node.left));
|
|
72939
|
-
},
|
|
72940
|
-
UpdateExpression(node) {
|
|
72941
|
-
writeTargets.push(node.argument);
|
|
72942
|
-
},
|
|
72943
|
-
"Program:exit"() {
|
|
72944
|
-
const reportedExpressions = /* @__PURE__ */ new Set();
|
|
72945
|
-
for (const identifier of identifierCandidates) {
|
|
72946
|
-
const readExpression = findOutermostMemberRead(identifier);
|
|
72947
|
-
if (reportedExpressions.has(readExpression) || isWithinAssignmentTarget(readExpression) && !isReadPositionWithinAssignmentTarget(readExpression) || isSnapshotArgument(readExpression, context.scopes)) continue;
|
|
72948
|
-
const readAliasCaptures = /* @__PURE__ */ new Map();
|
|
72949
|
-
const readPath = resolveProxyPath(readExpression, context.scopes, /* @__PURE__ */ new Set(), null, readAliasCaptures);
|
|
72950
|
-
const readPosition = readExpression.range?.[0];
|
|
72951
|
-
const ownerFunction = findRenderPhaseComponentOrHook(readExpression, context.scopes);
|
|
72952
|
-
if (!readPath || typeof readPosition !== "number" || !ownerFunction) continue;
|
|
72953
|
-
const readScope = context.scopes.scopeFor(readExpression);
|
|
72954
|
-
const matchingTarget = snapshotTargets.find((target) => target.ownerFunction === ownerFunction && target.declarationEnd < readPosition && target.snapshotBindingScopes.some((bindingScope) => isScopeWithin(readScope, bindingScope)) && isPathPrefix(target.path, readPath) && (readAliasCaptures.size === 0 || [...readAliasCaptures.values()].some((aliasCapture) => isPathPrefix(aliasCapture.path, target.path) || isPathPrefix(target.path, aliasCapture.path) && readPath.properties.length > aliasCapture.path.properties.length)) && !wasTargetReplacedBeforeRead(target, readPosition, writeTargets, readAliasCaptures, context));
|
|
72955
|
-
if (!matchingTarget || isStableProxyDependency(readExpression, readPath, matchingTarget, context.scopes)) continue;
|
|
72956
|
-
reportedExpressions.add(readExpression);
|
|
72957
|
-
context.report({
|
|
72958
|
-
node: readExpression,
|
|
72959
|
-
message: MESSAGE
|
|
72960
|
-
});
|
|
72961
|
-
}
|
|
72962
|
-
}
|
|
72963
|
-
};
|
|
72964
|
-
}
|
|
72965
|
-
});
|
|
72966
|
-
//#endregion
|
|
72967
|
-
//#region src/plugin/rules/valtio/valtio-no-snapshot-in-callback.ts
|
|
72968
|
-
const VALTIO_REACT_MODULE_SOURCES = new Set(["valtio", "valtio/react"]);
|
|
72969
|
-
const DEFERRED_REACT_HOOK_NAMES = new Set([
|
|
72970
|
-
"useEffect",
|
|
72971
|
-
"useInsertionEffect",
|
|
72972
|
-
"useLayoutEffect"
|
|
72973
|
-
]);
|
|
72974
|
-
const PROMISE_CONTINUATION_METHOD_NAMES = new Set([
|
|
72975
|
-
"catch",
|
|
72976
|
-
"finally",
|
|
72977
|
-
"then"
|
|
72978
|
-
]);
|
|
72979
|
-
const DEFERRED_CONSTRUCTOR_NAMES = new Set([
|
|
72980
|
-
"IntersectionObserver",
|
|
72981
|
-
"MutationObserver",
|
|
72982
|
-
"PerformanceObserver",
|
|
72983
|
-
"ResizeObserver"
|
|
72984
|
-
]);
|
|
72985
|
-
const isValtioImport = (symbol) => {
|
|
72986
|
-
const source = getImportDeclarationForSymbol(symbol)?.source.value;
|
|
72987
|
-
return typeof source === "string" && VALTIO_REACT_MODULE_SOURCES.has(source);
|
|
72988
|
-
};
|
|
72989
|
-
const resolvesToValtioNamespace = (expression, scopes, visitedSymbolIds) => {
|
|
72990
|
-
const candidate = stripParenExpression(expression);
|
|
72991
|
-
if (!isNodeOfType(candidate, "Identifier")) return false;
|
|
72992
|
-
const symbol = scopes.symbolFor(candidate);
|
|
72993
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
72994
|
-
if (symbol.kind === "import" && isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") && isValtioImport(symbol)) return true;
|
|
72995
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
72996
|
-
if (!initializer) return false;
|
|
72997
|
-
visitedSymbolIds.add(symbol.id);
|
|
72998
|
-
return resolvesToValtioNamespace(initializer, scopes, visitedSymbolIds);
|
|
72999
|
-
};
|
|
73000
|
-
const isValtioUseSnapshotCallee = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73001
|
-
const candidate = stripParenExpression(expression);
|
|
73002
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
73003
|
-
const symbol = scopes.symbolFor(candidate);
|
|
73004
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
73005
|
-
if (symbol.kind === "import" && isValtioImport(symbol) && getImportedName(symbol.declarationNode) === "useSnapshot") return true;
|
|
73006
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
73007
|
-
if (!initializer) return false;
|
|
73008
|
-
visitedSymbolIds.add(symbol.id);
|
|
73009
|
-
return isValtioUseSnapshotCallee(initializer, scopes, visitedSymbolIds);
|
|
73010
|
-
}
|
|
73011
|
-
if (!isNodeOfType(candidate, "MemberExpression") || getStaticPropertyName(candidate) !== "useSnapshot") return false;
|
|
73012
|
-
return resolvesToValtioNamespace(candidate.object, scopes, visitedSymbolIds);
|
|
73013
|
-
};
|
|
73014
|
-
const getSnapshotOriginCall = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73015
|
-
const candidate = stripParenExpression(expression);
|
|
73016
|
-
if (isNodeOfType(candidate, "CallExpression")) return isValtioUseSnapshotCallee(candidate.callee, context.scopes) ? candidate : null;
|
|
73017
|
-
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
73018
|
-
const symbol = context.scopes.symbolFor(candidate);
|
|
73019
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
73020
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
73021
|
-
if (!initializer) return null;
|
|
73022
|
-
visitedSymbolIds.add(symbol.id);
|
|
73023
|
-
return getSnapshotOriginCall(initializer, context, visitedSymbolIds);
|
|
73024
|
-
};
|
|
73025
|
-
const isJsxEventHandlerValue = (expression) => {
|
|
73026
|
-
const expressionContainer = expression.parent;
|
|
73027
|
-
if (!isNodeOfType(expressionContainer, "JSXExpressionContainer")) return false;
|
|
73028
|
-
const attribute = expressionContainer.parent;
|
|
73029
|
-
return Boolean(isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && REACT_HANDLER_PROP_PATTERN.test(attribute.name.name));
|
|
73030
|
-
};
|
|
73031
|
-
const isGlobalDeferredFunctionCall = (callExpression, context) => {
|
|
73032
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
73033
|
-
if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) return context.scopes.isGlobalReference(callee);
|
|
73034
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
73035
|
-
const methodName = getStaticPropertyName(callee);
|
|
73036
|
-
if (!methodName || !TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(methodName)) return false;
|
|
73037
|
-
const receiver = stripParenExpression(callee.object);
|
|
73038
|
-
return Boolean(isNodeOfType(receiver, "Identifier") && (receiver.name === "globalThis" || receiver.name === "window") && context.scopes.isGlobalReference(receiver));
|
|
73039
|
-
};
|
|
73040
|
-
const isDeferredCallbackArgument = (expression, context) => {
|
|
73041
|
-
const parent = expression.parent;
|
|
73042
|
-
if (isNodeOfType(parent, "NewExpression")) {
|
|
73043
|
-
const callee = stripParenExpression(parent.callee);
|
|
73044
|
-
return Boolean(parent.arguments?.[0] === expression && isNodeOfType(callee, "Identifier") && DEFERRED_CONSTRUCTOR_NAMES.has(callee.name) && context.scopes.isGlobalReference(callee));
|
|
73045
|
-
}
|
|
73046
|
-
if (!isNodeOfType(parent, "CallExpression")) return false;
|
|
73047
|
-
if (!(parent.arguments ?? []).some((argument) => argument === expression)) return false;
|
|
73048
|
-
if (parent.arguments?.[0] === expression && isReactApiCall(parent, DEFERRED_REACT_HOOK_NAMES, context.scopes, {
|
|
73049
|
-
allowGlobalReactNamespace: true,
|
|
73050
|
-
resolveNamedAliases: true
|
|
73051
|
-
})) return true;
|
|
73052
|
-
if (parent.arguments?.[0] === expression && isGlobalDeferredFunctionCall(parent, context)) return true;
|
|
73053
|
-
const callee = stripParenExpression(parent.callee);
|
|
73054
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
73055
|
-
const methodName = getStaticPropertyName(callee);
|
|
73056
|
-
return Boolean(methodName && (SUBSCRIPTION_METHOD_NAMES.has(methodName) || PROMISE_CONTINUATION_METHOD_NAMES.has(methodName)));
|
|
73057
|
-
};
|
|
73058
|
-
const isReactEffectCallbackValue = (expression, context) => {
|
|
73059
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
73060
|
-
const parent = expressionRoot.parent;
|
|
73061
|
-
return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === expressionRoot && isReactApiCall(parent, DEFERRED_REACT_HOOK_NAMES, context.scopes, {
|
|
73062
|
-
allowGlobalReactNamespace: true,
|
|
73063
|
-
resolveNamedAliases: true
|
|
73064
|
-
}));
|
|
73065
|
-
};
|
|
73066
|
-
const isSymbolUsedAsReactEffectCallback = (symbol, context, visitedSymbolIds) => {
|
|
73067
|
-
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
73068
|
-
visitedSymbolIds.add(symbol.id);
|
|
73069
|
-
return symbol.references.some((reference) => {
|
|
73070
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
73071
|
-
if (isReactEffectCallbackValue(referenceRoot, context)) return true;
|
|
73072
|
-
const aliasSymbol = getConstAliasSymbol(referenceRoot, context);
|
|
73073
|
-
return Boolean(aliasSymbol && isSymbolUsedAsReactEffectCallback(aliasSymbol, context, visitedSymbolIds));
|
|
73074
|
-
});
|
|
73075
|
-
};
|
|
73076
|
-
const isFunctionUsedAsReactEffectCallback = (functionNode, context) => {
|
|
73077
|
-
if (isReactEffectCallbackValue(functionNode, context)) return true;
|
|
73078
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
73079
|
-
const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
73080
|
-
return Boolean(symbol && isSymbolUsedAsReactEffectCallback(symbol, context, /* @__PURE__ */ new Set()));
|
|
73081
|
-
};
|
|
73082
|
-
const isReactEffectCleanupValue = (expression, context) => {
|
|
73083
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
73084
|
-
const returnStatement = expressionRoot.parent;
|
|
73085
|
-
if (isNodeOfType(returnStatement, "ArrowFunctionExpression") && returnStatement.body === expressionRoot) return isFunctionUsedAsReactEffectCallback(returnStatement, context);
|
|
73086
|
-
if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== expressionRoot) return false;
|
|
73087
|
-
const effectCallback = findEnclosingFunction$1(returnStatement);
|
|
73088
|
-
return Boolean(effectCallback && isFunctionUsedAsReactEffectCallback(effectCallback, context));
|
|
73089
|
-
};
|
|
73090
|
-
const isDeferredCallbackValue = (expression, context) => isJsxEventHandlerValue(expression) || isDeferredCallbackArgument(expression, context) || isReactEffectCleanupValue(expression, context);
|
|
73091
|
-
const getConstAliasSymbol = (expression, context) => {
|
|
73092
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
73093
|
-
const declarator = expressionRoot.parent;
|
|
73094
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== expressionRoot || !isNodeOfType(declarator.id, "Identifier")) return null;
|
|
73095
|
-
const aliasSymbol = context.scopes.symbolFor(declarator.id);
|
|
73096
|
-
return aliasSymbol?.kind === "const" ? aliasSymbol : null;
|
|
73097
|
-
};
|
|
73098
|
-
const isCallTarget = (expression) => {
|
|
73099
|
-
const parent = expression.parent;
|
|
73100
|
-
return isNodeOfType(parent, "CallExpression") && parent.callee === expression ? parent : null;
|
|
73101
|
-
};
|
|
73102
|
-
const getSynchronousCallbackInvocation = (expression, context) => {
|
|
73103
|
-
if (!executesDuringRender(expression, context.scopes)) return null;
|
|
73104
|
-
const parent = expression.parent;
|
|
73105
|
-
return isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression") ? parent : null;
|
|
73106
|
-
};
|
|
73107
|
-
const isNodeExecutedFromDeferredCallback = (node, snapshotOwner, context, visitedFunctionSymbolIds) => {
|
|
73108
|
-
const enclosingFunction = findEnclosingFunction$1(node);
|
|
73109
|
-
if (!enclosingFunction || enclosingFunction === snapshotOwner) return false;
|
|
73110
|
-
return isFunctionExecutedAsDeferredCallback(enclosingFunction, snapshotOwner, context, visitedFunctionSymbolIds);
|
|
73111
|
-
};
|
|
73112
|
-
const isSymbolExecutedAsDeferredCallback = (symbol, snapshotOwner, context, visitedFunctionSymbolIds) => {
|
|
73113
|
-
if (visitedFunctionSymbolIds.has(symbol.id)) return false;
|
|
73114
|
-
visitedFunctionSymbolIds.add(symbol.id);
|
|
73115
|
-
return symbol.references.some((reference) => {
|
|
73116
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
73117
|
-
if (isDeferredCallbackValue(referenceRoot, context)) return true;
|
|
73118
|
-
const aliasSymbol = getConstAliasSymbol(referenceRoot, context);
|
|
73119
|
-
if (aliasSymbol && isSymbolExecutedAsDeferredCallback(aliasSymbol, snapshotOwner, context, visitedFunctionSymbolIds)) return true;
|
|
73120
|
-
const callTarget = isCallTarget(referenceRoot);
|
|
73121
|
-
if (callTarget) return isNodeExecutedFromDeferredCallback(callTarget, snapshotOwner, context, visitedFunctionSymbolIds);
|
|
73122
|
-
const callbackInvocation = getSynchronousCallbackInvocation(referenceRoot, context);
|
|
73123
|
-
return Boolean(callbackInvocation && isNodeExecutedFromDeferredCallback(callbackInvocation, snapshotOwner, context, visitedFunctionSymbolIds));
|
|
73124
|
-
});
|
|
73125
|
-
};
|
|
73126
|
-
const isFunctionExecutedAsDeferredCallback = (functionNode, snapshotOwner, context, visitedFunctionSymbolIds) => {
|
|
73127
|
-
const functionRoot = findTransparentExpressionRoot(functionNode);
|
|
73128
|
-
if (isDeferredCallbackValue(functionRoot, context)) return true;
|
|
73129
|
-
const synchronousInvocation = getSynchronousCallbackInvocation(functionRoot, context);
|
|
73130
|
-
if (synchronousInvocation) return isNodeExecutedFromDeferredCallback(synchronousInvocation, snapshotOwner, context, visitedFunctionSymbolIds);
|
|
73131
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
73132
|
-
if (!bindingIdentifier) return false;
|
|
73133
|
-
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
73134
|
-
return Boolean(symbol && isSymbolExecutedAsDeferredCallback(symbol, snapshotOwner, context, visitedFunctionSymbolIds));
|
|
73135
|
-
};
|
|
73136
|
-
const isDirectSnapshotAliasInitializer = (identifier) => {
|
|
73137
|
-
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
73138
|
-
const parent = expressionRoot.parent;
|
|
73139
|
-
return Boolean(isNodeOfType(parent, "VariableDeclarator") && parent.init === expressionRoot && isNodeOfType(parent.id, "Identifier"));
|
|
73140
|
-
};
|
|
73141
|
-
const valtioNoSnapshotInCallback = defineRule({
|
|
73142
|
-
id: "valtio-no-snapshot-in-callback",
|
|
73143
|
-
title: "Valtio snapshot read in a callback",
|
|
73144
|
-
severity: "warn",
|
|
73145
|
-
requires: ["valtio", "valtio:1"],
|
|
73146
|
-
recommendation: "Read from the original Valtio proxy inside callbacks. `useSnapshot()` results are for render reads; callback reads can become tracked render dependencies and cause extra re-renders.",
|
|
73147
|
-
create: (context) => ({ Identifier(node) {
|
|
73148
|
-
const reference = context.scopes.referenceFor(node);
|
|
73149
|
-
if (!reference || reference.flag === "write") return;
|
|
73150
|
-
const snapshotOriginCall = getSnapshotOriginCall(node, context);
|
|
73151
|
-
if (!snapshotOriginCall || isDirectSnapshotAliasInitializer(node)) return;
|
|
73152
|
-
const snapshotOwner = findEnclosingFunction$1(snapshotOriginCall);
|
|
73153
|
-
if (!snapshotOwner) return;
|
|
73154
|
-
const referenceOwner = findEnclosingFunction$1(node);
|
|
73155
|
-
if (!referenceOwner || referenceOwner === snapshotOwner) return;
|
|
73156
|
-
if (!isFunctionExecutedAsDeferredCallback(referenceOwner, snapshotOwner, context, /* @__PURE__ */ new Set())) return;
|
|
73157
|
-
context.report({
|
|
73158
|
-
node,
|
|
73159
|
-
message: "This callback reads a Valtio snapshot. Read the original proxy instead so callback-only fields do not become tracked render dependencies."
|
|
73160
|
-
});
|
|
73161
|
-
} })
|
|
73162
|
-
});
|
|
73163
|
-
//#endregion
|
|
73164
71515
|
//#region src/plugin/rules/react-builtins/void-dom-elements-no-children.ts
|
|
73165
71516
|
const VOID_DOM_ELEMENTS = new Set([
|
|
73166
71517
|
"area",
|
|
@@ -73694,516 +72045,6 @@ const zodV4PreferTopLevelStringFormats = defineRule({
|
|
|
73694
72045
|
})
|
|
73695
72046
|
});
|
|
73696
72047
|
//#endregion
|
|
73697
|
-
//#region src/plugin/utils/function-returns-collection-at-path.ts
|
|
73698
|
-
const resolveImmutableInitializer = (expression, scopes, visitedSymbolIds) => {
|
|
73699
|
-
const candidate = stripParenExpression(expression);
|
|
73700
|
-
if (!isNodeOfType(candidate, "Identifier")) return candidate;
|
|
73701
|
-
const symbol = scopes.symbolFor(candidate);
|
|
73702
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return candidate;
|
|
73703
|
-
visitedSymbolIds.add(symbol.id);
|
|
73704
|
-
return resolveImmutableInitializer(symbol.initializer, scopes, visitedSymbolIds);
|
|
73705
|
-
};
|
|
73706
|
-
const expressionAtPropertyPath = (expression, propertyPath, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73707
|
-
const candidate = resolveImmutableInitializer(expression, scopes, visitedSymbolIds);
|
|
73708
|
-
const [propertyName, ...remainingPath] = propertyPath;
|
|
73709
|
-
if (!propertyName) return candidate;
|
|
73710
|
-
if (!isNodeOfType(candidate, "ObjectExpression")) return null;
|
|
73711
|
-
for (const property of candidate.properties) {
|
|
73712
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
73713
|
-
if (getStaticPropertyKeyName(property) !== propertyName) continue;
|
|
73714
|
-
return expressionAtPropertyPath(property.value, remainingPath, scopes, visitedSymbolIds);
|
|
73715
|
-
}
|
|
73716
|
-
return null;
|
|
73717
|
-
};
|
|
73718
|
-
const isGlobalConstructor = (expression, constructorNames, scopes) => {
|
|
73719
|
-
const candidate = stripParenExpression(expression);
|
|
73720
|
-
return isNodeOfType(candidate, "Identifier") && constructorNames.has(candidate.name) && scopes.isGlobalReference(candidate);
|
|
73721
|
-
};
|
|
73722
|
-
const isCollectionExpression = (expression, collectionKind, scopes) => {
|
|
73723
|
-
const candidate = stripParenExpression(expression);
|
|
73724
|
-
if (collectionKind === "array" && isNodeOfType(candidate, "ArrayExpression")) return true;
|
|
73725
|
-
if (!isNodeOfType(candidate, "NewExpression")) return false;
|
|
73726
|
-
return isGlobalConstructor(candidate.callee, collectionKind === "array" ? new Set(["Array"]) : new Set(["Map", "Set"]), scopes);
|
|
73727
|
-
};
|
|
73728
|
-
const functionReturnsCollectionAtPath = ({ collectionKind, functionNode, propertyPath, scopes }) => {
|
|
73729
|
-
if (!isFunctionLike$1(functionNode)) return false;
|
|
73730
|
-
const returnedExpressions = isNodeOfType(functionNode.body, "BlockStatement") ? collectFunctionReturnStatements(functionNode).flatMap((returnStatement) => returnStatement.argument ? [returnStatement.argument] : []) : [functionNode.body];
|
|
73731
|
-
return returnedExpressions.length > 0 && returnedExpressions.every((returnedExpression) => {
|
|
73732
|
-
const collectionExpression = expressionAtPropertyPath(returnedExpression, propertyPath, scopes);
|
|
73733
|
-
return Boolean(collectionExpression && isCollectionExpression(collectionExpression, collectionKind, scopes));
|
|
73734
|
-
});
|
|
73735
|
-
};
|
|
73736
|
-
//#endregion
|
|
73737
|
-
//#region src/plugin/utils/resolve-zustand-api.ts
|
|
73738
|
-
const ZUSTAND_APIS_BY_MODULE = new Map([
|
|
73739
|
-
["zustand", new Set([
|
|
73740
|
-
"create",
|
|
73741
|
-
"createStore",
|
|
73742
|
-
"useStore"
|
|
73743
|
-
])],
|
|
73744
|
-
["zustand/vanilla", new Set(["createStore"])],
|
|
73745
|
-
["zustand/react", new Set(["useStore"])],
|
|
73746
|
-
["zustand/traditional", new Set(["createWithEqualityFn", "useStoreWithEqualityFn"])],
|
|
73747
|
-
["zustand/shallow", new Set(["shallow", "useShallow"])],
|
|
73748
|
-
["zustand/react/shallow", new Set(["useShallow"])],
|
|
73749
|
-
["zustand/middleware", new Set([
|
|
73750
|
-
"combine",
|
|
73751
|
-
"createJSONStorage",
|
|
73752
|
-
"devtools",
|
|
73753
|
-
"persist",
|
|
73754
|
-
"redux",
|
|
73755
|
-
"subscribeWithSelector"
|
|
73756
|
-
])],
|
|
73757
|
-
["zustand/middleware/immer", new Set(["immer"])]
|
|
73758
|
-
]);
|
|
73759
|
-
const STATE_CREATOR_MIDDLEWARE_ARGUMENT_INDEX = new Map([
|
|
73760
|
-
["combine", 1],
|
|
73761
|
-
["devtools", 0],
|
|
73762
|
-
["immer", 0],
|
|
73763
|
-
["persist", 0],
|
|
73764
|
-
["subscribeWithSelector", 0]
|
|
73765
|
-
]);
|
|
73766
|
-
const toZustandApiName = (value) => {
|
|
73767
|
-
switch (value) {
|
|
73768
|
-
case "combine":
|
|
73769
|
-
case "create":
|
|
73770
|
-
case "createJSONStorage":
|
|
73771
|
-
case "createStore":
|
|
73772
|
-
case "createWithEqualityFn":
|
|
73773
|
-
case "devtools":
|
|
73774
|
-
case "immer":
|
|
73775
|
-
case "persist":
|
|
73776
|
-
case "redux":
|
|
73777
|
-
case "shallow":
|
|
73778
|
-
case "subscribeWithSelector":
|
|
73779
|
-
case "useShallow":
|
|
73780
|
-
case "useStore":
|
|
73781
|
-
case "useStoreWithEqualityFn": return value;
|
|
73782
|
-
default: return null;
|
|
73783
|
-
}
|
|
73784
|
-
};
|
|
73785
|
-
const getImportSource = (symbol) => {
|
|
73786
|
-
const declaration = getImportDeclarationForSymbol(symbol);
|
|
73787
|
-
if (!declaration || isTypeOnlyImport(declaration) || isNodeOfType(symbol.declarationNode, "ImportSpecifier") && symbol.declarationNode.importKind === "type") return null;
|
|
73788
|
-
return typeof declaration.source.value === "string" ? declaration.source.value : null;
|
|
73789
|
-
};
|
|
73790
|
-
const bindingFromImportSymbol = (symbol) => {
|
|
73791
|
-
const moduleSource = getImportSource(symbol);
|
|
73792
|
-
const supportedApiNames = moduleSource ? ZUSTAND_APIS_BY_MODULE.get(moduleSource) : null;
|
|
73793
|
-
if (!moduleSource || !supportedApiNames) return null;
|
|
73794
|
-
if (moduleSource === "zustand" && isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return {
|
|
73795
|
-
apiName: "create",
|
|
73796
|
-
moduleSource
|
|
73797
|
-
};
|
|
73798
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
73799
|
-
const apiName = importedName ? toZustandApiName(importedName) : null;
|
|
73800
|
-
return apiName && supportedApiNames.has(apiName) ? {
|
|
73801
|
-
apiName,
|
|
73802
|
-
moduleSource
|
|
73803
|
-
} : null;
|
|
73804
|
-
};
|
|
73805
|
-
const namespaceImportSource = (expression, scopes) => {
|
|
73806
|
-
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
73807
|
-
const symbol = resolveConstIdentifierAlias(expression, scopes);
|
|
73808
|
-
if (!symbol || !isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return null;
|
|
73809
|
-
return getImportSource(symbol);
|
|
73810
|
-
};
|
|
73811
|
-
const resolveZustandApiBinding = (expression, scopes) => {
|
|
73812
|
-
const candidate = stripParenExpression(expression);
|
|
73813
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
73814
|
-
const symbol = resolveConstIdentifierAlias(candidate, scopes);
|
|
73815
|
-
if (!symbol) return null;
|
|
73816
|
-
if (symbol.kind === "import") return bindingFromImportSymbol(symbol);
|
|
73817
|
-
if (symbol.kind !== "const" || !symbol.initializer) return null;
|
|
73818
|
-
return resolveZustandApiBinding(symbol.initializer, scopes);
|
|
73819
|
-
}
|
|
73820
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
73821
|
-
const propertyName = getStaticPropertyName(candidate);
|
|
73822
|
-
const moduleSource = namespaceImportSource(stripParenExpression(candidate.object), scopes);
|
|
73823
|
-
const supportedApiNames = moduleSource ? ZUSTAND_APIS_BY_MODULE.get(moduleSource) : null;
|
|
73824
|
-
const apiName = propertyName ? toZustandApiName(propertyName) : null;
|
|
73825
|
-
return apiName && moduleSource && supportedApiNames?.has(apiName) ? {
|
|
73826
|
-
apiName,
|
|
73827
|
-
moduleSource
|
|
73828
|
-
} : null;
|
|
73829
|
-
};
|
|
73830
|
-
const resolveStateCreator = (expression, scopes, middlewareNames, visitedExpressions = /* @__PURE__ */ new Set()) => {
|
|
73831
|
-
const candidate = stripParenExpression(expression);
|
|
73832
|
-
if (visitedExpressions.has(candidate)) return null;
|
|
73833
|
-
visitedExpressions.add(candidate);
|
|
73834
|
-
const creatorFunction = resolveExactLocalFunction(expression, scopes);
|
|
73835
|
-
if (isFunctionLike$1(creatorFunction)) return creatorFunction;
|
|
73836
|
-
if (!isNodeOfType(candidate, "CallExpression")) return null;
|
|
73837
|
-
const middleware = resolveZustandApiBinding(candidate.callee, scopes);
|
|
73838
|
-
const creatorArgumentIndex = middleware ? STATE_CREATOR_MIDDLEWARE_ARGUMENT_INDEX.get(middleware.apiName) : null;
|
|
73839
|
-
if (!middleware || creatorArgumentIndex === void 0 || creatorArgumentIndex === null) return null;
|
|
73840
|
-
const creatorArgument = candidate.arguments[creatorArgumentIndex];
|
|
73841
|
-
if (!creatorArgument || isNodeOfType(creatorArgument, "SpreadElement")) return null;
|
|
73842
|
-
middlewareNames.add(middleware.apiName);
|
|
73843
|
-
return resolveStateCreator(creatorArgument, scopes, middlewareNames, visitedExpressions);
|
|
73844
|
-
};
|
|
73845
|
-
const isStoreFactoryApi = (binding) => binding?.apiName === "create" || binding?.apiName === "createStore" || binding?.apiName === "createWithEqualityFn";
|
|
73846
|
-
const resolveCurriedStoreFactoryBinding = (expression, scopes) => {
|
|
73847
|
-
const candidate = stripParenExpression(expression);
|
|
73848
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
73849
|
-
const symbol = resolveConstIdentifierAlias(candidate, scopes);
|
|
73850
|
-
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
73851
|
-
return resolveCurriedStoreFactoryBinding(symbol.initializer, scopes);
|
|
73852
|
-
}
|
|
73853
|
-
if (!isNodeOfType(candidate, "CallExpression") || candidate.arguments.length > 0) return null;
|
|
73854
|
-
const factoryBinding = resolveZustandApiBinding(candidate.callee, scopes);
|
|
73855
|
-
return isStoreFactoryApi(factoryBinding) ? factoryBinding : null;
|
|
73856
|
-
};
|
|
73857
|
-
const resolveZustandStoreCreator = (callExpression, scopes) => {
|
|
73858
|
-
const factoryCall = resolveZustandStoreFactoryCall(callExpression, scopes);
|
|
73859
|
-
if (!factoryCall) return null;
|
|
73860
|
-
const middlewareNames = /* @__PURE__ */ new Set();
|
|
73861
|
-
const creatorFunction = resolveStateCreator(factoryCall.creatorArgument, scopes, middlewareNames);
|
|
73862
|
-
if (!creatorFunction) return null;
|
|
73863
|
-
return {
|
|
73864
|
-
creatorFunction,
|
|
73865
|
-
factoryApiName: factoryCall.factoryApiName,
|
|
73866
|
-
middlewareNames
|
|
73867
|
-
};
|
|
73868
|
-
};
|
|
73869
|
-
const resolveZustandStoreFactoryCall = (callExpression, scopes) => {
|
|
73870
|
-
let factoryBinding = resolveZustandApiBinding(callExpression.callee, scopes);
|
|
73871
|
-
if (!isStoreFactoryApi(factoryBinding)) {
|
|
73872
|
-
factoryBinding = resolveCurriedStoreFactoryBinding(callExpression.callee, scopes);
|
|
73873
|
-
if (!isStoreFactoryApi(factoryBinding)) return null;
|
|
73874
|
-
}
|
|
73875
|
-
const creatorArgument = callExpression.arguments[0];
|
|
73876
|
-
if (!creatorArgument || isNodeOfType(creatorArgument, "SpreadElement")) return null;
|
|
73877
|
-
return {
|
|
73878
|
-
callExpression,
|
|
73879
|
-
creatorArgument,
|
|
73880
|
-
factoryApiName: factoryBinding.apiName
|
|
73881
|
-
};
|
|
73882
|
-
};
|
|
73883
|
-
//#endregion
|
|
73884
|
-
//#region src/plugin/rules/state-and-effects/zustand-no-fresh-selector-result.ts
|
|
73885
|
-
const ALLOCATING_ARRAY_METHODS = new Set([
|
|
73886
|
-
"filter",
|
|
73887
|
-
"flat",
|
|
73888
|
-
"flatMap",
|
|
73889
|
-
"map",
|
|
73890
|
-
"toReversed",
|
|
73891
|
-
"toSorted",
|
|
73892
|
-
"toSpliced",
|
|
73893
|
-
"with"
|
|
73894
|
-
]);
|
|
73895
|
-
const SAME_REFERENCE_ARRAY_METHODS = new Set(["reverse", "sort"]);
|
|
73896
|
-
const ALLOCATING_NAMESPACE_METHOD_KINDS = new Map([["Array", new Map([["from", "array"], ["of", "array"]])], ["Object", new Map([
|
|
73897
|
-
["create", "object"],
|
|
73898
|
-
["entries", "array"],
|
|
73899
|
-
["fromEntries", "object"],
|
|
73900
|
-
["keys", "array"],
|
|
73901
|
-
["values", "array"]
|
|
73902
|
-
])]]);
|
|
73903
|
-
const isNullishEqualityArgument = (argument, scopes) => {
|
|
73904
|
-
const candidate = stripParenExpression(argument);
|
|
73905
|
-
return isNodeOfType(candidate, "Identifier") && candidate.name === "undefined" && scopes.isGlobalReference(candidate) || isNodeOfType(candidate, "Literal") && candidate.value === null || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "void";
|
|
73906
|
-
};
|
|
73907
|
-
const hasExplicitEqualityArgument = (argumentsList, equalityArgumentIndex, scopes) => {
|
|
73908
|
-
const equalityArgument = argumentsList[equalityArgumentIndex];
|
|
73909
|
-
if (!equalityArgument) return false;
|
|
73910
|
-
if (isNodeOfType(equalityArgument, "SpreadElement")) return true;
|
|
73911
|
-
return !isNullishEqualityArgument(equalityArgument, scopes);
|
|
73912
|
-
};
|
|
73913
|
-
const resolveZustandStoreCreation = (expression, scopes) => {
|
|
73914
|
-
const candidate = stripParenExpression(expression);
|
|
73915
|
-
if (!isNodeOfType(candidate, "CallExpression")) return null;
|
|
73916
|
-
const factoryCall = resolveZustandStoreFactoryCall(candidate, scopes);
|
|
73917
|
-
if (!factoryCall || factoryCall.factoryApiName !== "create" && factoryCall.factoryApiName !== "createWithEqualityFn") return null;
|
|
73918
|
-
return {
|
|
73919
|
-
creatorFunction: resolveZustandStoreCreator(candidate, scopes)?.creatorFunction ?? null,
|
|
73920
|
-
hasDefaultEquality: factoryCall.factoryApiName === "createWithEqualityFn" && hasExplicitEqualityArgument(candidate.arguments, 1, scopes),
|
|
73921
|
-
supportsEqualityArgument: factoryCall.factoryApiName === "createWithEqualityFn"
|
|
73922
|
-
};
|
|
73923
|
-
};
|
|
73924
|
-
const resolveZustandBoundStore = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73925
|
-
const candidate = stripParenExpression(expression);
|
|
73926
|
-
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
73927
|
-
const symbol = scopes.symbolFor(candidate);
|
|
73928
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
73929
|
-
visitedSymbolIds.add(symbol.id);
|
|
73930
|
-
return resolveZustandStoreCreation(symbol.initializer, scopes) ?? resolveZustandBoundStore(symbol.initializer, scopes, visitedSymbolIds);
|
|
73931
|
-
};
|
|
73932
|
-
const getZustandSelectorCall = (callExpression, scopes) => {
|
|
73933
|
-
const apiBinding = resolveZustandApiBinding(callExpression.callee, scopes);
|
|
73934
|
-
if (apiBinding?.apiName === "useStore" || apiBinding?.apiName === "useStoreWithEqualityFn") {
|
|
73935
|
-
const selector = callExpression.arguments[1];
|
|
73936
|
-
if (!selector || isNodeOfType(selector, "SpreadElement")) return null;
|
|
73937
|
-
if (apiBinding.apiName === "useStoreWithEqualityFn" && hasExplicitEqualityArgument(callExpression.arguments, 2, scopes)) return null;
|
|
73938
|
-
return {
|
|
73939
|
-
selector,
|
|
73940
|
-
storeCreatorFunction: null
|
|
73941
|
-
};
|
|
73942
|
-
}
|
|
73943
|
-
const boundStore = resolveZustandBoundStore(callExpression.callee, scopes);
|
|
73944
|
-
const selector = callExpression.arguments[0];
|
|
73945
|
-
if (!boundStore || !selector || isNodeOfType(selector, "SpreadElement")) return null;
|
|
73946
|
-
if (boundStore.hasDefaultEquality || boundStore.supportsEqualityArgument && hasExplicitEqualityArgument(callExpression.arguments, 1, scopes)) return null;
|
|
73947
|
-
return {
|
|
73948
|
-
selector,
|
|
73949
|
-
storeCreatorFunction: boundStore.creatorFunction
|
|
73950
|
-
};
|
|
73951
|
-
};
|
|
73952
|
-
const isUseShallowCall = (callExpression, scopes) => resolveZustandApiBinding(callExpression.callee, scopes)?.apiName === "useShallow";
|
|
73953
|
-
const resolveSelectorFunction = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73954
|
-
const candidate = stripParenExpression(expression);
|
|
73955
|
-
if (isFunctionLike$1(candidate)) return candidate;
|
|
73956
|
-
if (isNodeOfType(candidate, "CallExpression")) {
|
|
73957
|
-
if (isUseShallowCall(candidate, scopes)) return null;
|
|
73958
|
-
if (!isReactApiCall(candidate, "useCallback", scopes, {
|
|
73959
|
-
allowGlobalReactNamespace: true,
|
|
73960
|
-
resolveNamedAliases: true
|
|
73961
|
-
})) return null;
|
|
73962
|
-
const callback = candidate.arguments[0];
|
|
73963
|
-
if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
|
|
73964
|
-
return resolveSelectorFunction(callback, scopes, visitedSymbolIds);
|
|
73965
|
-
}
|
|
73966
|
-
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
73967
|
-
const symbol = scopes.symbolFor(candidate);
|
|
73968
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
73969
|
-
if (symbol.kind === "function" && isFunctionLike$1(symbol.declarationNode)) return symbol.declarationNode;
|
|
73970
|
-
if (symbol.kind !== "const" || !symbol.initializer) return null;
|
|
73971
|
-
visitedSymbolIds.add(symbol.id);
|
|
73972
|
-
return resolveSelectorFunction(symbol.initializer, scopes, visitedSymbolIds);
|
|
73973
|
-
};
|
|
73974
|
-
const selectorStatePropertyPath = (expression, analysis, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
73975
|
-
const candidate = stripParenExpression(expression);
|
|
73976
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
73977
|
-
const symbol = analysis.scopes.symbolFor(candidate);
|
|
73978
|
-
const selectorParameter = isFunctionLike$1(analysis.selectorFunction) ? analysis.selectorFunction.params[0] : null;
|
|
73979
|
-
if (selectorParameter && isNodeOfType(selectorParameter, "Identifier") && symbol?.id === analysis.scopes.symbolFor(selectorParameter)?.id) return [];
|
|
73980
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
73981
|
-
visitedSymbolIds.add(symbol.id);
|
|
73982
|
-
return selectorStatePropertyPath(symbol.initializer, analysis, visitedSymbolIds);
|
|
73983
|
-
}
|
|
73984
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
73985
|
-
const propertyName = getStaticPropertyName(candidate);
|
|
73986
|
-
const objectPath = selectorStatePropertyPath(candidate.object, analysis, visitedSymbolIds);
|
|
73987
|
-
return propertyName && objectPath ? [...objectPath, propertyName] : null;
|
|
73988
|
-
};
|
|
73989
|
-
const freshResultFromAllocatingCall = (expression, analysis, visitedSymbolIds) => {
|
|
73990
|
-
const callee = stripParenExpression(expression.callee);
|
|
73991
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
73992
|
-
const methodName = getStaticPropertyName(callee);
|
|
73993
|
-
if (!methodName) return null;
|
|
73994
|
-
const receiver = stripParenExpression(callee.object);
|
|
73995
|
-
if (isNodeOfType(receiver, "Identifier") && analysis.scopes.isGlobalReference(receiver)) {
|
|
73996
|
-
const resultKind = ALLOCATING_NAMESPACE_METHOD_KINDS.get(receiver.name)?.get(methodName);
|
|
73997
|
-
if (resultKind) return {
|
|
73998
|
-
kind: resultKind,
|
|
73999
|
-
node: expression
|
|
74000
|
-
};
|
|
74001
|
-
if (receiver.name === "Object" && methodName === "assign") {
|
|
74002
|
-
const target = expression.arguments[0];
|
|
74003
|
-
if (!target || isNodeOfType(target, "SpreadElement")) return null;
|
|
74004
|
-
const freshTarget = resolveFreshSelectorResult(target, analysis, new Set(visitedSymbolIds));
|
|
74005
|
-
return freshTarget ? {
|
|
74006
|
-
kind: freshTarget.kind,
|
|
74007
|
-
node: expression
|
|
74008
|
-
} : null;
|
|
74009
|
-
}
|
|
74010
|
-
}
|
|
74011
|
-
if (ALLOCATING_ARRAY_METHODS.has(methodName)) {
|
|
74012
|
-
if (resolveFreshSelectorResult(receiver, analysis, new Set(visitedSymbolIds))?.kind === "array") return {
|
|
74013
|
-
kind: "array",
|
|
74014
|
-
node: expression
|
|
74015
|
-
};
|
|
74016
|
-
const receiverPath = selectorStatePropertyPath(receiver, analysis);
|
|
74017
|
-
if (receiverPath && analysis.storeCreatorFunction && functionReturnsCollectionAtPath({
|
|
74018
|
-
collectionKind: "array",
|
|
74019
|
-
functionNode: analysis.storeCreatorFunction,
|
|
74020
|
-
propertyPath: receiverPath,
|
|
74021
|
-
scopes: analysis.scopes
|
|
74022
|
-
})) return {
|
|
74023
|
-
kind: "array",
|
|
74024
|
-
node: expression
|
|
74025
|
-
};
|
|
74026
|
-
return null;
|
|
74027
|
-
}
|
|
74028
|
-
if (!SAME_REFERENCE_ARRAY_METHODS.has(methodName)) return null;
|
|
74029
|
-
return resolveFreshSelectorResult(receiver, analysis, new Set(visitedSymbolIds)) ? {
|
|
74030
|
-
kind: "array",
|
|
74031
|
-
node: expression
|
|
74032
|
-
} : null;
|
|
74033
|
-
};
|
|
74034
|
-
const resolveFreshSelectorResult = (expression, analysis, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
74035
|
-
const freshRenderValue = resolveFreshRenderValue(expression, analysis.scopes);
|
|
74036
|
-
if (freshRenderValue?.kind === "array" || freshRenderValue?.kind === "function" || freshRenderValue?.kind === "instance" || freshRenderValue?.kind === "object") return {
|
|
74037
|
-
kind: freshRenderValue.kind,
|
|
74038
|
-
node: expression
|
|
74039
|
-
};
|
|
74040
|
-
const candidate = stripParenExpression(expression);
|
|
74041
|
-
if (isNodeOfType(candidate, "CallExpression")) return freshResultFromAllocatingCall(candidate, analysis, visitedSymbolIds);
|
|
74042
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return resolveFreshSelectorResult(candidate.consequent, analysis, new Set(visitedSymbolIds)) ?? resolveFreshSelectorResult(candidate.alternate, analysis, new Set(visitedSymbolIds));
|
|
74043
|
-
if (isNodeOfType(candidate, "LogicalExpression")) {
|
|
74044
|
-
if (candidate.operator === "&&") return resolveFreshSelectorResult(candidate.right, analysis, visitedSymbolIds);
|
|
74045
|
-
return resolveFreshSelectorResult(candidate.left, analysis, new Set(visitedSymbolIds)) ?? resolveFreshSelectorResult(candidate.right, analysis, new Set(visitedSymbolIds));
|
|
74046
|
-
}
|
|
74047
|
-
if (isNodeOfType(candidate, "SequenceExpression")) {
|
|
74048
|
-
const returnedExpression = candidate.expressions[candidate.expressions.length - 1];
|
|
74049
|
-
return returnedExpression ? resolveFreshSelectorResult(returnedExpression, analysis, visitedSymbolIds) : null;
|
|
74050
|
-
}
|
|
74051
|
-
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
74052
|
-
const symbol = analysis.scopes.symbolFor(candidate);
|
|
74053
|
-
if (symbol?.kind !== "const" || symbol.scope.kind === "module" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
74054
|
-
visitedSymbolIds.add(symbol.id);
|
|
74055
|
-
return resolveFreshSelectorResult(symbol.initializer, analysis, visitedSymbolIds);
|
|
74056
|
-
};
|
|
74057
|
-
const findFreshSelectorReturn = (selectorFunction, scopes, storeCreatorFunction) => {
|
|
74058
|
-
if (!isFunctionLike$1(selectorFunction) || !selectorFunction.body) return null;
|
|
74059
|
-
const analysis = {
|
|
74060
|
-
scopes,
|
|
74061
|
-
selectorFunction,
|
|
74062
|
-
storeCreatorFunction
|
|
74063
|
-
};
|
|
74064
|
-
if (!isNodeOfType(selectorFunction.body, "BlockStatement")) return resolveFreshSelectorResult(selectorFunction.body, analysis);
|
|
74065
|
-
let freshResult = null;
|
|
74066
|
-
walkAst(selectorFunction.body, (candidate) => {
|
|
74067
|
-
if (freshResult) return false;
|
|
74068
|
-
if (candidate !== selectorFunction.body && isFunctionLike$1(candidate)) return false;
|
|
74069
|
-
if (!isNodeOfType(candidate, "ReturnStatement") || !candidate.argument) return;
|
|
74070
|
-
freshResult = resolveFreshSelectorResult(candidate.argument, analysis);
|
|
74071
|
-
return freshResult ? false : void 0;
|
|
74072
|
-
});
|
|
74073
|
-
return freshResult;
|
|
74074
|
-
};
|
|
74075
|
-
const zustandNoFreshSelectorResult = defineRule({
|
|
74076
|
-
id: "zustand-no-fresh-selector-result",
|
|
74077
|
-
title: "Zustand selector returns a fresh value",
|
|
74078
|
-
severity: "error",
|
|
74079
|
-
category: "Performance",
|
|
74080
|
-
requires: ["zustand", "zustand:5"],
|
|
74081
|
-
recommendation: "Select a stable store field, split the selector, or wrap a collection selector with `useShallow`.",
|
|
74082
|
-
create: (context) => ({ CallExpression(node) {
|
|
74083
|
-
const selectorCall = getZustandSelectorCall(node, context.scopes);
|
|
74084
|
-
if (!selectorCall) return;
|
|
74085
|
-
const selectorFunction = resolveSelectorFunction(selectorCall.selector, context.scopes);
|
|
74086
|
-
if (!selectorFunction) return;
|
|
74087
|
-
const freshResult = findFreshSelectorReturn(selectorFunction, context.scopes, selectorCall.storeCreatorFunction);
|
|
74088
|
-
if (!freshResult) return;
|
|
74089
|
-
context.report({
|
|
74090
|
-
node: freshResult.node,
|
|
74091
|
-
message: "This Zustand selector creates a new reference whenever the store is read, so Object.is never sees a stable snapshot and Zustand v5 can repeatedly render or hit maximum update depth. Select a stable field or use `useShallow`."
|
|
74092
|
-
});
|
|
74093
|
-
} })
|
|
74094
|
-
});
|
|
74095
|
-
//#endregion
|
|
74096
|
-
//#region src/plugin/rules/state-and-effects/zustand-no-get-during-initialization.ts
|
|
74097
|
-
const findGetParameterIdentifier = (parameter) => {
|
|
74098
|
-
if (!parameter) return null;
|
|
74099
|
-
if (isNodeOfType(parameter, "Identifier")) return parameter;
|
|
74100
|
-
if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
|
|
74101
|
-
return null;
|
|
74102
|
-
};
|
|
74103
|
-
const isGetParameterCall = (callExpression, getParameterSymbol, context) => {
|
|
74104
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
74105
|
-
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
74106
|
-
return resolveConstIdentifierAlias(callee, context.scopes)?.id === getParameterSymbol.id;
|
|
74107
|
-
};
|
|
74108
|
-
const reportEagerGetCalls = (creatorFunction, getParameterSymbol, context) => {
|
|
74109
|
-
const visitedFunctions = /* @__PURE__ */ new Set();
|
|
74110
|
-
const reportFunctionReads = (functionNode) => {
|
|
74111
|
-
if (!isFunctionLike$1(functionNode) || functionNode.generator || visitedFunctions.has(functionNode)) return;
|
|
74112
|
-
visitedFunctions.add(functionNode);
|
|
74113
|
-
walkAst(functionNode, (node) => {
|
|
74114
|
-
if (node !== functionNode && isFunctionLike$1(node)) return false;
|
|
74115
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
74116
|
-
if (isGetParameterCall(node, getParameterSymbol, context)) {
|
|
74117
|
-
if (!canExecuteBeforeAsyncSuspension(node, functionNode, context)) return;
|
|
74118
|
-
context.report({
|
|
74119
|
-
node,
|
|
74120
|
-
message: "`get()` runs before Zustand installs the initial state, so it returns `undefined` here."
|
|
74121
|
-
});
|
|
74122
|
-
return;
|
|
74123
|
-
}
|
|
74124
|
-
if (!canExecuteBeforeAsyncSuspension(node, functionNode, context)) return;
|
|
74125
|
-
const calledFunction = resolveExactLocalFunction(node.callee, context.scopes);
|
|
74126
|
-
if (!calledFunction || calledFunction === functionNode) return;
|
|
74127
|
-
reportFunctionReads(calledFunction);
|
|
74128
|
-
});
|
|
74129
|
-
};
|
|
74130
|
-
reportFunctionReads(creatorFunction);
|
|
74131
|
-
};
|
|
74132
|
-
const zustandNoGetDuringInitialization = defineRule({
|
|
74133
|
-
id: "zustand-no-get-during-initialization",
|
|
74134
|
-
title: "Zustand get() called during store initialization",
|
|
74135
|
-
severity: "error",
|
|
74136
|
-
category: "Correctness",
|
|
74137
|
-
recommendation: "Move the read into a deferred store action or derive the initial value without calling `get()`.",
|
|
74138
|
-
requires: ["zustand", "zustand:1"],
|
|
74139
|
-
create: (context) => {
|
|
74140
|
-
const creatorFunctions = /* @__PURE__ */ new Set();
|
|
74141
|
-
return {
|
|
74142
|
-
CallExpression(node) {
|
|
74143
|
-
const creator = resolveZustandStoreCreator(node, context.scopes);
|
|
74144
|
-
if (creator) creatorFunctions.add(creator.creatorFunction);
|
|
74145
|
-
},
|
|
74146
|
-
"Program:exit"() {
|
|
74147
|
-
for (const creatorFunction of creatorFunctions) {
|
|
74148
|
-
if (creatorFunction.generator) continue;
|
|
74149
|
-
const parameterIdentifier = findGetParameterIdentifier(creatorFunction.params[1]);
|
|
74150
|
-
const getParameterSymbol = parameterIdentifier ? context.scopes.symbolFor(parameterIdentifier) : null;
|
|
74151
|
-
if (!getParameterSymbol) continue;
|
|
74152
|
-
reportEagerGetCalls(creatorFunction, getParameterSymbol, context);
|
|
74153
|
-
}
|
|
74154
|
-
}
|
|
74155
|
-
};
|
|
74156
|
-
}
|
|
74157
|
-
});
|
|
74158
|
-
//#endregion
|
|
74159
|
-
//#region src/plugin/rules/state-and-effects/zustand-no-whole-store-destructure.ts
|
|
74160
|
-
const BOUND_STORE_FACTORY_APIS = new Set(["create", "createWithEqualityFn"]);
|
|
74161
|
-
const VANILLA_STORE_FACTORY_APIS = new Set(["createStore"]);
|
|
74162
|
-
const isStoreFactoryValue = (rawValue, factoryApiNames, scopes) => {
|
|
74163
|
-
const value = stripParenExpression(rawValue);
|
|
74164
|
-
if (!isNodeOfType(value, "CallExpression")) return false;
|
|
74165
|
-
const factoryCall = resolveZustandStoreFactoryCall(value, scopes);
|
|
74166
|
-
return Boolean(factoryCall && factoryApiNames.has(factoryCall.factoryApiName));
|
|
74167
|
-
};
|
|
74168
|
-
const isStoreValue = (rawValue, factoryApiNames, scopes) => {
|
|
74169
|
-
const value = stripParenExpression(rawValue);
|
|
74170
|
-
if (isStoreFactoryValue(value, factoryApiNames, scopes)) return true;
|
|
74171
|
-
if (!isNodeOfType(value, "Identifier")) return false;
|
|
74172
|
-
const symbol = resolveConstIdentifierAlias(value, scopes);
|
|
74173
|
-
return Boolean(symbol?.kind === "const" && symbol.initializer && isStoreFactoryValue(symbol.initializer, factoryApiNames, scopes));
|
|
74174
|
-
};
|
|
74175
|
-
const isBoundStoreCall = (node, scopes) => {
|
|
74176
|
-
const callee = stripParenExpression(node.callee);
|
|
74177
|
-
return isNodeOfType(callee, "Identifier") && isStoreValue(callee, BOUND_STORE_FACTORY_APIS, scopes);
|
|
74178
|
-
};
|
|
74179
|
-
const isVanillaStoreHookCall = (node, scopes) => {
|
|
74180
|
-
if (node.arguments.length !== 1) return false;
|
|
74181
|
-
const hook = resolveZustandApiBinding(node.callee, scopes);
|
|
74182
|
-
if (hook?.apiName !== "useStore" && hook?.apiName !== "useStoreWithEqualityFn") return false;
|
|
74183
|
-
return isStoreValue(node.arguments[0], VANILLA_STORE_FACTORY_APIS, scopes);
|
|
74184
|
-
};
|
|
74185
|
-
const isDirectReactRenderCall = (node, context) => {
|
|
74186
|
-
const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
74187
|
-
if (!renderFunction || findEnclosingFunction$1(node) !== renderFunction) return false;
|
|
74188
|
-
const displayName = componentOrHookDisplayNameForFunction(renderFunction);
|
|
74189
|
-
return Boolean(displayName && (isReactHookName(displayName) || functionHasReactComponentEvidence(renderFunction, context.scopes, context.cfg)));
|
|
74190
|
-
};
|
|
74191
|
-
const zustandNoWholeStoreDestructure = defineRule({
|
|
74192
|
-
id: "zustand-no-whole-store-destructure",
|
|
74193
|
-
title: "Whole Zustand store subscribed during render",
|
|
74194
|
-
severity: "warn",
|
|
74195
|
-
requires: ["zustand:1"],
|
|
74196
|
-
recommendation: "Pass a selector to the Zustand hook so this component rerenders only when the state it reads changes.",
|
|
74197
|
-
create: (context) => ({ CallExpression(node) {
|
|
74198
|
-
if (!isDirectReactRenderCall(node, context)) return;
|
|
74199
|
-
if (!(node.arguments.length === 0 && isBoundStoreCall(node, context.scopes)) && !isVanillaStoreHookCall(node, context.scopes)) return;
|
|
74200
|
-
context.report({
|
|
74201
|
-
node,
|
|
74202
|
-
message: "This hook subscribes to the whole Zustand store, so every store update rerenders this component. Pass a selector for the state it reads."
|
|
74203
|
-
});
|
|
74204
|
-
} })
|
|
74205
|
-
});
|
|
74206
|
-
//#endregion
|
|
74207
72048
|
//#region src/plugin/rule-registry.ts
|
|
74208
72049
|
const reactDoctorRules = [
|
|
74209
72050
|
{
|
|
@@ -75518,39 +73359,6 @@ const reactDoctorRules = [
|
|
|
75518
73359
|
requires: [...new Set(["react", ...mediaHasCaption.requires ?? []])]
|
|
75519
73360
|
}
|
|
75520
73361
|
},
|
|
75521
|
-
{
|
|
75522
|
-
key: "react-doctor/mobx-no-make-auto-observable-in-inheritance",
|
|
75523
|
-
id: "mobx-no-make-auto-observable-in-inheritance",
|
|
75524
|
-
source: "react-doctor",
|
|
75525
|
-
originallyExternal: false,
|
|
75526
|
-
rule: {
|
|
75527
|
-
...mobxNoMakeAutoObservableInInheritance,
|
|
75528
|
-
framework: "global",
|
|
75529
|
-
category: "Bugs"
|
|
75530
|
-
}
|
|
75531
|
-
},
|
|
75532
|
-
{
|
|
75533
|
-
key: "react-doctor/mobx-no-observer-wrapped-memo",
|
|
75534
|
-
id: "mobx-no-observer-wrapped-memo",
|
|
75535
|
-
source: "react-doctor",
|
|
75536
|
-
originallyExternal: false,
|
|
75537
|
-
rule: {
|
|
75538
|
-
...mobxNoObserverWrappedMemo,
|
|
75539
|
-
framework: "global",
|
|
75540
|
-
category: "Bugs"
|
|
75541
|
-
}
|
|
75542
|
-
},
|
|
75543
|
-
{
|
|
75544
|
-
key: "react-doctor/mobx-reaction-disposer-discarded",
|
|
75545
|
-
id: "mobx-reaction-disposer-discarded",
|
|
75546
|
-
source: "react-doctor",
|
|
75547
|
-
originallyExternal: false,
|
|
75548
|
-
rule: {
|
|
75549
|
-
...mobxReactionDisposerDiscarded,
|
|
75550
|
-
framework: "global",
|
|
75551
|
-
category: "Bugs"
|
|
75552
|
-
}
|
|
75553
|
-
},
|
|
75554
73362
|
{
|
|
75555
73363
|
key: "react-doctor/mouse-events-have-key-events",
|
|
75556
73364
|
id: "mouse-events-have-key-events",
|
|
@@ -77733,105 +75541,6 @@ const reactDoctorRules = [
|
|
|
77733
75541
|
requires: [...new Set(["react", ...reduxUseselectorReturnsNewCollection.requires ?? []])]
|
|
77734
75542
|
}
|
|
77735
75543
|
},
|
|
77736
|
-
{
|
|
77737
|
-
key: "react-doctor/remotion-calculate-metadata-fetch-signal",
|
|
77738
|
-
id: "remotion-calculate-metadata-fetch-signal",
|
|
77739
|
-
source: "react-doctor",
|
|
77740
|
-
originallyExternal: false,
|
|
77741
|
-
rule: {
|
|
77742
|
-
...remotionCalculateMetadataFetchSignal,
|
|
77743
|
-
framework: "global",
|
|
77744
|
-
category: "Bugs"
|
|
77745
|
-
}
|
|
77746
|
-
},
|
|
77747
|
-
{
|
|
77748
|
-
key: "react-doctor/remotion-deterministic-randomness",
|
|
77749
|
-
id: "remotion-deterministic-randomness",
|
|
77750
|
-
source: "react-doctor",
|
|
77751
|
-
originallyExternal: false,
|
|
77752
|
-
rule: {
|
|
77753
|
-
...remotionDeterministicRandomness,
|
|
77754
|
-
framework: "global",
|
|
77755
|
-
category: "Bugs"
|
|
77756
|
-
}
|
|
77757
|
-
},
|
|
77758
|
-
{
|
|
77759
|
-
key: "react-doctor/remotion-no-css-animation",
|
|
77760
|
-
id: "remotion-no-css-animation",
|
|
77761
|
-
source: "react-doctor",
|
|
77762
|
-
originallyExternal: false,
|
|
77763
|
-
rule: {
|
|
77764
|
-
...remotionNoCssAnimation,
|
|
77765
|
-
framework: "global",
|
|
77766
|
-
category: "Bugs"
|
|
77767
|
-
}
|
|
77768
|
-
},
|
|
77769
|
-
{
|
|
77770
|
-
key: "react-doctor/remotion-no-css-transition",
|
|
77771
|
-
id: "remotion-no-css-transition",
|
|
77772
|
-
source: "react-doctor",
|
|
77773
|
-
originallyExternal: false,
|
|
77774
|
-
rule: {
|
|
77775
|
-
...remotionNoCssTransition,
|
|
77776
|
-
framework: "global",
|
|
77777
|
-
category: "Bugs"
|
|
77778
|
-
}
|
|
77779
|
-
},
|
|
77780
|
-
{
|
|
77781
|
-
key: "react-doctor/remotion-no-css-url-assets",
|
|
77782
|
-
id: "remotion-no-css-url-assets",
|
|
77783
|
-
source: "react-doctor",
|
|
77784
|
-
originallyExternal: false,
|
|
77785
|
-
rule: {
|
|
77786
|
-
...remotionNoCssUrlAssets,
|
|
77787
|
-
framework: "global",
|
|
77788
|
-
category: "Bugs"
|
|
77789
|
-
}
|
|
77790
|
-
},
|
|
77791
|
-
{
|
|
77792
|
-
key: "react-doctor/remotion-no-module-scope-delay-render",
|
|
77793
|
-
id: "remotion-no-module-scope-delay-render",
|
|
77794
|
-
source: "react-doctor",
|
|
77795
|
-
originallyExternal: false,
|
|
77796
|
-
rule: {
|
|
77797
|
-
...remotionNoModuleScopeDelayRender,
|
|
77798
|
-
framework: "global",
|
|
77799
|
-
category: "Bugs"
|
|
77800
|
-
}
|
|
77801
|
-
},
|
|
77802
|
-
{
|
|
77803
|
-
key: "react-doctor/remotion-no-native-media-elements",
|
|
77804
|
-
id: "remotion-no-native-media-elements",
|
|
77805
|
-
source: "react-doctor",
|
|
77806
|
-
originallyExternal: false,
|
|
77807
|
-
rule: {
|
|
77808
|
-
...remotionNoNativeMediaElements,
|
|
77809
|
-
framework: "global",
|
|
77810
|
-
category: "Bugs"
|
|
77811
|
-
}
|
|
77812
|
-
},
|
|
77813
|
-
{
|
|
77814
|
-
key: "react-doctor/remotion-no-next-image",
|
|
77815
|
-
id: "remotion-no-next-image",
|
|
77816
|
-
source: "react-doctor",
|
|
77817
|
-
originallyExternal: false,
|
|
77818
|
-
rule: {
|
|
77819
|
-
...remotionNoNextImage,
|
|
77820
|
-
framework: "global",
|
|
77821
|
-
category: "Bugs"
|
|
77822
|
-
}
|
|
77823
|
-
},
|
|
77824
|
-
{
|
|
77825
|
-
key: "react-doctor/remotion-stable-delay-render-handle",
|
|
77826
|
-
id: "remotion-stable-delay-render-handle",
|
|
77827
|
-
source: "react-doctor",
|
|
77828
|
-
originallyExternal: false,
|
|
77829
|
-
rule: {
|
|
77830
|
-
...remotionStableDelayRenderHandle,
|
|
77831
|
-
framework: "global",
|
|
77832
|
-
category: "Bugs"
|
|
77833
|
-
}
|
|
77834
|
-
},
|
|
77835
75544
|
{
|
|
77836
75545
|
key: "react-doctor/rendering-animate-svg-wrapper",
|
|
77837
75546
|
id: "rendering-animate-svg-wrapper",
|
|
@@ -78955,30 +76664,6 @@ const reactDoctorRules = [
|
|
|
78955
76664
|
category: "Performance"
|
|
78956
76665
|
}
|
|
78957
76666
|
},
|
|
78958
|
-
{
|
|
78959
|
-
key: "react-doctor/valtio-no-proxy-read-in-render",
|
|
78960
|
-
id: "valtio-no-proxy-read-in-render",
|
|
78961
|
-
source: "react-doctor",
|
|
78962
|
-
originallyExternal: false,
|
|
78963
|
-
rule: {
|
|
78964
|
-
...valtioNoProxyReadInRender,
|
|
78965
|
-
framework: "global",
|
|
78966
|
-
category: "Bugs",
|
|
78967
|
-
requires: [...new Set(["react", ...valtioNoProxyReadInRender.requires ?? []])]
|
|
78968
|
-
}
|
|
78969
|
-
},
|
|
78970
|
-
{
|
|
78971
|
-
key: "react-doctor/valtio-no-snapshot-in-callback",
|
|
78972
|
-
id: "valtio-no-snapshot-in-callback",
|
|
78973
|
-
source: "react-doctor",
|
|
78974
|
-
originallyExternal: false,
|
|
78975
|
-
rule: {
|
|
78976
|
-
...valtioNoSnapshotInCallback,
|
|
78977
|
-
framework: "global",
|
|
78978
|
-
category: "Bugs",
|
|
78979
|
-
requires: [...new Set(["react", ...valtioNoSnapshotInCallback.requires ?? []])]
|
|
78980
|
-
}
|
|
78981
|
-
},
|
|
78982
76667
|
{
|
|
78983
76668
|
key: "react-doctor/void-dom-elements-no-children",
|
|
78984
76669
|
id: "void-dom-elements-no-children",
|
|
@@ -79046,42 +76731,6 @@ const reactDoctorRules = [
|
|
|
79046
76731
|
framework: "global",
|
|
79047
76732
|
category: "Maintainability"
|
|
79048
76733
|
}
|
|
79049
|
-
},
|
|
79050
|
-
{
|
|
79051
|
-
key: "react-doctor/zustand-no-fresh-selector-result",
|
|
79052
|
-
id: "zustand-no-fresh-selector-result",
|
|
79053
|
-
source: "react-doctor",
|
|
79054
|
-
originallyExternal: false,
|
|
79055
|
-
rule: {
|
|
79056
|
-
...zustandNoFreshSelectorResult,
|
|
79057
|
-
framework: "global",
|
|
79058
|
-
category: "Performance",
|
|
79059
|
-
requires: [...new Set(["react", ...zustandNoFreshSelectorResult.requires ?? []])]
|
|
79060
|
-
}
|
|
79061
|
-
},
|
|
79062
|
-
{
|
|
79063
|
-
key: "react-doctor/zustand-no-get-during-initialization",
|
|
79064
|
-
id: "zustand-no-get-during-initialization",
|
|
79065
|
-
source: "react-doctor",
|
|
79066
|
-
originallyExternal: false,
|
|
79067
|
-
rule: {
|
|
79068
|
-
...zustandNoGetDuringInitialization,
|
|
79069
|
-
framework: "global",
|
|
79070
|
-
category: "Bugs",
|
|
79071
|
-
requires: [...new Set(["react", ...zustandNoGetDuringInitialization.requires ?? []])]
|
|
79072
|
-
}
|
|
79073
|
-
},
|
|
79074
|
-
{
|
|
79075
|
-
key: "react-doctor/zustand-no-whole-store-destructure",
|
|
79076
|
-
id: "zustand-no-whole-store-destructure",
|
|
79077
|
-
source: "react-doctor",
|
|
79078
|
-
originallyExternal: false,
|
|
79079
|
-
rule: {
|
|
79080
|
-
...zustandNoWholeStoreDestructure,
|
|
79081
|
-
framework: "global",
|
|
79082
|
-
category: "Bugs",
|
|
79083
|
-
requires: [...new Set(["react", ...zustandNoWholeStoreDestructure.requires ?? []])]
|
|
79084
|
-
}
|
|
79085
76734
|
}
|
|
79086
76735
|
];
|
|
79087
76736
|
const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id, rule.rule]));
|
|
@@ -79351,13 +77000,6 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
79351
77000
|
"no-unguarded-browser-global-in-render-or-hook-init",
|
|
79352
77001
|
"prefer-dynamic-import",
|
|
79353
77002
|
"rendering-hydration-mismatch-time",
|
|
79354
|
-
"remotion-calculate-metadata-fetch-signal",
|
|
79355
|
-
"remotion-deterministic-randomness",
|
|
79356
|
-
"remotion-no-css-animation",
|
|
79357
|
-
"remotion-no-css-transition",
|
|
79358
|
-
"remotion-no-css-url-assets",
|
|
79359
|
-
"remotion-no-native-media-elements",
|
|
79360
|
-
"remotion-no-next-image",
|
|
79361
77003
|
"rerender-memo-with-default-value",
|
|
79362
77004
|
"rn-no-legacy-shadow-styles",
|
|
79363
77005
|
"rn-no-raw-text",
|
|
@@ -79575,17 +77217,7 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
|
|
|
79575
77217
|
* `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
|
|
79576
77218
|
* partition), forcing a conscious classification.
|
|
79577
77219
|
*/
|
|
79578
|
-
const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set([
|
|
79579
|
-
"nextjs-no-img-element",
|
|
79580
|
-
"only-export-components",
|
|
79581
|
-
"remotion-calculate-metadata-fetch-signal",
|
|
79582
|
-
"remotion-deterministic-randomness",
|
|
79583
|
-
"remotion-no-css-animation",
|
|
79584
|
-
"remotion-no-css-transition",
|
|
79585
|
-
"remotion-no-css-url-assets",
|
|
79586
|
-
"remotion-no-native-media-elements",
|
|
79587
|
-
"remotion-no-next-image"
|
|
79588
|
-
]);
|
|
77220
|
+
const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set(["nextjs-no-img-element", "only-export-components"]);
|
|
79589
77221
|
/**
|
|
79590
77222
|
* Runs the collectors for `ruleIds` over one file and returns every
|
|
79591
77223
|
* filesystem probe they made — the file's cross-file dependency set.
|