oxlint-plugin-react-doctor 0.2.14-dev.8b313ba → 0.2.14-dev.938376

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 +55 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9111,6 +9111,25 @@ const findEnclosingIteratorContext = (jsxNode) => {
9111
9111
  }
9112
9112
  return null;
9113
9113
  };
9114
+ const resolveIterationItemName = (callExpression) => {
9115
+ if (!isNodeOfType(callExpression, "CallExpression")) return null;
9116
+ const callee = callExpression.callee;
9117
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
9118
+ if (!isNodeOfType(callee.property, "Identifier")) return null;
9119
+ const targetArgIndex = callee.property.name === "from" ? 1 : 0;
9120
+ const callback = callExpression.arguments[targetArgIndex];
9121
+ if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return null;
9122
+ const firstParam = callback.params[0];
9123
+ return firstParam && isNodeOfType(firstParam, "Identifier") ? firstParam.name : null;
9124
+ };
9125
+ const spreadsIterationItem = (openingElement, iterationItemName) => {
9126
+ for (const attribute of openingElement.attributes) {
9127
+ if (!isNodeOfType(attribute, "JSXSpreadAttribute")) continue;
9128
+ const argument = attribute.argument;
9129
+ if (isNodeOfType(argument, "Identifier") && argument.name === iterationItemName) return true;
9130
+ }
9131
+ return false;
9132
+ };
9114
9133
  const isWithinChildrenToArray = (jsxNode) => {
9115
9134
  let current = jsxNode.parent;
9116
9135
  while (current) {
@@ -9209,6 +9228,10 @@ const jsxKey = defineRule({
9209
9228
  if (!enclosingContext) return;
9210
9229
  if (isWithinChildrenToArray(node)) return;
9211
9230
  if (hasJsxKeyAttribute(openingElement)) return;
9231
+ if (enclosingContext.kind === "iterator") {
9232
+ const iterationItemName = resolveIterationItemName(enclosingContext.callExpression);
9233
+ if (iterationItemName && spreadsIterationItem(openingElement, iterationItemName)) return;
9234
+ }
9212
9235
  context.report({
9213
9236
  node: openingElement,
9214
9237
  message: enclosingContext.kind === "array" ? MISSING_KEY_ARRAY : MISSING_KEY_ITERATOR
@@ -25401,7 +25424,7 @@ const resolveRemovalMessageForCallee = (callee) => {
25401
25424
  const reactCompilerNoManualMemoization = defineRule({
25402
25425
  id: "react-compiler-no-manual-memoization",
25403
25426
  title: "Redundant manual memoization",
25404
- severity: "error",
25427
+ severity: "warn",
25405
25428
  requires: ["react-compiler"],
25406
25429
  recommendation: "Delete the `useMemo` / `useCallback` / `memo` call and use the plain value or component. React Compiler caches it for you.",
25407
25430
  create: (context) => ({ CallExpression(node) {
@@ -27533,6 +27556,34 @@ const isInsidePlatformOsWebBranch = (node) => {
27533
27556
  return false;
27534
27557
  };
27535
27558
  //#endregion
27559
+ //#region src/plugin/rules/react-native/utils/collect-text-wrapper-components.ts
27560
+ const isFunctionNode = (node) => isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration");
27561
+ const resolveReturnedRootElementName = (functionNode) => {
27562
+ const { body } = functionNode;
27563
+ if (!body) return null;
27564
+ if (!isNodeOfType(body, "BlockStatement")) return isNodeOfType(body, "JSXElement") ? resolveJsxElementName(body.openingElement) : null;
27565
+ for (const statement of body.body) {
27566
+ if (!isNodeOfType(statement, "ReturnStatement")) continue;
27567
+ const argument = statement.argument;
27568
+ if (argument && isNodeOfType(argument, "JSXElement")) return resolveJsxElementName(argument.openingElement);
27569
+ }
27570
+ return null;
27571
+ };
27572
+ const recordWrapperFromDeclaration = (componentName, functionNode, isTextHandlingRoot, wrappers) => {
27573
+ if (!componentName || !isReactComponentName(componentName)) return;
27574
+ if (!functionNode || !isFunctionNode(functionNode)) return;
27575
+ const rootName = resolveReturnedRootElementName(functionNode);
27576
+ if (rootName && isTextHandlingRoot(rootName)) wrappers.add(componentName);
27577
+ };
27578
+ const collectTextWrapperComponents = (programNode, isTextHandlingRoot) => {
27579
+ const wrappers = /* @__PURE__ */ new Set();
27580
+ walkAst(programNode, (node) => {
27581
+ if (isNodeOfType(node, "VariableDeclarator")) recordWrapperFromDeclaration(node.id && isNodeOfType(node.id, "Identifier") ? node.id.name : null, node.init, isTextHandlingRoot, wrappers);
27582
+ else if (isNodeOfType(node, "FunctionDeclaration")) recordWrapperFromDeclaration(node.id && isNodeOfType(node.id, "Identifier") ? node.id.name : null, node, isTextHandlingRoot, wrappers);
27583
+ });
27584
+ return wrappers;
27585
+ };
27586
+ //#endregion
27536
27587
  //#region src/plugin/rules/react-native/rn-no-raw-text.ts
27537
27588
  const truncateText = (text) => text.length > 30 ? `${text.slice(0, 30)}...` : text;
27538
27589
  const isRawTextContent = (child) => {
@@ -27582,14 +27633,16 @@ const rnNoRawText = defineRule({
27582
27633
  recommendation: "Text outside a `<Text>` component crashes on React Native. Wrap it like `<Text>{value}</Text>`.",
27583
27634
  create: (context) => {
27584
27635
  let isDomComponentFile = false;
27636
+ let autoDetectedWrappers = /* @__PURE__ */ new Set();
27585
27637
  return {
27586
27638
  Program(programNode) {
27587
27639
  isDomComponentFile = hasDirective(programNode, "use dom");
27640
+ autoDetectedWrappers = collectTextWrapperComponents(programNode, isTextHandlingComponent);
27588
27641
  },
27589
27642
  JSXElement(node) {
27590
27643
  if (isDomComponentFile) return;
27591
27644
  const elementName = resolveTextBoundaryName(node.openingElement);
27592
- if (elementName && isTextHandlingComponent(elementName)) return;
27645
+ if (elementName && (isTextHandlingComponent(elementName) || autoDetectedWrappers.has(elementName))) return;
27593
27646
  if (isInsidePlatformOsWebBranch(node)) return;
27594
27647
  if (isTransparentTextWrapper(elementName) && isInsideTextHandlingComponent(node)) return;
27595
27648
  for (const child of node.children ?? []) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.2.14-dev.8b313ba",
3
+ "version": "0.2.14-dev.0938376",
4
4
  "description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",