oxlint-plugin-react-doctor 0.2.14-dev.8b313ba → 0.2.14-dev.9777f1a
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 +55 -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: "
|
|
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.
|
|
3
|
+
"version": "0.2.14-dev.9777f1a",
|
|
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",
|