oxlint-plugin-react-doctor 0.6.2-dev.fa61c20 → 0.6.2-dev.fc75a3e
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.js +132 -205
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1458,12 +1458,6 @@ const getElementType = (openingElement, settings) => {
|
|
|
1458
1458
|
return elementType;
|
|
1459
1459
|
};
|
|
1460
1460
|
//#endregion
|
|
1461
|
-
//#region src/plugin/utils/has-jsx-a11y-settings.ts
|
|
1462
|
-
const hasJsxA11ySettings = (settings) => {
|
|
1463
|
-
const block = settings?.["jsx-a11y"];
|
|
1464
|
-
return typeof block === "object" && block !== null;
|
|
1465
|
-
};
|
|
1466
|
-
//#endregion
|
|
1467
1461
|
//#region src/plugin/utils/find-import-source-for-name.ts
|
|
1468
1462
|
const collectFromProgram = (programRoot) => {
|
|
1469
1463
|
const lookup = /* @__PURE__ */ new Map();
|
|
@@ -2016,12 +2010,7 @@ const altText = defineRule({
|
|
|
2016
2010
|
const objectAliases = new Set(settings.object ?? []);
|
|
2017
2011
|
const areaAliases = new Set(settings.area ?? []);
|
|
2018
2012
|
const inputImageAliases = new Set(settings["input[type=\"image\"]"] ?? []);
|
|
2019
|
-
const fileHasJsxA11ySettings = hasJsxA11ySettings(context.settings);
|
|
2020
2013
|
return { JSXOpeningElement(node) {
|
|
2021
|
-
if (!fileHasJsxA11ySettings && isNodeOfType(node.name, "JSXIdentifier")) {
|
|
2022
|
-
const rawName = node.name.name;
|
|
2023
|
-
if (rawName !== "img" && rawName !== "object" && rawName !== "area" && rawName.toLowerCase() !== "input" && !imgAliases.has(rawName) && !objectAliases.has(rawName) && !areaAliases.has(rawName) && !inputImageAliases.has(rawName)) return;
|
|
2024
|
-
}
|
|
2025
2014
|
if (isGeneratedImageRenderContext(context, node)) return;
|
|
2026
2015
|
const tag = getElementType(node, context.settings);
|
|
2027
2016
|
if (checkImg && (tag === "img" || imgAliases.has(tag))) {
|
|
@@ -2204,9 +2193,7 @@ const anchorIsValid = defineRule({
|
|
|
2204
2193
|
category: "Accessibility",
|
|
2205
2194
|
create: (context) => {
|
|
2206
2195
|
const settings = resolveSettings$50(context.settings);
|
|
2207
|
-
const fileHasJsxA11ySettings = hasJsxA11ySettings(context.settings);
|
|
2208
2196
|
return { JSXOpeningElement(node) {
|
|
2209
|
-
if (!fileHasJsxA11ySettings && (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a")) return;
|
|
2210
2197
|
if (getElementType(node, context.settings) !== "a") return;
|
|
2211
2198
|
let hrefAttribute;
|
|
2212
2199
|
for (const attributeName of settings.hrefAttributeNames) {
|
|
@@ -4890,8 +4877,9 @@ const isCreateElementCall = (node) => {
|
|
|
4890
4877
|
const callee = node.callee;
|
|
4891
4878
|
if (isNodeOfType(callee, "Identifier")) return callee.name === "createElement";
|
|
4892
4879
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
4893
|
-
if (
|
|
4894
|
-
return
|
|
4880
|
+
if (memberChainContainsDocument(callee.object)) return false;
|
|
4881
|
+
if (callee.computed) return isNodeOfType(callee.property, "Literal") && callee.property.value === "createElement";
|
|
4882
|
+
return isNodeOfType(callee.property, "Identifier") && callee.property.name === "createElement";
|
|
4895
4883
|
}
|
|
4896
4884
|
return false;
|
|
4897
4885
|
};
|
|
@@ -5958,7 +5946,6 @@ const noRedundantPaddingAxes = defineRule({
|
|
|
5958
5946
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
5959
5947
|
const classNameLiteral = getClassNameLiteral(jsxAttribute);
|
|
5960
5948
|
if (!classNameLiteral) return;
|
|
5961
|
-
if (!classNameLiteral.includes("px-") || !classNameLiteral.includes("py-")) return;
|
|
5962
5949
|
if (hasResponsivePrefix(classNameLiteral, "px") || hasResponsivePrefix(classNameLiteral, "py")) return;
|
|
5963
5950
|
const matchedPairs = collectAxisShorthandPairs(classNameLiteral, PADDING_HORIZONTAL_AXIS_PATTERN, PADDING_VERTICAL_AXIS_PATTERN);
|
|
5964
5951
|
if (matchedPairs.length === 0) return;
|
|
@@ -5983,7 +5970,6 @@ const noRedundantSizeAxes = defineRule({
|
|
|
5983
5970
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
5984
5971
|
const classNameLiteral = getClassNameLiteral(jsxAttribute);
|
|
5985
5972
|
if (!classNameLiteral) return;
|
|
5986
|
-
if (!classNameLiteral.includes("w-") || !classNameLiteral.includes("h-")) return;
|
|
5987
5973
|
if (hasResponsivePrefix(classNameLiteral, "w") || hasResponsivePrefix(classNameLiteral, "h")) return;
|
|
5988
5974
|
const matchedPairs = collectAxisShorthandPairs(classNameLiteral, SIZE_WIDTH_AXIS_PATTERN, SIZE_HEIGHT_AXIS_PATTERN);
|
|
5989
5975
|
if (matchedPairs.length === 0) return;
|
|
@@ -6008,7 +5994,6 @@ const noSpaceOnFlexChildren = defineRule({
|
|
|
6008
5994
|
if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
|
|
6009
5995
|
const classNameLiteral = getClassNameLiteral(jsxAttribute);
|
|
6010
5996
|
if (!classNameLiteral) return;
|
|
6011
|
-
if (!classNameLiteral.includes("space-")) return;
|
|
6012
5997
|
const tokens = tokenizeClassName(classNameLiteral);
|
|
6013
5998
|
let hasFlexOrGridLayout = false;
|
|
6014
5999
|
for (const token of tokens) {
|
|
@@ -9281,10 +9266,10 @@ const imgRedundantAlt = defineRule({
|
|
|
9281
9266
|
if (isGeneratedImageRenderContext(context)) return {};
|
|
9282
9267
|
const settings = resolveSettings$37(context.settings);
|
|
9283
9268
|
return { JSXOpeningElement(node) {
|
|
9269
|
+
if (isGeneratedImageRenderContext(context, node)) return;
|
|
9284
9270
|
const tag = getElementType(node, context.settings);
|
|
9285
9271
|
if (!settings.components.includes(tag)) return;
|
|
9286
9272
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
9287
|
-
if (isGeneratedImageRenderContext(context, node)) return;
|
|
9288
9273
|
const altAttribute = hasJsxPropIgnoreCase(node.attributes, "alt");
|
|
9289
9274
|
if (!altAttribute) return;
|
|
9290
9275
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
@@ -9339,7 +9324,6 @@ const SECURITY_RANDOM_CONTEXT_PATTERN = /token|secret|password|nonce|salt|csrf|c
|
|
|
9339
9324
|
const UI_NONCE_CONTEXT_PATTERN = /(?:focus|render|refresh|remount|redraw|animation|layout|cache|update)[-_]?nonce/i;
|
|
9340
9325
|
const MATH_RANDOM_CALL_PATTERN = /Math\.random\s*\(/g;
|
|
9341
9326
|
const SECURITY_CONTEXT_WINDOW_CHARS = 250;
|
|
9342
|
-
const CRYPTO_SURFACE_TRIGGER_PATTERN = /createHash|md5|cipher|encrypt|decrypt|crypto|signature|Math\.random/i;
|
|
9343
9327
|
const findMatchIndexNearContext = (content, pattern, contextPattern, excludeContextPattern) => {
|
|
9344
9328
|
for (const callMatch of content.matchAll(pattern)) {
|
|
9345
9329
|
const surroundingText = content.slice(Math.max(0, callMatch.index - SECURITY_CONTEXT_WINDOW_CHARS), callMatch.index + SECURITY_CONTEXT_WINDOW_CHARS);
|
|
@@ -9369,7 +9353,6 @@ const insecureCryptoRisk = defineRule({
|
|
|
9369
9353
|
if (!isProductionSourcePath(file.relativePath)) return [];
|
|
9370
9354
|
if (DEMO_CONTEXT_PATTERN.test(file.relativePath)) return [];
|
|
9371
9355
|
if (PROTOCOL_MANDATED_HASH_CONTEXT_PATTERN.test(file.relativePath)) return [];
|
|
9372
|
-
if (!CRYPTO_SURFACE_TRIGGER_PATTERN.test(file.content)) return [];
|
|
9373
9356
|
const content = getScannableContent(file);
|
|
9374
9357
|
let matchIndex = findMatchIndexNearContext(content, WEAK_HASH_PATTERN, SECURITY_CONTEXT_PATTERN, PROTOCOL_MANDATED_HASH_CONTEXT_PATTERN);
|
|
9375
9358
|
if (matchIndex < 0) matchIndex = content.search(WEAK_CIPHER_ALGORITHM_PATTERN);
|
|
@@ -9602,16 +9585,15 @@ const interactiveSupportsFocus = defineRule({
|
|
|
9602
9585
|
const settings = resolveSettings$36(context.settings);
|
|
9603
9586
|
const tabbableSet = new Set(settings.tabbable);
|
|
9604
9587
|
return { JSXOpeningElement(node) {
|
|
9605
|
-
if (node.attributes.length === 0) return;
|
|
9606
9588
|
if (hasJsxSpreadAttribute$1(node.attributes)) return;
|
|
9607
9589
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
9608
9590
|
const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
|
|
9609
|
-
if (!role) return;
|
|
9610
|
-
if (!ALL_EVENT_HANDLERS.some((handler) => Boolean(hasJsxPropIgnoreCase(node.attributes, handler)))) return;
|
|
9611
9591
|
const elementType = getElementType(node, context.settings);
|
|
9612
9592
|
if (!HTML_TAGS.has(elementType)) return;
|
|
9613
|
-
|
|
9593
|
+
const hasInteractiveHandler = ALL_EVENT_HANDLERS.some((handler) => Boolean(hasJsxPropIgnoreCase(node.attributes, handler)));
|
|
9614
9594
|
const hasTabIndex = Boolean(hasJsxPropIgnoreCase(node.attributes, "tabIndex"));
|
|
9595
|
+
if (!hasInteractiveHandler || isDisabledElement(node) || isHiddenFromScreenReader(node, context.settings) || isPresentationRole(node)) return;
|
|
9596
|
+
if (!role) return;
|
|
9615
9597
|
if (!isInteractiveRole(role) || isInteractiveElement(elementType, node) || isNonInteractiveRole(role) || isNonInteractiveElement(elementType, node) || hasTabIndex) return;
|
|
9616
9598
|
const message = tabbableSet.has(role) ? buildTabbableMessage(role) : buildFocusableMessage(role);
|
|
9617
9599
|
context.report({
|
|
@@ -10711,6 +10693,7 @@ const jsHoistIntl = defineRule({
|
|
|
10711
10693
|
recommendation: "Move `new Intl.NumberFormat(...)` to the top of the file or wrap it in `useMemo`. Building one is slow, so don't redo it on every call",
|
|
10712
10694
|
create: (context) => ({ NewExpression(node) {
|
|
10713
10695
|
if (!isIntlNewExpression(node)) return;
|
|
10696
|
+
if (isInsideCacheMemo(node)) return;
|
|
10714
10697
|
let cursor = node.parent ?? null;
|
|
10715
10698
|
let inFunctionBody = false;
|
|
10716
10699
|
while (cursor) {
|
|
@@ -10727,7 +10710,6 @@ const jsHoistIntl = defineRule({
|
|
|
10727
10710
|
cursor = cursor.parent ?? null;
|
|
10728
10711
|
}
|
|
10729
10712
|
if (!inFunctionBody) return;
|
|
10730
|
-
if (isInsideCacheMemo(node)) return;
|
|
10731
10713
|
const className = isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") ? node.callee.property.name : "Intl";
|
|
10732
10714
|
context.report({
|
|
10733
10715
|
node,
|
|
@@ -15669,12 +15651,8 @@ const containsFetchCall = (node, options) => {
|
|
|
15669
15651
|
const effectInvokedFunctions = options?.stopAtFunctionBoundary ? collectEffectInvokedFunctions(node) : null;
|
|
15670
15652
|
let didFindFetchCall = false;
|
|
15671
15653
|
walkAst(node, (child) => {
|
|
15672
|
-
if (didFindFetchCall) return false;
|
|
15673
15654
|
if (effectInvokedFunctions && child !== node && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
|
|
15674
|
-
if (isFetchCall$1(child))
|
|
15675
|
-
didFindFetchCall = true;
|
|
15676
|
-
return false;
|
|
15677
|
-
}
|
|
15655
|
+
if (isFetchCall$1(child)) didFindFetchCall = true;
|
|
15678
15656
|
});
|
|
15679
15657
|
return didFindFetchCall;
|
|
15680
15658
|
};
|
|
@@ -16364,18 +16342,16 @@ const nextjsNoSideEffectInGetHandler = defineRule({
|
|
|
16364
16342
|
recommendation: "GET requests can be prefetched and are open to CSRF. Move the side effect to a POST handler.",
|
|
16365
16343
|
create: (context) => {
|
|
16366
16344
|
let resolveBinding = () => null;
|
|
16367
|
-
let isRouteHandlerFile = false;
|
|
16368
|
-
let mutatingSegment = null;
|
|
16369
16345
|
return {
|
|
16370
16346
|
Program(node) {
|
|
16371
16347
|
resolveBinding = buildProgramBindingLookup(node);
|
|
16372
|
-
const filename = normalizeFilename(context.filename ?? "");
|
|
16373
|
-
isRouteHandlerFile = ROUTE_HANDLER_FILE_PATTERN.test(filename) && !CRON_ROUTE_PATTERN.test(filename);
|
|
16374
|
-
mutatingSegment = isRouteHandlerFile ? extractMutatingRouteSegment(filename) : null;
|
|
16375
16348
|
},
|
|
16376
16349
|
ExportNamedDeclaration(node) {
|
|
16377
|
-
|
|
16350
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
16351
|
+
if (!ROUTE_HANDLER_FILE_PATTERN.test(filename)) return;
|
|
16352
|
+
if (CRON_ROUTE_PATTERN.test(filename)) return;
|
|
16378
16353
|
if (!isExportedGetHandler(node)) return;
|
|
16354
|
+
const mutatingSegment = extractMutatingRouteSegment(filename);
|
|
16379
16355
|
const handlerBodies = resolveGetHandlerBodies(node, resolveBinding);
|
|
16380
16356
|
for (const handlerBody of handlerBodies) {
|
|
16381
16357
|
const bodiesToScan = [handlerBody, ...collectCalledSameFileHelperBodies(handlerBody, resolveBinding)];
|
|
@@ -19459,8 +19435,8 @@ const CONTEXT_MODULES = [
|
|
|
19459
19435
|
];
|
|
19460
19436
|
const isCreateContextCallee = (callee) => {
|
|
19461
19437
|
if (isNodeOfType(callee, "Identifier")) {
|
|
19462
|
-
const
|
|
19463
|
-
return
|
|
19438
|
+
for (const moduleName of CONTEXT_MODULES) if (getImportedNameFromModule(callee, callee.name, moduleName) === "createContext") return true;
|
|
19439
|
+
return false;
|
|
19464
19440
|
}
|
|
19465
19441
|
if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
|
|
19466
19442
|
const namespaceIdentifier = callee.object;
|
|
@@ -19470,8 +19446,8 @@ const isCreateContextCallee = (callee) => {
|
|
|
19470
19446
|
if (propertyIdentifier.name !== "createContext") return false;
|
|
19471
19447
|
const namespaceName = namespaceIdentifier.name;
|
|
19472
19448
|
if (isCanonicalReactNamespaceName(namespaceName)) return true;
|
|
19473
|
-
const
|
|
19474
|
-
return
|
|
19449
|
+
for (const moduleName of CONTEXT_MODULES) if (isImportedFromModule(namespaceIdentifier, namespaceName, moduleName)) return true;
|
|
19450
|
+
return false;
|
|
19475
19451
|
}
|
|
19476
19452
|
return false;
|
|
19477
19453
|
};
|
|
@@ -26167,14 +26143,14 @@ const noRenderInRender = defineRule({
|
|
|
26167
26143
|
const expression = node.expression;
|
|
26168
26144
|
if (!isNodeOfType(expression, "CallExpression")) return;
|
|
26169
26145
|
let calleeName = null;
|
|
26170
|
-
if (isNodeOfType(expression.callee, "Identifier")) calleeName = expression.callee.name;
|
|
26171
|
-
else if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier")) calleeName = expression.callee.property.name;
|
|
26172
|
-
if (!calleeName || !RENDER_FUNCTION_PATTERN.test(calleeName)) return;
|
|
26173
26146
|
if (isNodeOfType(expression.callee, "Identifier")) {
|
|
26174
26147
|
if (tracesToPropOrParameter(context.scopes.symbolFor(expression.callee), context.scopes)) return;
|
|
26175
|
-
|
|
26148
|
+
calleeName = expression.callee.name;
|
|
26149
|
+
} else if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier")) {
|
|
26176
26150
|
if (rootsInProps(expression.callee.object, context.scopes)) return;
|
|
26151
|
+
calleeName = expression.callee.property.name;
|
|
26177
26152
|
}
|
|
26153
|
+
if (!calleeName || !RENDER_FUNCTION_PATTERN.test(calleeName)) return;
|
|
26178
26154
|
context.report({
|
|
26179
26155
|
node: expression,
|
|
26180
26156
|
message: `Your users lose state because "${calleeName}()" builds UI from an inline call that React remounts, so pull it into its own component instead.`
|
|
@@ -28889,26 +28865,32 @@ const isFirstArgumentOfHocCall = (node) => {
|
|
|
28889
28865
|
if (!isHocCallee$1(parent)) return false;
|
|
28890
28866
|
return parent.arguments[0] === node;
|
|
28891
28867
|
};
|
|
28892
|
-
const MAP_LIKE_METHOD_NAMES = new Set([
|
|
28893
|
-
"map",
|
|
28894
|
-
"forEach",
|
|
28895
|
-
"filter",
|
|
28896
|
-
"flatMap",
|
|
28897
|
-
"reduce",
|
|
28898
|
-
"reduceRight"
|
|
28899
|
-
]);
|
|
28900
28868
|
const isReturnOfMapCallback = (node) => {
|
|
28901
28869
|
const parent = node.parent;
|
|
28902
28870
|
if (!parent) return false;
|
|
28903
28871
|
if (isNodeOfType(parent, "CallExpression")) {
|
|
28904
28872
|
const callee = parent.callee;
|
|
28905
|
-
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return
|
|
28873
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return [
|
|
28874
|
+
"map",
|
|
28875
|
+
"forEach",
|
|
28876
|
+
"filter",
|
|
28877
|
+
"flatMap",
|
|
28878
|
+
"reduce",
|
|
28879
|
+
"reduceRight"
|
|
28880
|
+
].includes(callee.property.name);
|
|
28906
28881
|
}
|
|
28907
28882
|
if (isNodeOfType(parent, "ArrowFunctionExpression") || isNodeOfType(parent, "FunctionExpression")) {
|
|
28908
28883
|
const callbackParent = parent.parent;
|
|
28909
28884
|
if (callbackParent && isNodeOfType(callbackParent, "CallExpression")) {
|
|
28910
28885
|
const callee = callbackParent.callee;
|
|
28911
|
-
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return
|
|
28886
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return [
|
|
28887
|
+
"map",
|
|
28888
|
+
"forEach",
|
|
28889
|
+
"filter",
|
|
28890
|
+
"flatMap",
|
|
28891
|
+
"reduce",
|
|
28892
|
+
"reduceRight"
|
|
28893
|
+
].includes(callee.property.name);
|
|
28912
28894
|
}
|
|
28913
28895
|
}
|
|
28914
28896
|
return false;
|
|
@@ -28944,10 +28926,10 @@ const isRenderFlowingReadReference = (identifier) => {
|
|
|
28944
28926
|
valueNode = parent;
|
|
28945
28927
|
parent = parent.parent;
|
|
28946
28928
|
continue;
|
|
28947
|
-
case "VariableDeclarator": return parent.init === valueNode && isNodeOfType(parent.id, "Identifier") &&
|
|
28929
|
+
case "VariableDeclarator": return parent.init === valueNode && isNodeOfType(parent.id, "Identifier") && isUppercaseName(parent.id.name);
|
|
28948
28930
|
case "AssignmentExpression": {
|
|
28949
28931
|
const assignmentTarget = parent.left;
|
|
28950
|
-
return parent.right === valueNode && isNodeOfType(assignmentTarget, "Identifier") &&
|
|
28932
|
+
return parent.right === valueNode && isNodeOfType(assignmentTarget, "Identifier") && isUppercaseName(assignmentTarget.name);
|
|
28951
28933
|
}
|
|
28952
28934
|
case "Property":
|
|
28953
28935
|
if (parent.value !== valueNode) return false;
|
|
@@ -29040,14 +29022,14 @@ const noUnstableNestedComponents = defineRule({
|
|
|
29040
29022
|
};
|
|
29041
29023
|
return {
|
|
29042
29024
|
JSXOpeningElement(node) {
|
|
29043
|
-
if (isNodeOfType(node.name, "JSXIdentifier") &&
|
|
29025
|
+
if (isNodeOfType(node.name, "JSXIdentifier") && isUppercaseName(node.name.name)) {
|
|
29044
29026
|
recordInstantiation(node.name, node.name.name);
|
|
29045
29027
|
return;
|
|
29046
29028
|
}
|
|
29047
29029
|
if (isNodeOfType(node.name, "JSXMemberExpression")) recordMemberChainInstantiation(node.name);
|
|
29048
29030
|
},
|
|
29049
29031
|
Identifier(node) {
|
|
29050
|
-
if (!
|
|
29032
|
+
if (!isUppercaseName(node.name)) return;
|
|
29051
29033
|
if (!isRenderFlowingReadReference(node)) return;
|
|
29052
29034
|
recordInstantiation(node, node.name);
|
|
29053
29035
|
},
|
|
@@ -30857,13 +30839,13 @@ const preferStableEmptyFallback = defineRule({
|
|
|
30857
30839
|
memoRegistry = buildSameFileMemoRegistry(node);
|
|
30858
30840
|
},
|
|
30859
30841
|
JSXAttribute(node) {
|
|
30842
|
+
if (!isInsideFunctionScope(node)) return;
|
|
30843
|
+
if (isJsxAttributeOnIntrinsicHtmlElement(node)) return;
|
|
30860
30844
|
if (!node.value) return;
|
|
30861
30845
|
if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
|
|
30862
30846
|
const innerExpression = node.value.expression;
|
|
30863
30847
|
if (!innerExpression) return;
|
|
30864
30848
|
if (innerExpression.type === "JSXEmptyExpression") return;
|
|
30865
|
-
if (!isInsideFunctionScope(node)) return;
|
|
30866
|
-
if (isJsxAttributeOnIntrinsicHtmlElement(node)) return;
|
|
30867
30849
|
const parentJsxOpening = node.parent;
|
|
30868
30850
|
const openingName = parentJsxOpening && isNodeOfType(parentJsxOpening, "JSXOpeningElement") ? parentJsxOpening.name : null;
|
|
30869
30851
|
if (memoStatusForJsxOpeningName(memoRegistry, openingName) !== "memoised") return;
|
|
@@ -33456,7 +33438,6 @@ const RECYCLABLE_LIST_PACKAGES = {
|
|
|
33456
33438
|
FlashList: ["@shopify/flash-list"],
|
|
33457
33439
|
LegendList: ["@legendapp/list"]
|
|
33458
33440
|
};
|
|
33459
|
-
const RECYCLABLE_LIST_PACKAGE_SOURCES = Object.values(RECYCLABLE_LIST_PACKAGES).flat();
|
|
33460
33441
|
const REACT_NATIVE_LIST_COMPONENTS = new Set([...REACT_NATIVE_BUILTIN_LIST_COMPONENTS, ...Object.keys(RECYCLABLE_LIST_PACKAGES)]);
|
|
33461
33442
|
const RENDER_ITEM_PROP_NAMES = new Set([
|
|
33462
33443
|
"renderItem",
|
|
@@ -33699,42 +33680,33 @@ const rnListMissingEstimatedItemSize = defineRule({
|
|
|
33699
33680
|
requires: ["react-native"],
|
|
33700
33681
|
severity: "warn",
|
|
33701
33682
|
recommendation: "Without `estimatedItemSize` the list guesses row height and can flash blank cells on fast scroll. Add `estimatedItemSize={<avg-row-height-in-px>}` so it matches your rows.",
|
|
33702
|
-
create: (context) => {
|
|
33703
|
-
|
|
33704
|
-
return
|
|
33705
|
-
|
|
33706
|
-
|
|
33707
|
-
|
|
33708
|
-
|
|
33709
|
-
|
|
33710
|
-
|
|
33711
|
-
|
|
33712
|
-
|
|
33713
|
-
|
|
33714
|
-
|
|
33715
|
-
|
|
33716
|
-
|
|
33717
|
-
|
|
33718
|
-
|
|
33719
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
33720
|
-
if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
|
|
33721
|
-
const attributeName = attribute.name.name;
|
|
33722
|
-
if (SIZING_HINT_ATTRIBUTE_NAMES.has(attributeName)) hasSizingHint = true;
|
|
33723
|
-
if (attributeName === "data") {
|
|
33724
|
-
hasDataProp = true;
|
|
33725
|
-
if (isEmptyArrayLiteral(attribute)) dataIsEmptyLiteral = true;
|
|
33726
|
-
}
|
|
33727
|
-
}
|
|
33728
|
-
if (hasSizingHint) return;
|
|
33729
|
-
if (dataIsEmptyLiteral) return;
|
|
33730
|
-
if (!hasDataProp) return;
|
|
33731
|
-
context.report({
|
|
33732
|
-
node,
|
|
33733
|
-
message: `Your users see blank cells flash on fast scroll when <${localElementName}> has no \`estimatedItemSize\`.`
|
|
33734
|
-
});
|
|
33683
|
+
create: (context) => ({ JSXOpeningElement(node) {
|
|
33684
|
+
const localElementName = resolveJsxElementName(node);
|
|
33685
|
+
if (!localElementName) return;
|
|
33686
|
+
const canonicalRecyclerName = resolveImportedRecyclerName(node, localElementName);
|
|
33687
|
+
if (canonicalRecyclerName === null) return;
|
|
33688
|
+
if (canonicalRecyclerName === "FlashList" && isFlashListV2OrNewer(context)) return;
|
|
33689
|
+
let hasSizingHint = false;
|
|
33690
|
+
let dataIsEmptyLiteral = false;
|
|
33691
|
+
let hasDataProp = false;
|
|
33692
|
+
for (const attribute of node.attributes ?? []) {
|
|
33693
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
33694
|
+
if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
|
|
33695
|
+
const attributeName = attribute.name.name;
|
|
33696
|
+
if (SIZING_HINT_ATTRIBUTE_NAMES.has(attributeName)) hasSizingHint = true;
|
|
33697
|
+
if (attributeName === "data") {
|
|
33698
|
+
hasDataProp = true;
|
|
33699
|
+
if (isEmptyArrayLiteral(attribute)) dataIsEmptyLiteral = true;
|
|
33735
33700
|
}
|
|
33736
|
-
}
|
|
33737
|
-
|
|
33701
|
+
}
|
|
33702
|
+
if (hasSizingHint) return;
|
|
33703
|
+
if (dataIsEmptyLiteral) return;
|
|
33704
|
+
if (!hasDataProp) return;
|
|
33705
|
+
context.report({
|
|
33706
|
+
node,
|
|
33707
|
+
message: `Your users see blank cells flash on fast scroll when <${localElementName}> has no \`estimatedItemSize\`.`
|
|
33708
|
+
});
|
|
33709
|
+
} })
|
|
33738
33710
|
});
|
|
33739
33711
|
//#endregion
|
|
33740
33712
|
//#region src/plugin/rules/react-native/rn-list-recyclable-without-types.ts
|
|
@@ -33745,34 +33717,25 @@ const rnListRecyclableWithoutTypes = defineRule({
|
|
|
33745
33717
|
requires: ["react-native"],
|
|
33746
33718
|
severity: "warn",
|
|
33747
33719
|
recommendation: "When rows have different shapes, reused cells can show the wrong layout. Add `getItemType={item => item.kind}` so FlashList keeps a separate pool per row type.",
|
|
33748
|
-
create: (context) => {
|
|
33749
|
-
|
|
33750
|
-
return
|
|
33751
|
-
|
|
33752
|
-
|
|
33753
|
-
|
|
33754
|
-
|
|
33755
|
-
|
|
33756
|
-
|
|
33757
|
-
|
|
33758
|
-
|
|
33759
|
-
|
|
33760
|
-
|
|
33761
|
-
|
|
33762
|
-
|
|
33763
|
-
|
|
33764
|
-
|
|
33765
|
-
|
|
33766
|
-
|
|
33767
|
-
if (attr.name.name === "getItemType") hasGetItemType = true;
|
|
33768
|
-
}
|
|
33769
|
-
if (hasRecycleItemsEnabled && !hasGetItemType) context.report({
|
|
33770
|
-
node,
|
|
33771
|
-
message: `Your users see rows of different shapes reuse the wrong cells when <${elementName} recycleItems> has no \`getItemType\`.`
|
|
33772
|
-
});
|
|
33773
|
-
}
|
|
33774
|
-
};
|
|
33775
|
-
}
|
|
33720
|
+
create: (context) => ({ JSXOpeningElement(node) {
|
|
33721
|
+
const elementName = resolveJsxElementName(node);
|
|
33722
|
+
if (!elementName) return;
|
|
33723
|
+
if (resolveImportedRecyclerName(node, elementName, { allowNamespaceMemberAccess: true }) === null) return;
|
|
33724
|
+
let hasRecycleItemsEnabled = false;
|
|
33725
|
+
let hasGetItemType = false;
|
|
33726
|
+
for (const attr of node.attributes ?? []) {
|
|
33727
|
+
if (!isNodeOfType(attr, "JSXAttribute")) continue;
|
|
33728
|
+
if (!isNodeOfType(attr.name, "JSXIdentifier")) continue;
|
|
33729
|
+
if (attr.name.name === "recycleItems") if (!attr.value) hasRecycleItemsEnabled = true;
|
|
33730
|
+
else if (isNodeOfType(attr.value, "JSXExpressionContainer") && isNodeOfType(attr.value.expression, "Literal")) hasRecycleItemsEnabled = attr.value.expression.value === true;
|
|
33731
|
+
else hasRecycleItemsEnabled = true;
|
|
33732
|
+
if (attr.name.name === "getItemType") hasGetItemType = true;
|
|
33733
|
+
}
|
|
33734
|
+
if (hasRecycleItemsEnabled && !hasGetItemType) context.report({
|
|
33735
|
+
node,
|
|
33736
|
+
message: `Your users see rows of different shapes reuse the wrong cells when <${elementName} recycleItems> has no \`getItemType\`.`
|
|
33737
|
+
});
|
|
33738
|
+
} })
|
|
33776
33739
|
});
|
|
33777
33740
|
//#endregion
|
|
33778
33741
|
//#region src/plugin/rules/react-native/rn-no-deep-imports.ts
|
|
@@ -34802,7 +34765,6 @@ const rnNoRawText = defineRule({
|
|
|
34802
34765
|
return {
|
|
34803
34766
|
Program(programNode) {
|
|
34804
34767
|
isDomComponentFile = hasDirective(programNode, "use dom");
|
|
34805
|
-
if (!containsJsxElement(programNode)) return;
|
|
34806
34768
|
const childrenForwarding = collectTextWrapperComponents(programNode, isTextHandlingComponent, isNonTextHostName);
|
|
34807
34769
|
autoDetectedTextWrappers = childrenForwarding.textWrappers;
|
|
34808
34770
|
autoDetectedNonTextWrappers = childrenForwarding.nonTextWrappers;
|
|
@@ -38757,7 +38719,14 @@ const roleSupportsAriaProps = defineRule({
|
|
|
38757
38719
|
recommendation: "Only use `aria-*` attributes that the element's role supports.",
|
|
38758
38720
|
category: "Accessibility",
|
|
38759
38721
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
38760
|
-
|
|
38722
|
+
const elementType = getElementType(node, context.settings);
|
|
38723
|
+
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
38724
|
+
const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : getImplicitRole(node, elementType);
|
|
38725
|
+
if (!role) return;
|
|
38726
|
+
if (!VALID_ARIA_ROLES.has(role)) return;
|
|
38727
|
+
const isImplicit = !roleAttribute;
|
|
38728
|
+
const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
|
|
38729
|
+
if (!supported) return;
|
|
38761
38730
|
for (const attribute of node.attributes) {
|
|
38762
38731
|
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
38763
38732
|
const attributeNode = attribute;
|
|
@@ -38767,21 +38736,6 @@ const roleSupportsAriaProps = defineRule({
|
|
|
38767
38736
|
const propName = propRawName.toLowerCase();
|
|
38768
38737
|
if (!propName.startsWith("aria-")) continue;
|
|
38769
38738
|
if (!ARIA_PROPERTIES.has(propName)) continue;
|
|
38770
|
-
(ariaAttributes ??= []).push({
|
|
38771
|
-
attribute,
|
|
38772
|
-
propName
|
|
38773
|
-
});
|
|
38774
|
-
}
|
|
38775
|
-
if (!ariaAttributes) return;
|
|
38776
|
-
const elementType = getElementType(node, context.settings);
|
|
38777
|
-
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
38778
|
-
const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : getImplicitRole(node, elementType);
|
|
38779
|
-
if (!role) return;
|
|
38780
|
-
if (!VALID_ARIA_ROLES.has(role)) return;
|
|
38781
|
-
const isImplicit = !roleAttribute;
|
|
38782
|
-
const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
|
|
38783
|
-
if (!supported) return;
|
|
38784
|
-
for (const { attribute, propName } of ariaAttributes) {
|
|
38785
38739
|
if (supported.has(propName)) continue;
|
|
38786
38740
|
context.report({
|
|
38787
38741
|
node: attribute,
|
|
@@ -39619,7 +39573,6 @@ const serverCacheWithObjectLiteral = defineRule({
|
|
|
39619
39573
|
cachedFunctionNames.add(node.id.name);
|
|
39620
39574
|
},
|
|
39621
39575
|
CallExpression(node) {
|
|
39622
|
-
if (cachedFunctionNames.size === 0) return;
|
|
39623
39576
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
39624
39577
|
if (!cachedFunctionNames.has(node.callee.name)) return;
|
|
39625
39578
|
const firstArg = node.arguments?.[0];
|
|
@@ -41578,7 +41531,6 @@ const webhookSignatureRisk = defineRule({
|
|
|
41578
41531
|
//#endregion
|
|
41579
41532
|
//#region src/plugin/rules/zod/utils/zod-ast.ts
|
|
41580
41533
|
const ZOD_MODULE = "zod";
|
|
41581
|
-
const ZOD_MODULE_SOURCES = [ZOD_MODULE];
|
|
41582
41534
|
const getStaticPropertyName = (member) => {
|
|
41583
41535
|
const property = member.property;
|
|
41584
41536
|
if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
@@ -41722,33 +41674,25 @@ const zodV4NoDeprecatedErrorApis = defineRule({
|
|
|
41722
41674
|
tags: ["migration-hint"],
|
|
41723
41675
|
severity: "warn",
|
|
41724
41676
|
recommendation: "Use the Zod 4 helpers instead: `z.treeifyError()`, `z.flattenError()`, `z.prettifyError()`, or read `error.issues` directly.",
|
|
41725
|
-
create: (context) => {
|
|
41726
|
-
|
|
41727
|
-
|
|
41728
|
-
|
|
41729
|
-
|
|
41730
|
-
|
|
41731
|
-
|
|
41732
|
-
|
|
41733
|
-
|
|
41734
|
-
|
|
41735
|
-
|
|
41736
|
-
|
|
41737
|
-
|
|
41738
|
-
|
|
41739
|
-
|
|
41740
|
-
|
|
41741
|
-
|
|
41742
|
-
|
|
41743
|
-
|
|
41744
|
-
if (!isDeprecatedZodErrorMemberAccess(node)) return;
|
|
41745
|
-
context.report({
|
|
41746
|
-
node,
|
|
41747
|
-
message: ZOD_ERROR_API_MESSAGE
|
|
41748
|
-
});
|
|
41749
|
-
}
|
|
41750
|
-
};
|
|
41751
|
-
}
|
|
41677
|
+
create: (context) => ({
|
|
41678
|
+
CallExpression(node) {
|
|
41679
|
+
if (isZodErrorCreateCall(node) && isReceiverOfDeprecatedZodErrorMember(node)) return;
|
|
41680
|
+
if (!isZodErrorCreateCall(node) && !isDeprecatedZodErrorMemberAccess(node.callee)) return;
|
|
41681
|
+
context.report({
|
|
41682
|
+
node,
|
|
41683
|
+
message: ZOD_ERROR_API_MESSAGE
|
|
41684
|
+
});
|
|
41685
|
+
},
|
|
41686
|
+
MemberExpression(node) {
|
|
41687
|
+
const parent = node.parent;
|
|
41688
|
+
if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
|
|
41689
|
+
if (!isDeprecatedZodErrorMemberAccess(node)) return;
|
|
41690
|
+
context.report({
|
|
41691
|
+
node,
|
|
41692
|
+
message: ZOD_ERROR_API_MESSAGE
|
|
41693
|
+
});
|
|
41694
|
+
}
|
|
41695
|
+
})
|
|
41752
41696
|
});
|
|
41753
41697
|
//#endregion
|
|
41754
41698
|
//#region src/plugin/rules/zod/zod-v4-no-deprecated-error-customization.ts
|
|
@@ -41940,24 +41884,16 @@ const zodV4NoDeprecatedSchemaApis = defineRule({
|
|
|
41940
41884
|
tags: ["migration-hint"],
|
|
41941
41885
|
severity: "warn",
|
|
41942
41886
|
recommendation: "Switch to the Zod 4 versions: top-level factories like `z.enum()`, object helpers like `z.strictObject()`, the new `z.function({ input, output })` form, and explicit key/value schemas for `z.record()`.",
|
|
41943
|
-
create: (context) => {
|
|
41944
|
-
|
|
41945
|
-
|
|
41946
|
-
|
|
41947
|
-
|
|
41948
|
-
|
|
41949
|
-
CallExpression(node)
|
|
41950
|
-
|
|
41951
|
-
|
|
41952
|
-
|
|
41953
|
-
MemberExpression(node) {
|
|
41954
|
-
if (!fileImportsZod) return;
|
|
41955
|
-
const parent = node.parent;
|
|
41956
|
-
if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
|
|
41957
|
-
if (isDroppedEnumAliasAccess(node) || isZodNamespaceImportMemberCreate(node)) reportSchemaMigration(context, node);
|
|
41958
|
-
}
|
|
41959
|
-
};
|
|
41960
|
-
}
|
|
41887
|
+
create: (context) => ({
|
|
41888
|
+
CallExpression(node) {
|
|
41889
|
+
if (isCallToDeprecatedTopLevelFactory(node) || isCallToDroppedCreateFactory(node) || isSingleArgumentRecordCall(node) || isLiteralSymbolCall(node) || isDeprecatedFunctionChainCall(node) || isDirectMethodCallOnZodFactory(node, OBJECT_FACTORY, OBJECT_METHODS) || isDirectMethodCallOnZodFactory(node, NUMBER_FACTORY, NUMBER_METHODS) || isRefineSecondArgumentFunction(node)) reportSchemaMigration(context, node);
|
|
41890
|
+
},
|
|
41891
|
+
MemberExpression(node) {
|
|
41892
|
+
const parent = node.parent;
|
|
41893
|
+
if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
|
|
41894
|
+
if (isDroppedEnumAliasAccess(node) || isZodNamespaceImportMemberCreate(node)) reportSchemaMigration(context, node);
|
|
41895
|
+
}
|
|
41896
|
+
})
|
|
41961
41897
|
});
|
|
41962
41898
|
//#endregion
|
|
41963
41899
|
//#region src/plugin/rules/zod/zod-v4-prefer-top-level-string-formats.ts
|
|
@@ -41992,22 +41928,13 @@ const zodV4PreferTopLevelStringFormats = defineRule({
|
|
|
41992
41928
|
tags: ["migration-hint"],
|
|
41993
41929
|
severity: "warn",
|
|
41994
41930
|
recommendation: "Use the Zod 4 top-level format checks like `z.email()`, `z.uuid()`, or `z.ipv4()` instead of `z.string().<format>()`.",
|
|
41995
|
-
create: (context) => {
|
|
41996
|
-
|
|
41997
|
-
|
|
41998
|
-
|
|
41999
|
-
|
|
42000
|
-
|
|
42001
|
-
|
|
42002
|
-
if (!fileImportsZod) return;
|
|
42003
|
-
if (!isDirectMethodCallOnZodFactory(node, ZOD_STRING_FACTORY, STRING_FORMAT_METHODS)) return;
|
|
42004
|
-
context.report({
|
|
42005
|
-
node,
|
|
42006
|
-
message: "This `z.string().<format>()` check is deprecated in Zod 4, so it can break during the upgrade."
|
|
42007
|
-
});
|
|
42008
|
-
}
|
|
42009
|
-
};
|
|
42010
|
-
}
|
|
41931
|
+
create: (context) => ({ CallExpression(node) {
|
|
41932
|
+
if (!isDirectMethodCallOnZodFactory(node, ZOD_STRING_FACTORY, STRING_FORMAT_METHODS)) return;
|
|
41933
|
+
context.report({
|
|
41934
|
+
node,
|
|
41935
|
+
message: "This `z.string().<format>()` check is deprecated in Zod 4, so it can break during the upgrade."
|
|
41936
|
+
});
|
|
41937
|
+
} })
|
|
42011
41938
|
});
|
|
42012
41939
|
//#endregion
|
|
42013
41940
|
//#region src/plugin/rule-registry.ts
|