oxlint-plugin-react-doctor 0.7.6-dev.8c9a6c4 → 0.7.6-dev.a8cd24e
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 +234 -41
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6021,7 +6021,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
6021
6021
|
//#endregion
|
|
6022
6022
|
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
6023
6023
|
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
6024
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
6024
|
+
if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
|
|
6025
6025
|
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
6026
6026
|
let symbol = scopes.symbolFor(identifier);
|
|
6027
6027
|
while (symbol?.kind === "const") {
|
|
@@ -6296,6 +6296,131 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
|
|
|
6296
6296
|
return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
|
|
6297
6297
|
};
|
|
6298
6298
|
//#endregion
|
|
6299
|
+
//#region src/plugin/utils/collect-safely-validated-local-storage-keys.ts
|
|
6300
|
+
const isLocalStorageCall = (node, methodName) => {
|
|
6301
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
6302
|
+
const callee = stripParenExpression(node.callee);
|
|
6303
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
6304
|
+
const receiver = stripParenExpression(callee.object);
|
|
6305
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "localStorage" && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
|
|
6306
|
+
};
|
|
6307
|
+
const catchReturnsFallback = (tryStatement) => {
|
|
6308
|
+
const handler = tryStatement.handler;
|
|
6309
|
+
if (!handler) return false;
|
|
6310
|
+
let hasReturn = false;
|
|
6311
|
+
let hasThrow = false;
|
|
6312
|
+
walkAst(handler.body, (child) => {
|
|
6313
|
+
if (isFunctionLike$1(child)) return false;
|
|
6314
|
+
if (isNodeOfType(child, "ReturnStatement")) hasReturn = true;
|
|
6315
|
+
if (isNodeOfType(child, "ThrowStatement")) hasThrow = true;
|
|
6316
|
+
});
|
|
6317
|
+
return hasReturn && !hasThrow;
|
|
6318
|
+
};
|
|
6319
|
+
const resolveValidatorFunction = (callee, scopes) => {
|
|
6320
|
+
const unwrappedCallee = stripParenExpression(callee);
|
|
6321
|
+
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
6322
|
+
const symbol = scopes.symbolFor(unwrappedCallee);
|
|
6323
|
+
if (!symbol) return null;
|
|
6324
|
+
const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
|
|
6325
|
+
return isFunctionLike$1(candidate) ? candidate : null;
|
|
6326
|
+
};
|
|
6327
|
+
const validatorChecksPayloadProperties = (validatorFunction, scopes) => {
|
|
6328
|
+
if (!isFunctionLike$1(validatorFunction)) return false;
|
|
6329
|
+
const firstParameter = validatorFunction.params?.[0];
|
|
6330
|
+
if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
|
|
6331
|
+
const parameterSymbol = scopes.symbolFor(firstParameter);
|
|
6332
|
+
if (!parameterSymbol) return false;
|
|
6333
|
+
const payloadSymbolIds = new Set([parameterSymbol.id]);
|
|
6334
|
+
let hasPropertyTypeCheck = false;
|
|
6335
|
+
walkAst(validatorFunction.body, (child) => {
|
|
6336
|
+
if (isFunctionLike$1(child)) return false;
|
|
6337
|
+
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init) {
|
|
6338
|
+
const initializer = stripParenExpression(child.init);
|
|
6339
|
+
if (isNodeOfType(initializer, "Identifier")) {
|
|
6340
|
+
const initializerSymbol = scopes.symbolFor(initializer);
|
|
6341
|
+
if (initializerSymbol && payloadSymbolIds.has(initializerSymbol.id)) {
|
|
6342
|
+
const aliasSymbol = scopes.symbolFor(child.id);
|
|
6343
|
+
if (aliasSymbol) payloadSymbolIds.add(aliasSymbol.id);
|
|
6344
|
+
}
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
if (!isNodeOfType(child, "UnaryExpression") || child.operator !== "typeof") return;
|
|
6348
|
+
const checkedValue = stripParenExpression(child.argument);
|
|
6349
|
+
if (!isNodeOfType(checkedValue, "MemberExpression")) return;
|
|
6350
|
+
const receiver = stripParenExpression(checkedValue.object);
|
|
6351
|
+
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
6352
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
6353
|
+
if (receiverSymbol && payloadSymbolIds.has(receiverSymbol.id)) hasPropertyTypeCheck = true;
|
|
6354
|
+
});
|
|
6355
|
+
return hasPropertyTypeCheck;
|
|
6356
|
+
};
|
|
6357
|
+
const incrementCount = (counts, keyValue) => {
|
|
6358
|
+
counts.set(keyValue, (counts.get(keyValue) ?? 0) + 1);
|
|
6359
|
+
};
|
|
6360
|
+
const isReturnedExpression = (expression) => {
|
|
6361
|
+
const parent = expression.parent;
|
|
6362
|
+
return Boolean(parent && isNodeOfType(parent, "ReturnStatement") && parent.argument === expression);
|
|
6363
|
+
};
|
|
6364
|
+
const collectSafelyValidatedLocalStorageKeys = ({ programNode, scopes, resolveKey }) => {
|
|
6365
|
+
const readCountsByKey = /* @__PURE__ */ new Map();
|
|
6366
|
+
const safeReadCountsByKey = /* @__PURE__ */ new Map();
|
|
6367
|
+
const safeReadSymbolIds = /* @__PURE__ */ new Set();
|
|
6368
|
+
walkAst(programNode, (child) => {
|
|
6369
|
+
if (!isNodeOfType(child, "CallExpression") || !isLocalStorageCall(child, "getItem")) return;
|
|
6370
|
+
const keyArgument = child.arguments?.[0];
|
|
6371
|
+
if (!keyArgument) return;
|
|
6372
|
+
const keyValue = resolveKey(keyArgument);
|
|
6373
|
+
if (keyValue !== null) incrementCount(readCountsByKey, keyValue);
|
|
6374
|
+
});
|
|
6375
|
+
walkAst(programNode, (child) => {
|
|
6376
|
+
if (!isNodeOfType(child, "TryStatement") || !catchReturnsFallback(child)) return;
|
|
6377
|
+
const rawValueKeys = /* @__PURE__ */ new Map();
|
|
6378
|
+
const parsedValueSources = /* @__PURE__ */ new Map();
|
|
6379
|
+
walkAst(child.block, (tryChild) => {
|
|
6380
|
+
if (isFunctionLike$1(tryChild)) return false;
|
|
6381
|
+
if (isNodeOfType(tryChild, "VariableDeclarator") && isNodeOfType(tryChild.id, "Identifier") && tryChild.init) {
|
|
6382
|
+
const initializer = stripParenExpression(tryChild.init);
|
|
6383
|
+
if (isNodeOfType(initializer, "CallExpression") && isLocalStorageCall(initializer, "getItem")) {
|
|
6384
|
+
const keyArgument = initializer.arguments?.[0];
|
|
6385
|
+
const bindingSymbol = scopes.symbolFor(tryChild.id);
|
|
6386
|
+
if (keyArgument && bindingSymbol) {
|
|
6387
|
+
const keyValue = resolveKey(keyArgument);
|
|
6388
|
+
if (keyValue !== null) rawValueKeys.set(bindingSymbol.id, keyValue);
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
if (isNodeOfType(initializer, "CallExpression") && isGlobalMethodCall(initializer, "JSON", "parse")) {
|
|
6392
|
+
const rawValueArgument = initializer.arguments?.[0];
|
|
6393
|
+
const parsedValueSymbol = scopes.symbolFor(tryChild.id);
|
|
6394
|
+
if (rawValueArgument && isNodeOfType(rawValueArgument, "Identifier") && parsedValueSymbol) {
|
|
6395
|
+
const rawValueSymbol = scopes.symbolFor(rawValueArgument);
|
|
6396
|
+
if (rawValueSymbol && rawValueKeys.has(rawValueSymbol.id)) parsedValueSources.set(parsedValueSymbol.id, rawValueSymbol.id);
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
}
|
|
6400
|
+
if (!isNodeOfType(tryChild, "ConditionalExpression") || !isReturnedExpression(tryChild)) return;
|
|
6401
|
+
const test = stripParenExpression(tryChild.test);
|
|
6402
|
+
if (!isNodeOfType(test, "CallExpression")) return;
|
|
6403
|
+
const testedValue = test.arguments?.[0];
|
|
6404
|
+
if (!testedValue || !isNodeOfType(testedValue, "Identifier")) return;
|
|
6405
|
+
const parsedValueSymbol = scopes.symbolFor(testedValue);
|
|
6406
|
+
if (!parsedValueSymbol) return;
|
|
6407
|
+
const rawValueSymbolId = parsedValueSources.get(parsedValueSymbol.id);
|
|
6408
|
+
if (rawValueSymbolId === void 0) return;
|
|
6409
|
+
const returnedValue = stripParenExpression(tryChild.consequent);
|
|
6410
|
+
if (!isNodeOfType(returnedValue, "Identifier") || scopes.symbolFor(returnedValue)?.id !== parsedValueSymbol.id) return;
|
|
6411
|
+
const validatorFunction = resolveValidatorFunction(test.callee, scopes);
|
|
6412
|
+
if (!validatorFunction || !validatorChecksPayloadProperties(validatorFunction, scopes)) return;
|
|
6413
|
+
const keyValue = rawValueKeys.get(rawValueSymbolId);
|
|
6414
|
+
if (keyValue === void 0 || safeReadSymbolIds.has(rawValueSymbolId)) return;
|
|
6415
|
+
safeReadSymbolIds.add(rawValueSymbolId);
|
|
6416
|
+
incrementCount(safeReadCountsByKey, keyValue);
|
|
6417
|
+
});
|
|
6418
|
+
});
|
|
6419
|
+
const safeKeys = /* @__PURE__ */ new Set();
|
|
6420
|
+
for (const [keyValue, readCount] of readCountsByKey) if (safeReadCountsByKey.get(keyValue) === readCount) safeKeys.add(keyValue);
|
|
6421
|
+
return safeKeys;
|
|
6422
|
+
};
|
|
6423
|
+
//#endregion
|
|
6299
6424
|
//#region src/plugin/rules/client/client-localstorage-no-version.ts
|
|
6300
6425
|
const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
|
|
6301
6426
|
const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
|
|
@@ -6317,26 +6442,39 @@ const clientLocalstorageNoVersion = defineRule({
|
|
|
6317
6442
|
severity: "warn",
|
|
6318
6443
|
category: "Correctness",
|
|
6319
6444
|
recommendation: "Put a version in the storage key (e.g. \"myKey:v1\"). If you change the data shape later, old saved data can be ignored instead of crashing the app.",
|
|
6320
|
-
create: (context) =>
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6445
|
+
create: (context) => {
|
|
6446
|
+
let safelyValidatedStorageKeys = /* @__PURE__ */ new Set();
|
|
6447
|
+
return {
|
|
6448
|
+
Program(node) {
|
|
6449
|
+
safelyValidatedStorageKeys = collectSafelyValidatedLocalStorageKeys({
|
|
6450
|
+
programNode: node,
|
|
6451
|
+
scopes: context.scopes,
|
|
6452
|
+
resolveKey: (keyNode) => resolveStringKey(keyNode, context)
|
|
6453
|
+
});
|
|
6454
|
+
},
|
|
6455
|
+
CallExpression(node) {
|
|
6456
|
+
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
6457
|
+
const receiver = stripParenExpression(node.callee.object);
|
|
6458
|
+
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
6459
|
+
if (receiver.name !== "localStorage") return;
|
|
6460
|
+
if (!isNodeOfType(node.callee.property, "Identifier")) return;
|
|
6461
|
+
if (node.callee.property.name !== "setItem") return;
|
|
6462
|
+
const keyArg = node.arguments?.[0];
|
|
6463
|
+
if (!keyArg) return;
|
|
6464
|
+
const keyValue = resolveStringKey(keyArg, context);
|
|
6465
|
+
if (keyValue === null) return;
|
|
6466
|
+
if (isVersionedKey(keyValue)) return;
|
|
6467
|
+
if (safelyValidatedStorageKeys.has(keyValue)) return;
|
|
6468
|
+
const valueArg = node.arguments?.[1];
|
|
6469
|
+
if (!valueArg) return;
|
|
6470
|
+
if (!isJsonStringifyCall(valueArg)) return;
|
|
6471
|
+
context.report({
|
|
6472
|
+
node: keyArg,
|
|
6473
|
+
message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
|
|
6474
|
+
});
|
|
6475
|
+
}
|
|
6476
|
+
};
|
|
6477
|
+
}
|
|
6340
6478
|
});
|
|
6341
6479
|
//#endregion
|
|
6342
6480
|
//#region src/plugin/rules/client/client-passive-event-listeners.ts
|
|
@@ -15972,14 +16110,21 @@ const jsxFilenameExtension = defineRule({
|
|
|
15972
16110
|
});
|
|
15973
16111
|
//#endregion
|
|
15974
16112
|
//#region src/plugin/utils/is-jsx-fragment-element.ts
|
|
15975
|
-
const isJsxFragmentElement = (node) => {
|
|
16113
|
+
const isJsxFragmentElement = (node, scopes) => {
|
|
15976
16114
|
if (!isNodeOfType(node, "JSXOpeningElement")) return false;
|
|
15977
16115
|
const elementName = node.name;
|
|
15978
|
-
if (isNodeOfType(elementName, "JSXIdentifier"))
|
|
16116
|
+
if (isNodeOfType(elementName, "JSXIdentifier")) {
|
|
16117
|
+
if (!scopes) return elementName.name === "Fragment";
|
|
16118
|
+
const symbol = resolveConstIdentifierAlias(elementName, scopes);
|
|
16119
|
+
if (!symbol) return elementName.name === "Fragment" && scopes.isGlobalReference(elementName);
|
|
16120
|
+
return isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment";
|
|
16121
|
+
}
|
|
15979
16122
|
if (isNodeOfType(elementName, "JSXMemberExpression")) {
|
|
15980
16123
|
if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
|
|
15981
|
-
if (elementName.
|
|
15982
|
-
return elementName.
|
|
16124
|
+
if (elementName.property.name !== "Fragment") return false;
|
|
16125
|
+
if (!scopes) return elementName.object.name === "React";
|
|
16126
|
+
if (isReactNamespaceImport(elementName.object, scopes)) return true;
|
|
16127
|
+
return elementName.object.name === "React" && scopes.isGlobalReference(elementName.object);
|
|
15983
16128
|
}
|
|
15984
16129
|
return false;
|
|
15985
16130
|
};
|
|
@@ -16005,7 +16150,7 @@ const jsxFragments = defineRule({
|
|
|
16005
16150
|
if (mode !== "syntax") return;
|
|
16006
16151
|
if (!node.closingElement) return;
|
|
16007
16152
|
const openingElement = node.openingElement;
|
|
16008
|
-
if (!isJsxFragmentElement(openingElement)) return;
|
|
16153
|
+
if (!isJsxFragmentElement(openingElement, context.scopes)) return;
|
|
16009
16154
|
if (openingElement.attributes.length > 0) return;
|
|
16010
16155
|
context.report({
|
|
16011
16156
|
node: openingElement,
|
|
@@ -19146,7 +19291,7 @@ const jsxNoUselessFragment = defineRule({
|
|
|
19146
19291
|
return {
|
|
19147
19292
|
JSXElement(node) {
|
|
19148
19293
|
const openingElement = node.openingElement;
|
|
19149
|
-
if (!isJsxFragmentElement(openingElement)) return;
|
|
19294
|
+
if (!isJsxFragmentElement(openingElement, context.scopes)) return;
|
|
19150
19295
|
if (hasJsxKeyAttribute(openingElement)) return;
|
|
19151
19296
|
if (checkChildren(node, openingElement, node.children)) return;
|
|
19152
19297
|
if (isChildOfHtmlElement(node)) context.report({
|
|
@@ -26060,6 +26205,56 @@ const noBarrelImport = defineRule({
|
|
|
26060
26205
|
}
|
|
26061
26206
|
});
|
|
26062
26207
|
//#endregion
|
|
26208
|
+
//#region src/plugin/utils/function-returns-matching-expression.ts
|
|
26209
|
+
const collectReturnedExpressions = (functionNode) => {
|
|
26210
|
+
if (!isFunctionLike$1(functionNode)) return [];
|
|
26211
|
+
const body = functionNode.body;
|
|
26212
|
+
if (!body) return [];
|
|
26213
|
+
if (!isNodeOfType(body, "BlockStatement")) return [body];
|
|
26214
|
+
const returnedExpressions = [];
|
|
26215
|
+
walkAst(body, (node) => {
|
|
26216
|
+
if (node !== body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
26217
|
+
if (isNodeOfType(node, "ReturnStatement") && node.argument) returnedExpressions.push(node.argument);
|
|
26218
|
+
});
|
|
26219
|
+
return returnedExpressions;
|
|
26220
|
+
};
|
|
26221
|
+
const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpression) => {
|
|
26222
|
+
const visitedExpressions = /* @__PURE__ */ new Set();
|
|
26223
|
+
const visitedFunctions = /* @__PURE__ */ new Set();
|
|
26224
|
+
const functionMatches = (candidateFunction) => {
|
|
26225
|
+
if (visitedFunctions.has(candidateFunction)) return false;
|
|
26226
|
+
visitedFunctions.add(candidateFunction);
|
|
26227
|
+
return collectReturnedExpressions(candidateFunction).some(expressionMatches);
|
|
26228
|
+
};
|
|
26229
|
+
const expressionMatches = (expression) => {
|
|
26230
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
26231
|
+
if (visitedExpressions.has(unwrappedExpression)) return false;
|
|
26232
|
+
visitedExpressions.add(unwrappedExpression);
|
|
26233
|
+
if (matchesExpression(unwrappedExpression)) return true;
|
|
26234
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
26235
|
+
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
26236
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer) return false;
|
|
26237
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
26238
|
+
if (isFunctionLike$1(initializer)) return false;
|
|
26239
|
+
return expressionMatches(initializer);
|
|
26240
|
+
}
|
|
26241
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
26242
|
+
if (unwrappedExpression.arguments.length !== 0) return false;
|
|
26243
|
+
if (!isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
|
|
26244
|
+
const symbol = scopes.symbolFor(unwrappedExpression.callee);
|
|
26245
|
+
if (!symbol || symbol.kind !== "const" && symbol.kind !== "function") return false;
|
|
26246
|
+
const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
26247
|
+
const candidateFunction = isFunctionLike$1(initializer) ? initializer : isFunctionLike$1(symbol.declarationNode) ? symbol.declarationNode : null;
|
|
26248
|
+
if (!candidateFunction || candidateFunction.async || candidateFunction.generator || candidateFunction.params.length !== 0) return false;
|
|
26249
|
+
return functionMatches(candidateFunction);
|
|
26250
|
+
}
|
|
26251
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return expressionMatches(unwrappedExpression.consequent) || expressionMatches(unwrappedExpression.alternate);
|
|
26252
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return expressionMatches(unwrappedExpression.left) || expressionMatches(unwrappedExpression.right);
|
|
26253
|
+
return false;
|
|
26254
|
+
};
|
|
26255
|
+
return functionMatches(functionNode);
|
|
26256
|
+
};
|
|
26257
|
+
//#endregion
|
|
26063
26258
|
//#region src/plugin/utils/function-contains-react-render-output.ts
|
|
26064
26259
|
const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
|
|
26065
26260
|
"FunctionDeclaration",
|
|
@@ -26079,12 +26274,13 @@ const isCallArgumentFunctionExpression = (node) => {
|
|
|
26079
26274
|
return parent.arguments.some((argumentNode) => argumentNode === node);
|
|
26080
26275
|
};
|
|
26081
26276
|
const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
|
|
26277
|
+
const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
|
|
26082
26278
|
const containsRenderOutput$1 = (rootNode, scopes) => {
|
|
26083
26279
|
let hasRenderOutput = false;
|
|
26084
26280
|
walkAst(rootNode, (node) => {
|
|
26085
26281
|
if (hasRenderOutput) return false;
|
|
26086
26282
|
if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
|
|
26087
|
-
if (
|
|
26283
|
+
if (isRenderOutputExpression(node, scopes)) {
|
|
26088
26284
|
hasRenderOutput = true;
|
|
26089
26285
|
return false;
|
|
26090
26286
|
}
|
|
@@ -26095,7 +26291,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
|
|
|
26095
26291
|
const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
26096
26292
|
const cachedEntry = renderOutputCache.get(functionNode);
|
|
26097
26293
|
if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
|
|
26098
|
-
const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
|
|
26294
|
+
const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
|
|
26099
26295
|
renderOutputCache.set(functionNode, {
|
|
26100
26296
|
scopes,
|
|
26101
26297
|
hasRenderOutput
|
|
@@ -40123,20 +40319,21 @@ const expressionContainsJsxOrCreateElement = (root) => {
|
|
|
40123
40319
|
});
|
|
40124
40320
|
return found;
|
|
40125
40321
|
};
|
|
40322
|
+
const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
|
|
40126
40323
|
const isReactClassComponent = (classNode) => {
|
|
40127
40324
|
if (isEs6Component(classNode)) return true;
|
|
40128
40325
|
return expressionContainsJsxOrCreateElement(classNode);
|
|
40129
40326
|
};
|
|
40130
|
-
const findEnclosingComponent = (node) => {
|
|
40327
|
+
const findEnclosingComponent = (node, scopes) => {
|
|
40131
40328
|
let walker = node.parent;
|
|
40132
40329
|
while (walker) {
|
|
40133
40330
|
if (isFunctionLike$1(walker)) {
|
|
40134
40331
|
const componentName = inferFunctionLikeName(walker);
|
|
40135
|
-
if (componentName && isReactComponentName(componentName) &&
|
|
40332
|
+
if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
|
|
40136
40333
|
component: walker,
|
|
40137
40334
|
name: componentName
|
|
40138
40335
|
};
|
|
40139
|
-
if (!componentName &&
|
|
40336
|
+
if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
|
|
40140
40337
|
component: walker,
|
|
40141
40338
|
name: null
|
|
40142
40339
|
};
|
|
@@ -40375,7 +40572,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
40375
40572
|
if (renderPropRegex.test(propInfo.propName)) return;
|
|
40376
40573
|
if (settings.allowAsProps) return;
|
|
40377
40574
|
}
|
|
40378
|
-
const enclosing = findEnclosingComponent(candidateNode);
|
|
40575
|
+
const enclosing = findEnclosingComponent(candidateNode, context.scopes);
|
|
40379
40576
|
if (!enclosing) return;
|
|
40380
40577
|
const gatedName = propInfo ? null : requiredInstantiationName;
|
|
40381
40578
|
queuedReports.push({
|
|
@@ -40386,7 +40583,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
40386
40583
|
});
|
|
40387
40584
|
};
|
|
40388
40585
|
const checkFunctionLike = (node) => {
|
|
40389
|
-
if (!
|
|
40586
|
+
if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
|
|
40390
40587
|
const inferredName = inferFunctionLikeName(node);
|
|
40391
40588
|
const propInfo = isComponentDeclaredInProp(node);
|
|
40392
40589
|
const isObjectCallback = isObjectCallbackCandidate(node);
|
|
@@ -45416,14 +45613,10 @@ const rerenderLazyStateInit = defineRule({
|
|
|
45416
45613
|
//#endregion
|
|
45417
45614
|
//#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
|
|
45418
45615
|
const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
|
|
45419
|
-
const callbackReturnsJsx = (callback) => {
|
|
45616
|
+
const callbackReturnsJsx = (callback, scopes) => {
|
|
45420
45617
|
if (!callback) return false;
|
|
45421
45618
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
45422
|
-
|
|
45423
|
-
if (isJsxExpression(body)) return true;
|
|
45424
|
-
if (!isNodeOfType(body, "BlockStatement")) return false;
|
|
45425
|
-
for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
|
|
45426
|
-
return false;
|
|
45619
|
+
return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
|
|
45427
45620
|
};
|
|
45428
45621
|
const returnArgumentUsesAnyName = (returnStatement, names) => {
|
|
45429
45622
|
if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
|
|
@@ -45501,7 +45694,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
|
|
|
45501
45694
|
if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
|
|
45502
45695
|
for (const declarator of stmt.declarations ?? []) {
|
|
45503
45696
|
const init = declarator.init;
|
|
45504
|
-
if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
|
|
45697
|
+
if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
|
|
45505
45698
|
memoNode = declarator;
|
|
45506
45699
|
callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
|
|
45507
45700
|
if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
|