oxlint-plugin-react-doctor 0.7.5-dev.18e8717 → 0.7.5-dev.21da48f
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 +88 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5178,8 +5178,10 @@ const STORAGE_GLOBALS = new Set([
|
|
|
5178
5178
|
const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
|
|
5179
5179
|
const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
|
|
5180
5180
|
const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
|
|
5181
|
+
const PRODUCT_API_KEY_RECORDS_PATTERN = /(?:^|[._:-])(?:created|saved|integration|mailing)[-_]?api[-_]?keys$/i;
|
|
5181
5182
|
const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
|
|
5182
5183
|
const isAuthCredentialKey = (key) => {
|
|
5184
|
+
if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
|
|
5183
5185
|
if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
|
|
5184
5186
|
if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
|
|
5185
5187
|
if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
|
|
@@ -23998,6 +24000,26 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
|
|
|
23998
24000
|
"dialog",
|
|
23999
24001
|
"canvas"
|
|
24000
24002
|
]);
|
|
24003
|
+
const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
|
|
24004
|
+
"autofocus",
|
|
24005
|
+
"contenteditable",
|
|
24006
|
+
"draggable",
|
|
24007
|
+
"tabindex"
|
|
24008
|
+
]);
|
|
24009
|
+
const isStaticallyFalseAttributeValue = (attribute) => {
|
|
24010
|
+
if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
|
|
24011
|
+
const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
|
|
24012
|
+
return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
|
|
24013
|
+
};
|
|
24014
|
+
const hasStatefulHtmlAttribute = (openingElement) => {
|
|
24015
|
+
if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
24016
|
+
return openingElement.attributes.some((attribute) => {
|
|
24017
|
+
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
24018
|
+
const attributeName = attribute.name.name.toLowerCase();
|
|
24019
|
+
if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
|
|
24020
|
+
return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
|
|
24021
|
+
});
|
|
24022
|
+
};
|
|
24001
24023
|
const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
|
|
24002
24024
|
const rootIdentifierNameOf = (expression) => {
|
|
24003
24025
|
let object = expression;
|
|
@@ -24015,7 +24037,8 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24015
24037
|
budget -= 1;
|
|
24016
24038
|
const node = stack.pop();
|
|
24017
24039
|
if (isNodeOfType(node, "JSXElement")) {
|
|
24018
|
-
const
|
|
24040
|
+
const opening = node.openingElement;
|
|
24041
|
+
const name = opening.name;
|
|
24019
24042
|
if (name && isNodeOfType(name, "JSXIdentifier")) {
|
|
24020
24043
|
const tagName = name.name;
|
|
24021
24044
|
const firstChar = tagName.charCodeAt(0);
|
|
@@ -24023,6 +24046,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24023
24046
|
if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
|
|
24024
24047
|
}
|
|
24025
24048
|
if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
|
|
24049
|
+
if (hasStatefulHtmlAttribute(opening)) return true;
|
|
24026
24050
|
const children = node.children ?? [];
|
|
24027
24051
|
for (const child of children) stack.push(child);
|
|
24028
24052
|
continue;
|
|
@@ -24582,6 +24606,14 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
|
|
|
24582
24606
|
}
|
|
24583
24607
|
return false;
|
|
24584
24608
|
};
|
|
24609
|
+
const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
24610
|
+
const referencedItemNames = /* @__PURE__ */ new Set();
|
|
24611
|
+
for (const expression of template.expressions ?? []) {
|
|
24612
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
24613
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
|
|
24614
|
+
}
|
|
24615
|
+
return referencedItemNames;
|
|
24616
|
+
};
|
|
24585
24617
|
const forLoopTestReadsDataLength = (test) => {
|
|
24586
24618
|
let didFindLengthRead = false;
|
|
24587
24619
|
walkAst(test, (child) => {
|
|
@@ -24718,9 +24750,11 @@ const noArrayIndexAsKey = defineRule({
|
|
|
24718
24750
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
24719
24751
|
const jsxElement = openingElement.parent;
|
|
24720
24752
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
24753
|
+
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
24754
|
+
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
|
|
24721
24755
|
if (!containsStatefulDescendant(jsxElement, {
|
|
24722
|
-
memberRootNames:
|
|
24723
|
-
bareIdentifierNames: derivedNames
|
|
24756
|
+
memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
|
|
24757
|
+
bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
|
|
24724
24758
|
})) return;
|
|
24725
24759
|
}
|
|
24726
24760
|
}
|
|
@@ -35017,6 +35051,30 @@ const guardsRenderShape = (comparison) => {
|
|
|
35017
35051
|
}
|
|
35018
35052
|
return false;
|
|
35019
35053
|
};
|
|
35054
|
+
const isPropsChildrenLength = (node, scopes) => {
|
|
35055
|
+
const unwrappedNode = stripParenExpression(node);
|
|
35056
|
+
return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
|
|
35057
|
+
};
|
|
35058
|
+
const isLargeTextLengthComparison = (node, scopes) => {
|
|
35059
|
+
const unwrappedNode = stripParenExpression(node);
|
|
35060
|
+
if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
|
|
35061
|
+
const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
|
|
35062
|
+
const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
|
|
35063
|
+
const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
|
|
35064
|
+
if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
|
|
35065
|
+
if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
|
|
35066
|
+
return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
|
|
35067
|
+
};
|
|
35068
|
+
const isLargeStringOptimizationGuard = (comparison, scopes) => {
|
|
35069
|
+
let current = findTransparentExpressionRoot(comparison);
|
|
35070
|
+
while (current.parent) {
|
|
35071
|
+
const parent = current.parent;
|
|
35072
|
+
if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
|
|
35073
|
+
if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
|
|
35074
|
+
current = findTransparentExpressionRoot(parent);
|
|
35075
|
+
}
|
|
35076
|
+
return false;
|
|
35077
|
+
};
|
|
35020
35078
|
const noPolymorphicChildren = defineRule({
|
|
35021
35079
|
id: "no-polymorphic-children",
|
|
35022
35080
|
title: "Children type checked at runtime",
|
|
@@ -35030,6 +35088,7 @@ const noPolymorphicChildren = defineRule({
|
|
|
35030
35088
|
const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
|
|
35031
35089
|
if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
|
|
35032
35090
|
if (!guardsRenderShape(node)) return;
|
|
35091
|
+
if (isLargeStringOptimizationGuard(node, context.scopes)) return;
|
|
35033
35092
|
context.report({
|
|
35034
35093
|
node,
|
|
35035
35094
|
message: "Your users hit inconsistent behavior because `typeof children === \"string\"` makes this component switch on what callers pass, so add clear subcomponents like `<Button.Text>` instead."
|
|
@@ -35247,6 +35306,27 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
35247
35306
|
}
|
|
35248
35307
|
return false;
|
|
35249
35308
|
};
|
|
35309
|
+
const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
35310
|
+
if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
|
|
35311
|
+
if (!analysis) return true;
|
|
35312
|
+
const reference = getRef(analysis, element);
|
|
35313
|
+
if (!reference) return true;
|
|
35314
|
+
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
35315
|
+
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
35316
|
+
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
35317
|
+
};
|
|
35318
|
+
const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
35319
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
35320
|
+
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
|
|
35321
|
+
const receiver = stripParenExpression(callee.object);
|
|
35322
|
+
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
35323
|
+
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
35324
|
+
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
35325
|
+
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
35326
|
+
const callbackArgument = binding.initializer.arguments?.[0];
|
|
35327
|
+
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
35328
|
+
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
35329
|
+
};
|
|
35250
35330
|
const noPropCallbackInEffect = defineRule({
|
|
35251
35331
|
id: "no-prop-callback-in-effect",
|
|
35252
35332
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -35263,11 +35343,11 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35263
35343
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
35264
35344
|
const depsNode = node.arguments[1];
|
|
35265
35345
|
if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
|
|
35266
|
-
const
|
|
35346
|
+
const analysis = getProgramAnalysis(node);
|
|
35347
|
+
const stateLikeDeps = (depsNode.elements ?? []).filter((element) => element && isStateLikeDependency(analysis, element, propStackTracker.isPropName));
|
|
35267
35348
|
if (stateLikeDeps.length === 0) return;
|
|
35268
35349
|
if (isRefLatchGuardedEffect(callback.body)) return;
|
|
35269
35350
|
if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
|
|
35270
|
-
const analysis = getProgramAnalysis(node);
|
|
35271
35351
|
if (analysis) {
|
|
35272
35352
|
const stateLikeDepRefs = [];
|
|
35273
35353
|
for (const element of stateLikeDeps) {
|
|
@@ -35279,9 +35359,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35279
35359
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
35280
35360
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
35281
35361
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35282
|
-
|
|
35283
|
-
const calleeName =
|
|
35284
|
-
if (!
|
|
35362
|
+
const directCallee = stripParenExpression(child.callee);
|
|
35363
|
+
const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
|
|
35364
|
+
if (!calleeName) return;
|
|
35285
35365
|
if (!isResultDiscardedCall(child)) return;
|
|
35286
35366
|
if (reportedNodes.has(child)) return;
|
|
35287
35367
|
reportedNodes.add(child);
|