react-doctor 0.0.32 → 0.0.34
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/README.md +0 -2
- package/dist/cli.js +226 -434
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +108 -79
- package/dist/index.js.map +1 -1
- package/dist/react-doctor-plugin.js +41 -10
- package/dist/react-doctor-plugin.js.map +1 -1
- package/package.json +4 -4
|
@@ -251,14 +251,30 @@ const LARGE_BLUR_THRESHOLD_PX = 10;
|
|
|
251
251
|
const BLUR_VALUE_PATTERN = /blur\((\d+(?:\.\d+)?)px\)/;
|
|
252
252
|
const ANIMATION_CALLBACK_NAMES = new Set(["requestAnimationFrame", "setInterval"]);
|
|
253
253
|
const RAW_TEXT_PREVIEW_MAX_CHARS = 30;
|
|
254
|
-
const REACT_NATIVE_TEXT_COMPONENTS = new Set([
|
|
255
|
-
|
|
254
|
+
const REACT_NATIVE_TEXT_COMPONENTS = new Set([
|
|
255
|
+
"Text",
|
|
256
|
+
"TextInput",
|
|
257
|
+
"Typography",
|
|
258
|
+
"Paragraph",
|
|
259
|
+
"Span",
|
|
260
|
+
"H1",
|
|
261
|
+
"H2",
|
|
262
|
+
"H3",
|
|
263
|
+
"H4",
|
|
264
|
+
"H5",
|
|
265
|
+
"H6"
|
|
266
|
+
]);
|
|
267
|
+
const REACT_NATIVE_TEXT_COMPONENT_KEYWORDS = new Set([
|
|
256
268
|
"Text",
|
|
257
269
|
"Title",
|
|
258
270
|
"Label",
|
|
259
271
|
"Heading",
|
|
260
272
|
"Caption",
|
|
261
|
-
"Subtitle"
|
|
273
|
+
"Subtitle",
|
|
274
|
+
"Typography",
|
|
275
|
+
"Paragraph",
|
|
276
|
+
"Description",
|
|
277
|
+
"Body"
|
|
262
278
|
]);
|
|
263
279
|
const DEPRECATED_RN_MODULE_REPLACEMENTS = {
|
|
264
280
|
AsyncStorage: "@react-native-async-storage/async-storage",
|
|
@@ -311,6 +327,7 @@ const walkAst = (node, visitor) => {
|
|
|
311
327
|
}
|
|
312
328
|
};
|
|
313
329
|
const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
|
|
330
|
+
const isSetterCall = (node) => node.type === "CallExpression" && node.callee?.type === "Identifier" && isSetterIdentifier(node.callee.name);
|
|
314
331
|
const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
|
|
315
332
|
const isMemberProperty = (node, propertyName) => node.type === "MemberExpression" && node.property?.type === "Identifier" && node.property.name === propertyName;
|
|
316
333
|
const getEffectCallback = (node) => {
|
|
@@ -326,7 +343,7 @@ const getCallbackStatements = (callback) => {
|
|
|
326
343
|
const countSetStateCalls = (node) => {
|
|
327
344
|
let setStateCallCount = 0;
|
|
328
345
|
walkAst(node, (child) => {
|
|
329
|
-
if (
|
|
346
|
+
if (isSetterCall(child)) setStateCallCount++;
|
|
330
347
|
});
|
|
331
348
|
return setStateCallCount;
|
|
332
349
|
};
|
|
@@ -345,7 +362,17 @@ const isSimpleExpression = (node) => {
|
|
|
345
362
|
};
|
|
346
363
|
const isComponentDeclaration = (node) => node.type === "FunctionDeclaration" && Boolean(node.id?.name) && isUppercaseName(node.id.name);
|
|
347
364
|
const isComponentAssignment = (node) => node.type === "VariableDeclarator" && node.id?.type === "Identifier" && isUppercaseName(node.id.name) && Boolean(node.init) && (node.init.type === "ArrowFunctionExpression" || node.init.type === "FunctionExpression");
|
|
348
|
-
const
|
|
365
|
+
const getCalleeName = (node) => {
|
|
366
|
+
if (node.callee?.type === "Identifier") return node.callee.name;
|
|
367
|
+
if (node.callee?.type === "MemberExpression" && node.callee.property?.type === "Identifier") return node.callee.property.name;
|
|
368
|
+
return null;
|
|
369
|
+
};
|
|
370
|
+
const isHookCall = (node, hookName) => {
|
|
371
|
+
if (node.type !== "CallExpression") return false;
|
|
372
|
+
const calleeName = getCalleeName(node);
|
|
373
|
+
if (!calleeName) return false;
|
|
374
|
+
return typeof hookName === "string" ? calleeName === hookName : hookName.has(calleeName);
|
|
375
|
+
};
|
|
349
376
|
const hasDirective = (programNode, directive) => Boolean(programNode.body?.some((statement) => statement.type === "ExpressionStatement" && statement.expression?.type === "Literal" && statement.expression.value === directive));
|
|
350
377
|
const hasUseServerDirective = (node) => {
|
|
351
378
|
if (node.body?.type !== "BlockStatement") return false;
|
|
@@ -1228,7 +1255,7 @@ const renderingHydrationNoFlicker = { create: (context) => ({ CallExpression(nod
|
|
|
1228
1255
|
const bodyStatements = callback.body?.type === "BlockStatement" ? callback.body.body : [callback.body];
|
|
1229
1256
|
if (!bodyStatements || bodyStatements.length !== 1) return;
|
|
1230
1257
|
const soleStatement = bodyStatements[0];
|
|
1231
|
-
if (soleStatement?.type === "ExpressionStatement" &&
|
|
1258
|
+
if (soleStatement?.type === "ExpressionStatement" && isSetterCall(soleStatement.expression)) context.report({
|
|
1232
1259
|
node,
|
|
1233
1260
|
message: "useEffect(setState, []) on mount causes a flash — consider useSyncExternalStore or suppressHydrationWarning"
|
|
1234
1261
|
});
|
|
@@ -1262,7 +1289,7 @@ const getRawTextDescription = (child) => {
|
|
|
1262
1289
|
};
|
|
1263
1290
|
const isTextHandlingComponent = (elementName) => {
|
|
1264
1291
|
if (REACT_NATIVE_TEXT_COMPONENTS.has(elementName)) return true;
|
|
1265
|
-
return [...
|
|
1292
|
+
return [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS].some((keyword) => elementName.includes(keyword));
|
|
1266
1293
|
};
|
|
1267
1294
|
const rnNoRawText = { create: (context) => {
|
|
1268
1295
|
let isDomComponentFile = false;
|
|
@@ -1507,7 +1534,10 @@ const noDerivedStateEffect = { create: (context) => ({ CallExpression(node) {
|
|
|
1507
1534
|
if (dependencyNames.size === 0) return;
|
|
1508
1535
|
const statements = getCallbackStatements(callback);
|
|
1509
1536
|
if (statements.length === 0) return;
|
|
1510
|
-
if (!statements.every((statement) =>
|
|
1537
|
+
if (!statements.every((statement) => {
|
|
1538
|
+
if (statement.type !== "ExpressionStatement") return false;
|
|
1539
|
+
return isSetterCall(statement.expression);
|
|
1540
|
+
})) return;
|
|
1511
1541
|
let allArgumentsDeriveFromDeps = true;
|
|
1512
1542
|
let hasAnyDependencyReference = false;
|
|
1513
1543
|
for (const statement of statements) {
|
|
@@ -1621,12 +1651,13 @@ const rerenderLazyStateInit = { create: (context) => ({ CallExpression(node) {
|
|
|
1621
1651
|
});
|
|
1622
1652
|
} }) };
|
|
1623
1653
|
const rerenderFunctionalSetstate = { create: (context) => ({ CallExpression(node) {
|
|
1624
|
-
if (
|
|
1654
|
+
if (!isSetterCall(node)) return;
|
|
1625
1655
|
if (!node.arguments?.length) return;
|
|
1656
|
+
const calleeName = node.callee.name;
|
|
1626
1657
|
const argument = node.arguments[0];
|
|
1627
1658
|
if (argument.type === "BinaryExpression" && (argument.operator === "+" || argument.operator === "-") && argument.left?.type === "Identifier") context.report({
|
|
1628
1659
|
node,
|
|
1629
|
-
message: `${
|
|
1660
|
+
message: `${calleeName}(${argument.left.name} ${argument.operator} ...) — use functional update to avoid stale closures`
|
|
1630
1661
|
});
|
|
1631
1662
|
} }) };
|
|
1632
1663
|
const rerenderDependencies = { create: (context) => ({ CallExpression(node) {
|