oxlint-plugin-react-doctor 0.2.14-dev.9fc6f62 → 0.2.14-dev.b3c3aa9

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 +64 -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
@@ -13289,12 +13312,21 @@ const getRef = (analysis, identifier) => {
13289
13312
  for (const reference of scope.references) if (reference.identifier === identifier) return reference;
13290
13313
  return null;
13291
13314
  };
13315
+ const downstreamRefsCache = /* @__PURE__ */ new WeakMap();
13292
13316
  const getDownstreamRefs = (analysis, node) => {
13317
+ let perNode = downstreamRefsCache.get(analysis);
13318
+ if (!perNode) {
13319
+ perNode = /* @__PURE__ */ new WeakMap();
13320
+ downstreamRefsCache.set(analysis, perNode);
13321
+ }
13322
+ const cached = perNode.get(node);
13323
+ if (cached) return cached;
13293
13324
  const refs = [];
13294
13325
  for (const identifier of findDownstreamNodes(node, "Identifier")) {
13295
13326
  const ref = getRef(analysis, identifier);
13296
13327
  if (ref) refs.push(ref);
13297
13328
  }
13329
+ perNode.set(node, refs);
13298
13330
  return refs;
13299
13331
  };
13300
13332
  const getCallExpr = (ref, current = ref.identifier.parent) => {
@@ -25392,7 +25424,7 @@ const resolveRemovalMessageForCallee = (callee) => {
25392
25424
  const reactCompilerNoManualMemoization = defineRule({
25393
25425
  id: "react-compiler-no-manual-memoization",
25394
25426
  title: "Redundant manual memoization",
25395
- severity: "error",
25427
+ severity: "warn",
25396
25428
  requires: ["react-compiler"],
25397
25429
  recommendation: "Delete the `useMemo` / `useCallback` / `memo` call and use the plain value or component. React Compiler caches it for you.",
25398
25430
  create: (context) => ({ CallExpression(node) {
@@ -27524,6 +27556,34 @@ const isInsidePlatformOsWebBranch = (node) => {
27524
27556
  return false;
27525
27557
  };
27526
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
27527
27587
  //#region src/plugin/rules/react-native/rn-no-raw-text.ts
27528
27588
  const truncateText = (text) => text.length > 30 ? `${text.slice(0, 30)}...` : text;
27529
27589
  const isRawTextContent = (child) => {
@@ -27573,14 +27633,16 @@ const rnNoRawText = defineRule({
27573
27633
  recommendation: "Text outside a `<Text>` component crashes on React Native. Wrap it like `<Text>{value}</Text>`.",
27574
27634
  create: (context) => {
27575
27635
  let isDomComponentFile = false;
27636
+ let autoDetectedWrappers = /* @__PURE__ */ new Set();
27576
27637
  return {
27577
27638
  Program(programNode) {
27578
27639
  isDomComponentFile = hasDirective(programNode, "use dom");
27640
+ autoDetectedWrappers = collectTextWrapperComponents(programNode, isTextHandlingComponent);
27579
27641
  },
27580
27642
  JSXElement(node) {
27581
27643
  if (isDomComponentFile) return;
27582
27644
  const elementName = resolveTextBoundaryName(node.openingElement);
27583
- if (elementName && isTextHandlingComponent(elementName)) return;
27645
+ if (elementName && (isTextHandlingComponent(elementName) || autoDetectedWrappers.has(elementName))) return;
27584
27646
  if (isInsidePlatformOsWebBranch(node)) return;
27585
27647
  if (isTransparentTextWrapper(elementName) && isInsideTextHandlingComponent(node)) return;
27586
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.9fc6f62",
3
+ "version": "0.2.14-dev.b3c3aa9",
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",