oxlint-plugin-react-doctor 0.7.6-dev.81e6647 → 0.7.6-dev.8528def

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.
Files changed (2) hide show
  1. package/dist/index.js +76 -356
  2. 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") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
6024
+ if (!isNodeOfType(identifier, "Identifier")) return null;
6025
6025
  const visitedSymbolIds = /* @__PURE__ */ new Set();
6026
6026
  let symbol = scopes.symbolFor(identifier);
6027
6027
  while (symbol?.kind === "const") {
@@ -6296,131 +6296,6 @@ 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
6424
6299
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6425
6300
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6426
6301
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
@@ -6442,39 +6317,26 @@ const clientLocalstorageNoVersion = defineRule({
6442
6317
  severity: "warn",
6443
6318
  category: "Correctness",
6444
6319
  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.",
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
- }
6320
+ create: (context) => ({ CallExpression(node) {
6321
+ if (!isNodeOfType(node.callee, "MemberExpression")) return;
6322
+ const receiver = stripParenExpression(node.callee.object);
6323
+ if (!isNodeOfType(receiver, "Identifier")) return;
6324
+ if (receiver.name !== "localStorage") return;
6325
+ if (!isNodeOfType(node.callee.property, "Identifier")) return;
6326
+ if (node.callee.property.name !== "setItem") return;
6327
+ const keyArg = node.arguments?.[0];
6328
+ if (!keyArg) return;
6329
+ const keyValue = resolveStringKey(keyArg, context);
6330
+ if (keyValue === null) return;
6331
+ if (isVersionedKey(keyValue)) return;
6332
+ const valueArg = node.arguments?.[1];
6333
+ if (!valueArg) return;
6334
+ if (!isJsonStringifyCall(valueArg)) return;
6335
+ context.report({
6336
+ node: keyArg,
6337
+ 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").`
6338
+ });
6339
+ } })
6478
6340
  });
6479
6341
  //#endregion
6480
6342
  //#region src/plugin/rules/client/client-passive-event-listeners.ts
@@ -14749,18 +14611,6 @@ const jsHoistRegexp = defineRule({
14749
14611
  }, { treatIteratorCallbacksAsLoops: true })
14750
14612
  });
14751
14613
  //#endregion
14752
- //#region src/plugin/utils/resolve-first-argument-binding.ts
14753
- const resolveFirstArgumentBinding = (firstParameter) => {
14754
- if (!firstParameter) return null;
14755
- if (!isNodeOfType(firstParameter, "RestElement")) return firstParameter;
14756
- if (!isNodeOfType(firstParameter.argument, "ArrayPattern")) return null;
14757
- const elements = firstParameter.argument.elements ?? [];
14758
- if (elements.length !== 1) return null;
14759
- const firstBinding = elements[0];
14760
- if (!firstBinding || isNodeOfType(firstBinding, "RestElement")) return null;
14761
- return firstBinding;
14762
- };
14763
- //#endregion
14764
14614
  //#region src/plugin/rules/js-performance/js-index-maps.ts
14765
14615
  const referencesParameter = (expression, parameterName) => {
14766
14616
  if (!expression) return false;
@@ -14771,7 +14621,7 @@ const referencesParameter = (expression, parameterName) => {
14771
14621
  const isSingleFieldEqualityPredicate = (node) => {
14772
14622
  const callback = node.arguments?.[0];
14773
14623
  if (!isInlineFunctionExpression(callback)) return false;
14774
- const firstParameter = resolveFirstArgumentBinding(callback.params?.[0]);
14624
+ const firstParameter = callback.params?.[0];
14775
14625
  if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
14776
14626
  let predicate = null;
14777
14627
  const body = callback.body;
@@ -16110,21 +15960,14 @@ const jsxFilenameExtension = defineRule({
16110
15960
  });
16111
15961
  //#endregion
16112
15962
  //#region src/plugin/utils/is-jsx-fragment-element.ts
16113
- const isJsxFragmentElement = (node, scopes) => {
15963
+ const isJsxFragmentElement = (node) => {
16114
15964
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
16115
15965
  const elementName = node.name;
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
- }
15966
+ if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
16122
15967
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
16123
15968
  if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
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);
15969
+ if (elementName.object.name !== "React") return false;
15970
+ return elementName.property.name === "Fragment";
16128
15971
  }
16129
15972
  return false;
16130
15973
  };
@@ -16150,7 +15993,7 @@ const jsxFragments = defineRule({
16150
15993
  if (mode !== "syntax") return;
16151
15994
  if (!node.closingElement) return;
16152
15995
  const openingElement = node.openingElement;
16153
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
15996
+ if (!isJsxFragmentElement(openingElement)) return;
16154
15997
  if (openingElement.attributes.length > 0) return;
16155
15998
  context.report({
16156
15999
  node: openingElement,
@@ -19291,7 +19134,7 @@ const jsxNoUselessFragment = defineRule({
19291
19134
  return {
19292
19135
  JSXElement(node) {
19293
19136
  const openingElement = node.openingElement;
19294
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
19137
+ if (!isJsxFragmentElement(openingElement)) return;
19295
19138
  if (hasJsxKeyAttribute(openingElement)) return;
19296
19139
  if (checkChildren(node, openingElement, node.children)) return;
19297
19140
  if (isChildOfHtmlElement(node)) context.report({
@@ -20533,24 +20376,8 @@ const hasDirective = (programNode, directive) => {
20533
20376
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20534
20377
  };
20535
20378
  //#endregion
20536
- //#region src/plugin/utils/unwrap-object-integrity-expression.ts
20537
- const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
20538
- "freeze",
20539
- "seal",
20540
- "preventExtensions"
20541
- ]);
20542
- const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
20543
- const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
20544
- let expression = stripParenExpression(node);
20545
- while (isNodeOfType(expression, "CallExpression")) {
20546
- const callee = stripParenExpression(expression.callee);
20547
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || callee.object.name !== "Object" || !scopes.isGlobalReference(callee.object) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) break;
20548
- const wrappedExpression = expression.arguments[0];
20549
- if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
20550
- expression = stripParenExpression(wrappedExpression);
20551
- }
20552
- return expression;
20553
- };
20379
+ //#region src/plugin/utils/is-component-assignment.ts
20380
+ const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
20554
20381
  //#endregion
20555
20382
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
20556
20383
  const nextjsAsyncClientComponent = defineRule({
@@ -20576,10 +20403,10 @@ const nextjsAsyncClientComponent = defineRule({
20576
20403
  },
20577
20404
  VariableDeclarator(node) {
20578
20405
  if (!fileHasUseClient) return;
20406
+ if (!isComponentAssignment(node)) return;
20407
+ if (!isInlineFunctionExpression(node.init)) return;
20408
+ if (!node.init.async) return;
20579
20409
  if (!isNodeOfType(node.id, "Identifier")) return;
20580
- if (!isUppercaseName(node.id.name) || !node.init) return;
20581
- const componentFunction = unwrapObjectIntegrityExpression(node.init, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES);
20582
- if (!isInlineFunctionExpression(componentFunction) || !componentFunction.async) return;
20583
20410
  context.report({
20584
20411
  node,
20585
20412
  message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
@@ -23216,15 +23043,10 @@ const isProp = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
23216
23043
  if (!declaringNode) return false;
23217
23044
  return isReactFunctionalComponent(declaringNode) && !isReactFunctionalHOC(analysis, declaringNode) || isCustomHook(declaringNode);
23218
23045
  }));
23219
- const isWholePropsParameterBinding = (bindingNode) => {
23220
- if (isFunctionLike$1(bindingNode.parent)) return true;
23221
- let declaringFunction = bindingNode.parent;
23222
- while (declaringFunction && !isFunctionLike$1(declaringFunction)) declaringFunction = declaringFunction.parent;
23223
- return Boolean(declaringFunction && resolveFirstArgumentBinding(declaringFunction.params?.[0]) === bindingNode);
23224
- };
23225
23046
  const isWholePropsObjectReference = (analysis, ref) => isProp(analysis, ref) && Boolean(ref.resolved?.defs.some((def) => {
23226
23047
  if (def.type !== "Parameter") return false;
23227
- return isWholePropsParameterBinding(def.name);
23048
+ const bindingParent = def.name.parent;
23049
+ return isFunctionLike$1(bindingParent);
23228
23050
  }));
23229
23051
  const isIdentifierOrMemberExpression = (node) => isNodeOfType(node, "Identifier") || isNodeOfType(node, "MemberExpression");
23230
23052
  const isPropAlias = (analysis, ref) => {
@@ -26221,56 +26043,6 @@ const noBarrelImport = defineRule({
26221
26043
  }
26222
26044
  });
26223
26045
  //#endregion
26224
- //#region src/plugin/utils/function-returns-matching-expression.ts
26225
- const collectReturnedExpressions = (functionNode) => {
26226
- if (!isFunctionLike$1(functionNode)) return [];
26227
- const body = functionNode.body;
26228
- if (!body) return [];
26229
- if (!isNodeOfType(body, "BlockStatement")) return [body];
26230
- const returnedExpressions = [];
26231
- walkAst(body, (node) => {
26232
- if (node !== body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
26233
- if (isNodeOfType(node, "ReturnStatement") && node.argument) returnedExpressions.push(node.argument);
26234
- });
26235
- return returnedExpressions;
26236
- };
26237
- const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpression) => {
26238
- const visitedExpressions = /* @__PURE__ */ new Set();
26239
- const visitedFunctions = /* @__PURE__ */ new Set();
26240
- const functionMatches = (candidateFunction) => {
26241
- if (visitedFunctions.has(candidateFunction)) return false;
26242
- visitedFunctions.add(candidateFunction);
26243
- return collectReturnedExpressions(candidateFunction).some(expressionMatches);
26244
- };
26245
- const expressionMatches = (expression) => {
26246
- const unwrappedExpression = stripParenExpression(expression);
26247
- if (visitedExpressions.has(unwrappedExpression)) return false;
26248
- visitedExpressions.add(unwrappedExpression);
26249
- if (matchesExpression(unwrappedExpression)) return true;
26250
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
26251
- const symbol = scopes.symbolFor(unwrappedExpression);
26252
- if (!symbol || symbol.kind !== "const" || !symbol.initializer) return false;
26253
- const initializer = stripParenExpression(symbol.initializer);
26254
- if (isFunctionLike$1(initializer)) return false;
26255
- return expressionMatches(initializer);
26256
- }
26257
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
26258
- if (unwrappedExpression.arguments.length !== 0) return false;
26259
- if (!isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
26260
- const symbol = scopes.symbolFor(unwrappedExpression.callee);
26261
- if (!symbol || symbol.kind !== "const" && symbol.kind !== "function") return false;
26262
- const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
26263
- const candidateFunction = isFunctionLike$1(initializer) ? initializer : isFunctionLike$1(symbol.declarationNode) ? symbol.declarationNode : null;
26264
- if (!candidateFunction || candidateFunction.async || candidateFunction.generator || candidateFunction.params.length !== 0) return false;
26265
- return functionMatches(candidateFunction);
26266
- }
26267
- if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return expressionMatches(unwrappedExpression.consequent) || expressionMatches(unwrappedExpression.alternate);
26268
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return expressionMatches(unwrappedExpression.left) || expressionMatches(unwrappedExpression.right);
26269
- return false;
26270
- };
26271
- return functionMatches(functionNode);
26272
- };
26273
- //#endregion
26274
26046
  //#region src/plugin/utils/function-contains-react-render-output.ts
26275
26047
  const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
26276
26048
  "FunctionDeclaration",
@@ -26290,13 +26062,12 @@ const isCallArgumentFunctionExpression = (node) => {
26290
26062
  return parent.arguments.some((argumentNode) => argumentNode === node);
26291
26063
  };
26292
26064
  const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
26293
- const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
26294
26065
  const containsRenderOutput$1 = (rootNode, scopes) => {
26295
26066
  let hasRenderOutput = false;
26296
26067
  walkAst(rootNode, (node) => {
26297
26068
  if (hasRenderOutput) return false;
26298
26069
  if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
26299
- if (isRenderOutputExpression(node, scopes)) {
26070
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
26300
26071
  hasRenderOutput = true;
26301
26072
  return false;
26302
26073
  }
@@ -26307,7 +26078,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
26307
26078
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
26308
26079
  const cachedEntry = renderOutputCache.get(functionNode);
26309
26080
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
26310
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
26081
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
26311
26082
  renderOutputCache.set(functionNode, {
26312
26083
  scopes,
26313
26084
  hasRenderOutput
@@ -26315,9 +26086,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
26315
26086
  return hasRenderOutput;
26316
26087
  };
26317
26088
  //#endregion
26318
- //#region src/plugin/utils/is-component-assignment.ts
26319
- const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
26320
- //#endregion
26321
26089
  //#region src/plugin/utils/is-component-declaration.ts
26322
26090
  const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
26323
26091
  //#endregion
@@ -32367,12 +32135,11 @@ const isMemoCall = (node) => {
32367
32135
  };
32368
32136
  const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
32369
32137
  const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
32370
- const isInlineReference = (node, scopes) => {
32371
- const referenceNode = unwrapObjectIntegrityExpression(node, scopes);
32372
- if (isNodeOfType(referenceNode, "ArrowFunctionExpression") || isNodeOfType(referenceNode, "FunctionExpression") || isNodeOfType(referenceNode, "CallExpression") && isNodeOfType(referenceNode.callee, "MemberExpression") && isNodeOfType(referenceNode.callee.property, "Identifier") && referenceNode.callee.property.name === "bind") return "functions";
32373
- if (isNodeOfType(referenceNode, "ObjectExpression")) return "objects";
32374
- if (isNodeOfType(referenceNode, "ArrayExpression")) return "Arrays";
32375
- if (isNodeOfType(referenceNode, "JSXElement") || isNodeOfType(referenceNode, "JSXFragment")) return "JSX";
32138
+ const isInlineReference = (node) => {
32139
+ if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "bind") return "functions";
32140
+ if (isNodeOfType(node, "ObjectExpression")) return "objects";
32141
+ if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
32142
+ if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
32376
32143
  return null;
32377
32144
  };
32378
32145
  const noInlinePropOnMemoComponent = defineRule({
@@ -32402,7 +32169,7 @@ const noInlinePropOnMemoComponent = defineRule({
32402
32169
  let elementName = null;
32403
32170
  if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
32404
32171
  if (!elementName || !memoizedComponentNames.has(elementName)) return;
32405
- const propType = isInlineReference(node.value.expression, context.scopes);
32172
+ const propType = isInlineReference(node.value.expression);
32406
32173
  if (propType) context.report({
32407
32174
  node: node.value.expression,
32408
32175
  message: `This redraws ${elementName} on every render because the prop is ${propType} built right here, so memo() can't skip it. Move it to a stable value with useMemo, useCallback, or module scope`
@@ -33261,13 +33028,11 @@ const noManyBooleanProps = defineRule({
33261
33028
  };
33262
33029
  const checkComponent = (functionNode, param, body, componentName, reportNode) => {
33263
33030
  if (!param) return;
33264
- const propsBinding = resolveFirstArgumentBinding(param);
33265
- if (!propsBinding) return;
33266
33031
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
33267
- if (isNodeOfType(propsBinding, "ObjectPattern")) {
33032
+ if (isNodeOfType(param, "ObjectPattern")) {
33268
33033
  const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
33269
33034
  const booleanLikePropNames = [];
33270
- for (const property of propsBinding.properties ?? []) {
33035
+ for (const property of param.properties ?? []) {
33271
33036
  if (!isNodeOfType(property, "Property")) continue;
33272
33037
  const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
33273
33038
  if (!keyName) continue;
@@ -33278,7 +33043,7 @@ const noManyBooleanProps = defineRule({
33278
33043
  reportIfMany(booleanLikePropNames, componentName, reportNode);
33279
33044
  return;
33280
33045
  }
33281
- if (isNodeOfType(propsBinding, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, propsBinding.name)], componentName, reportNode);
33046
+ if (isNodeOfType(param, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, param.name)], componentName, reportNode);
33282
33047
  };
33283
33048
  return {
33284
33049
  FunctionDeclaration(node) {
@@ -40339,21 +40104,20 @@ const expressionContainsJsxOrCreateElement = (root) => {
40339
40104
  });
40340
40105
  return found;
40341
40106
  };
40342
- const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
40343
40107
  const isReactClassComponent = (classNode) => {
40344
40108
  if (isEs6Component(classNode)) return true;
40345
40109
  return expressionContainsJsxOrCreateElement(classNode);
40346
40110
  };
40347
- const findEnclosingComponent = (node, scopes) => {
40111
+ const findEnclosingComponent = (node) => {
40348
40112
  let walker = node.parent;
40349
40113
  while (walker) {
40350
40114
  if (isFunctionLike$1(walker)) {
40351
40115
  const componentName = inferFunctionLikeName(walker);
40352
- if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
40116
+ if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
40353
40117
  component: walker,
40354
40118
  name: componentName
40355
40119
  };
40356
- if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40120
+ if (!componentName && expressionContainsJsxOrCreateElement(walker) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40357
40121
  component: walker,
40358
40122
  name: null
40359
40123
  };
@@ -40592,7 +40356,7 @@ const noUnstableNestedComponents = defineRule({
40592
40356
  if (renderPropRegex.test(propInfo.propName)) return;
40593
40357
  if (settings.allowAsProps) return;
40594
40358
  }
40595
- const enclosing = findEnclosingComponent(candidateNode, context.scopes);
40359
+ const enclosing = findEnclosingComponent(candidateNode);
40596
40360
  if (!enclosing) return;
40597
40361
  const gatedName = propInfo ? null : requiredInstantiationName;
40598
40362
  queuedReports.push({
@@ -40603,7 +40367,7 @@ const noUnstableNestedComponents = defineRule({
40603
40367
  });
40604
40368
  };
40605
40369
  const checkFunctionLike = (node) => {
40606
- if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
40370
+ if (!expressionContainsJsxOrCreateElement(node)) return;
40607
40371
  const inferredName = inferFunctionLikeName(node);
40608
40372
  const propInfo = isComponentDeclaredInProp(node);
40609
40373
  const isObjectCallback = isObjectCallbackCandidate(node);
@@ -42040,9 +41804,8 @@ const CROSS_CUTTING_STATE_BOOLEAN_NAMES = new Set([
42040
41804
  ]);
42041
41805
  const collectBooleanPropBindings = (param) => {
42042
41806
  const bindings = /* @__PURE__ */ new Set();
42043
- const propsBinding = resolveFirstArgumentBinding(param);
42044
- if (!isNodeOfType(propsBinding, "ObjectPattern")) return bindings;
42045
- for (const property of propsBinding.properties ?? []) {
41807
+ if (!param || !isNodeOfType(param, "ObjectPattern")) return bindings;
41808
+ for (const property of param.properties ?? []) {
42046
41809
  if (!isNodeOfType(property, "Property")) continue;
42047
41810
  if (property.computed) continue;
42048
41811
  if (!isNodeOfType(property.key, "Identifier")) continue;
@@ -43850,13 +43613,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
43850
43613
  }
43851
43614
  return null;
43852
43615
  };
43853
- const resolveHookNameFromInitializer = (initializer, scopes) => {
43616
+ const resolveHookNameFromInitializer = (initializer) => {
43854
43617
  if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
43855
43618
  if (!isNodeOfType(initializer, "Identifier")) return null;
43856
- const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
43857
- if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
43858
- if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
43859
- return resolveTanstackQueryHookName(resolvedSymbol.initializer);
43619
+ const binding = findVariableInitializer(initializer, initializer.name);
43620
+ if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
43621
+ if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
43622
+ return resolveTanstackQueryHookName(binding.initializer);
43860
43623
  };
43861
43624
  const queryNoRestDestructuring = defineRule({
43862
43625
  id: "query-no-rest-destructuring",
@@ -43869,7 +43632,7 @@ const queryNoRestDestructuring = defineRule({
43869
43632
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
43870
43633
  if (!node.init) return;
43871
43634
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
43872
- const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
43635
+ const hookName = resolveHookNameFromInitializer(node.init);
43873
43636
  if (!hookName) return;
43874
43637
  context.report({
43875
43638
  node: node.id,
@@ -45224,7 +44987,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
45224
44987
  "useParams",
45225
44988
  "usePathname"
45226
44989
  ]);
45227
- const findHookCallBindings = (componentBody, scopes) => {
44990
+ const findHookCallBindings = (componentBody) => {
45228
44991
  const bindings = [];
45229
44992
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
45230
44993
  for (const statement of componentBody.body ?? []) {
@@ -45235,10 +44998,8 @@ const findHookCallBindings = (componentBody, scopes) => {
45235
44998
  const callee = declarator.init.callee;
45236
44999
  if (!isNodeOfType(callee, "Identifier")) continue;
45237
45000
  if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
45238
- const valueSymbol = scopes.symbolFor(declarator.id);
45239
- if (!valueSymbol) continue;
45240
45001
  bindings.push({
45241
- valueSymbol,
45002
+ valueName: declarator.id.name,
45242
45003
  hookName: callee.name,
45243
45004
  declarator
45244
45005
  });
@@ -45246,49 +45007,6 @@ const findHookCallBindings = (componentBody, scopes) => {
45246
45007
  }
45247
45008
  return bindings;
45248
45009
  };
45249
- const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
45250
- const symbols = [sourceSymbol];
45251
- const symbolIds = new Set([sourceSymbol.id]);
45252
- const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
45253
- if (!isNodeOfType(componentBody, "BlockStatement")) return {
45254
- symbols,
45255
- aliasSourceIdentifiers
45256
- };
45257
- let didFindAlias = true;
45258
- while (didFindAlias) {
45259
- didFindAlias = false;
45260
- for (const statement of componentBody.body ?? []) {
45261
- if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
45262
- for (const declarator of statement.declarations ?? []) {
45263
- let aliasIdentifier = null;
45264
- let sourceIdentifier = null;
45265
- const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
45266
- const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
45267
- const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
45268
- const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
45269
- if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
45270
- aliasIdentifier = declarator.id;
45271
- sourceIdentifier = initializer;
45272
- } else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
45273
- aliasIdentifier = arrayBinding;
45274
- sourceIdentifier = arrayValue;
45275
- }
45276
- if (!aliasIdentifier || !sourceIdentifier) continue;
45277
- const referencedSymbol = scopes.symbolFor(sourceIdentifier);
45278
- const aliasSymbol = scopes.symbolFor(aliasIdentifier);
45279
- if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
45280
- symbolIds.add(aliasSymbol.id);
45281
- symbols.push(aliasSymbol);
45282
- aliasSourceIdentifiers.add(sourceIdentifier);
45283
- didFindAlias = true;
45284
- }
45285
- }
45286
- }
45287
- return {
45288
- symbols,
45289
- aliasSourceIdentifiers
45290
- };
45291
- };
45292
45010
  const rerenderDeferReadsHook = defineRule({
45293
45011
  id: "rerender-defer-reads-hook",
45294
45012
  title: "URL hook value only read in handlers",
@@ -45299,13 +45017,15 @@ const rerenderDeferReadsHook = defineRule({
45299
45017
  create: (context) => {
45300
45018
  const checkComponent = (componentBody) => {
45301
45019
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
45302
- const bindings = findHookCallBindings(componentBody, context.scopes);
45020
+ const bindings = findHookCallBindings(componentBody);
45303
45021
  if (bindings.length === 0) return;
45304
45022
  const handlerBindingNames = collectHandlerBindingNames(componentBody);
45305
45023
  for (const binding of bindings) {
45306
- const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
45307
45024
  const referenceLocations = [];
45308
- for (const symbol of symbols) for (const reference of symbol.references) if (!aliasSourceIdentifiers.has(reference.identifier)) referenceLocations.push(reference.identifier);
45025
+ walkAst(componentBody, (child) => {
45026
+ if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
45027
+ if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
45028
+ });
45309
45029
  if (referenceLocations.length === 0) continue;
45310
45030
  if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
45311
45031
  context.report({
@@ -45676,10 +45396,14 @@ const rerenderLazyStateInit = defineRule({
45676
45396
  //#endregion
45677
45397
  //#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
45678
45398
  const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
45679
- const callbackReturnsJsx = (callback, scopes) => {
45399
+ const callbackReturnsJsx = (callback) => {
45680
45400
  if (!callback) return false;
45681
45401
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
45682
- return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
45402
+ const body = callback.body;
45403
+ if (isJsxExpression(body)) return true;
45404
+ if (!isNodeOfType(body, "BlockStatement")) return false;
45405
+ for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
45406
+ return false;
45683
45407
  };
45684
45408
  const returnArgumentUsesAnyName = (returnStatement, names) => {
45685
45409
  if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
@@ -45757,7 +45481,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
45757
45481
  if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
45758
45482
  for (const declarator of stmt.declarations ?? []) {
45759
45483
  const init = declarator.init;
45760
- if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
45484
+ if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
45761
45485
  memoNode = declarator;
45762
45486
  callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
45763
45487
  if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
@@ -45823,11 +45547,8 @@ const collectFromObjectPattern = (pattern, bindings) => {
45823
45547
  const collectDefaultedEmptyBindings = (functionNode) => {
45824
45548
  const bindings = /* @__PURE__ */ new Map();
45825
45549
  const params = functionNode.params ?? [];
45826
- for (const [parameterIndex, param] of params.entries()) {
45827
- const parameterBinding = parameterIndex === 0 ? resolveFirstArgumentBinding(param) : param;
45828
- if (parameterBinding) collectFromObjectPattern(parameterBinding, bindings);
45829
- }
45830
- const propsParam = resolveFirstArgumentBinding(params[0]);
45550
+ for (const param of params) collectFromObjectPattern(param, bindings);
45551
+ const propsParam = params[0];
45831
45552
  const body = functionNode.body;
45832
45553
  if (!propsParam || !isNodeOfType(propsParam, "Identifier") || !body) return bindings;
45833
45554
  if (!isNodeOfType(body, "BlockStatement")) return bindings;
@@ -53254,8 +52975,7 @@ const serverCacheWithObjectLiteral = defineRule({
53254
52975
  if (!isNodeOfType(node.callee, "Identifier")) return;
53255
52976
  if (!cachedFunctionNames.has(node.callee.name)) return;
53256
52977
  const firstArg = node.arguments?.[0];
53257
- if (!firstArg || isNodeOfType(firstArg, "SpreadElement")) return;
53258
- if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
52978
+ if (!isNodeOfType(firstArg, "ObjectExpression")) return;
53259
52979
  context.report({
53260
52980
  node,
53261
52981
  message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.6-dev.81e6647",
3
+ "version": "0.7.6-dev.8528def",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",