react-doctor 0.0.31 → 0.0.33

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.
@@ -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(["Text", "TextInput"]);
255
- const REACT_NATIVE_TEXT_COMPONENT_SUFFIXES = new Set([
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 (child.type === "CallExpression" && child.callee?.type === "Identifier" && isSetterIdentifier(child.callee.name)) setStateCallCount++;
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 isHookCall = (node, hookName) => node.type === "CallExpression" && node.callee?.type === "Identifier" && (typeof hookName === "string" ? node.callee.name === hookName : hookName.has(node.callee.name));
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" && soleStatement.expression?.type === "CallExpression" && soleStatement.expression.callee?.type === "Identifier" && SETTER_PATTERN.test(soleStatement.expression.callee.name)) context.report({
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 [...REACT_NATIVE_TEXT_COMPONENT_SUFFIXES].some((suffix) => elementName.endsWith(suffix));
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) => statement.type === "ExpressionStatement" && statement.expression?.type === "CallExpression" && statement.expression.callee?.type === "Identifier" && isSetterIdentifier(statement.expression.callee.name))) return;
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 (node.callee?.type !== "Identifier" || !isSetterIdentifier(node.callee.name)) return;
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: `${node.callee.name}(${argument.left.name} ${argument.operator} ...) — use functional update to avoid stale closures`
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) {