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.
- package/dist/index.js +64 -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: "
|
|
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.
|
|
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",
|