eslint-plugin-react-x 4.0.2-beta.6 → 4.1.0-beta.0

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.
Files changed (2) hide show
  1. package/dist/index.js +56 -4
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -2,11 +2,11 @@ import { DEFAULT_ESLINT_REACT_SETTINGS, IMPURE_CTORS, IMPURE_FUNCS, WEBSITE_URL,
2
2
  import * as ast from "@eslint-react/ast";
3
3
  import * as core from "@eslint-react/core";
4
4
  import { isUseRefCall } from "@eslint-react/core";
5
+ import { AST_NODE_TYPES } from "@typescript-eslint/types";
5
6
  import { ESLintUtils } from "@typescript-eslint/utils";
6
7
  import { P, isMatching, match } from "ts-pattern";
7
8
  import ts from "typescript";
8
9
  import { JsxDetectionHint, findParentAttribute, getElementFullType, hasAttribute, isJsxLike } from "@eslint-react/jsx";
9
- import { AST_NODE_TYPES } from "@typescript-eslint/types";
10
10
  import { computeObjectType, findEnclosingAssignmentTarget, isAssignmentTargetEqual, resolve } from "@eslint-react/var";
11
11
  import { DefinitionType } from "@typescript-eslint/scope-manager";
12
12
  import { findVariable, getStaticValue, isIdentifier, isVariableDeclarator } from "@typescript-eslint/utils/ast-utils";
@@ -147,7 +147,7 @@ const rules$7 = {
147
147
  //#endregion
148
148
  //#region package.json
149
149
  var name$6 = "eslint-plugin-react-x";
150
- var version = "4.0.2-beta.6";
150
+ var version = "4.1.0-beta.0";
151
151
 
152
152
  //#endregion
153
153
  //#region src/utils/create-rule.ts
@@ -319,6 +319,51 @@ var component_hook_factories_default = createRule({
319
319
  create: create$56,
320
320
  defaultOptions: []
321
321
  });
322
+ /**
323
+ * Check if a function parameter name looks like a React component (PascalCase).
324
+ */
325
+ function isComponentLikeParamName(name) {
326
+ return /^[A-Z]/.test(name);
327
+ }
328
+ /**
329
+ * Check if a function parameter has a type annotation that looks like a React component type.
330
+ * Matches types like ComponentType, React.ComponentType, FC, React.FC, etc.
331
+ */
332
+ function hasComponentTypeAnnotation(param) {
333
+ if (param.type !== AST_NODE_TYPES.Identifier || param.typeAnnotation == null) return false;
334
+ const annotation = param.typeAnnotation.typeAnnotation;
335
+ if (annotation.type === AST_NODE_TYPES.TSTypeReference) return isComponentTypeName(annotation.typeName);
336
+ return false;
337
+ }
338
+ /**
339
+ * Check if a type name refers to a known React component type.
340
+ */
341
+ function isComponentTypeName(typeName) {
342
+ if (typeName.type === AST_NODE_TYPES.Identifier) return /^(ComponentType|FC|ComponentClass|FunctionComponent|Component)$/.test(typeName.name);
343
+ if (typeName.type === AST_NODE_TYPES.TSQualifiedName) {
344
+ if (typeName.left.type === AST_NODE_TYPES.Identifier && typeName.left.name === "React") return isComponentTypeName(typeName.right);
345
+ }
346
+ return false;
347
+ }
348
+ /**
349
+ * Heuristically check if a function is a Higher Order Component (HOC) based on its parameters.
350
+ * Considers a function an HOC if it takes a parameter that looks like a React component
351
+ * (by name or type annotation). This does not validate that the function actually returns
352
+ * a React component.
353
+ */
354
+ function isHigherOrderComponent(fn) {
355
+ return fn.params.some((param) => {
356
+ if (param.type === AST_NODE_TYPES.Identifier && isComponentLikeParamName(param.name)) return true;
357
+ if (hasComponentTypeAnnotation(param)) return true;
358
+ return false;
359
+ });
360
+ }
361
+ /**
362
+ * Check if a node is inside a test mock callback (vi.mock or jest.mock).
363
+ */
364
+ function isInsideTestMockCallback(node) {
365
+ return ast.findParent(node, ast.isTestMockCallback) != null;
366
+ }
322
367
  function create$56(context) {
323
368
  const hint = core.ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | core.ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | core.ComponentDetectionHint.DoNotIncludeJsxWithNullValue | core.ComponentDetectionHint.DoNotIncludeJsxWithStringValue | core.ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx | core.ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | core.ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | core.ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | core.ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback;
324
369
  const fCollector = core.getComponentCollector(context, { hint });
@@ -331,7 +376,10 @@ function create$56(context) {
331
376
  const hooks = [...hCollector.api.getAllHooks(program)];
332
377
  for (const { name, node } of fComponents) {
333
378
  if (name == null) continue;
334
- if (ast.findParent(node, ast.isFunction) == null) continue;
379
+ const parentFn = ast.findParent(node, ast.isFunction);
380
+ if (parentFn == null) continue;
381
+ if (isInsideTestMockCallback(node)) continue;
382
+ if (isHigherOrderComponent(parentFn)) continue;
335
383
  if (reported.has(node)) continue;
336
384
  context.report({
337
385
  data: { name },
@@ -341,7 +389,10 @@ function create$56(context) {
341
389
  reported.add(node);
342
390
  }
343
391
  for (const { name = "unknown", node } of cComponents) {
344
- if (ast.findParent(node, ast.isFunction) == null) continue;
392
+ const parentFn = ast.findParent(node, ast.isFunction);
393
+ if (parentFn == null) continue;
394
+ if (isInsideTestMockCallback(node)) continue;
395
+ if (isHigherOrderComponent(parentFn)) continue;
345
396
  context.report({
346
397
  data: { name },
347
398
  messageId: "component",
@@ -350,6 +401,7 @@ function create$56(context) {
350
401
  }
351
402
  for (const { name, node } of hooks) {
352
403
  if (ast.findParent(node, ast.isFunction) == null) continue;
404
+ if (isInsideTestMockCallback(node)) continue;
353
405
  if (reported.has(node)) continue;
354
406
  context.report({
355
407
  data: { name },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "4.0.2-beta.6",
3
+ "version": "4.1.0-beta.0",
4
4
  "description": "A set of composable ESLint rules for libraries and frameworks that use React as a UI runtime.",
5
5
  "keywords": [
6
6
  "react",
@@ -45,11 +45,11 @@
45
45
  "string-ts": "^2.3.1",
46
46
  "ts-api-utils": "^2.5.0",
47
47
  "ts-pattern": "^5.9.0",
48
- "@eslint-react/ast": "4.0.2-beta.6",
49
- "@eslint-react/core": "4.0.2-beta.6",
50
- "@eslint-react/shared": "4.0.2-beta.6",
51
- "@eslint-react/jsx": "4.0.2-beta.6",
52
- "@eslint-react/var": "4.0.2-beta.6"
48
+ "@eslint-react/ast": "4.1.0-beta.0",
49
+ "@eslint-react/core": "4.1.0-beta.0",
50
+ "@eslint-react/shared": "4.1.0-beta.0",
51
+ "@eslint-react/jsx": "4.1.0-beta.0",
52
+ "@eslint-react/var": "4.1.0-beta.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/react": "^19.2.14",