eslint-plugin-react-x 4.0.2-beta.5 → 4.0.3-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.
- package/dist/index.js +70 -21
- 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.
|
|
150
|
+
var version = "4.0.3-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
|
-
|
|
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
|
-
|
|
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 },
|
|
@@ -2952,8 +3004,8 @@ const RULE_NAME$27 = "no-nested-lazy-component-declarations";
|
|
|
2952
3004
|
var no_nested_lazy_component_declarations_default = createRule({
|
|
2953
3005
|
meta: {
|
|
2954
3006
|
type: "problem",
|
|
2955
|
-
docs: { description: "Disallows nesting lazy component declarations inside other components." },
|
|
2956
|
-
messages: { default: "Do not declare lazy components inside other components. Instead, always declare them at the top level of your module." },
|
|
3007
|
+
docs: { description: "Disallows nesting lazy component declarations inside other components or hooks." },
|
|
3008
|
+
messages: { default: "Do not declare lazy components inside other components or hooks. Instead, always declare them at the top level of your module." },
|
|
2957
3009
|
schema: []
|
|
2958
3010
|
},
|
|
2959
3011
|
name: RULE_NAME$27,
|
|
@@ -2961,25 +3013,22 @@ var no_nested_lazy_component_declarations_default = createRule({
|
|
|
2961
3013
|
defaultOptions: []
|
|
2962
3014
|
});
|
|
2963
3015
|
function create$27(context) {
|
|
2964
|
-
const
|
|
2965
|
-
const
|
|
2966
|
-
const
|
|
2967
|
-
const
|
|
2968
|
-
return defineRuleListener(
|
|
3016
|
+
const fCollector = core.getComponentCollector(context);
|
|
3017
|
+
const cCollector = core.getComponentCollectorLegacy(context);
|
|
3018
|
+
const hCollector = core.getHookCollector(context);
|
|
3019
|
+
const lazyCalls = /* @__PURE__ */ new Set();
|
|
3020
|
+
return defineRuleListener(fCollector.visitor, cCollector.visitor, hCollector.visitor, {
|
|
2969
3021
|
ImportExpression(node) {
|
|
2970
3022
|
const lazyCall = ast.findParent(node, (n) => core.isLazyCall(context, n));
|
|
2971
|
-
if (lazyCall != null)
|
|
3023
|
+
if (lazyCall != null) lazyCalls.add(lazyCall);
|
|
2972
3024
|
},
|
|
2973
3025
|
"Program:exit"(program) {
|
|
2974
|
-
const
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
if (ast.isClass(n)) return classComponents.some((c) => c.node === n);
|
|
2981
|
-
return false;
|
|
2982
|
-
}) != null) context.report({
|
|
3026
|
+
const significantParents = [
|
|
3027
|
+
...fCollector.api.getAllComponents(program),
|
|
3028
|
+
...hCollector.api.getAllHooks(program),
|
|
3029
|
+
...cCollector.api.getAllComponents(program)
|
|
3030
|
+
];
|
|
3031
|
+
for (const lazy of lazyCalls) if (ast.findParent(lazy, (n) => significantParents.some((p) => p.node === n))) context.report({
|
|
2983
3032
|
messageId: "default",
|
|
2984
3033
|
node: lazy
|
|
2985
3034
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-x",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3-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.
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@eslint-react/
|
|
51
|
-
"@eslint-react/
|
|
52
|
-
"@eslint-react/
|
|
48
|
+
"@eslint-react/ast": "4.0.3-beta.0",
|
|
49
|
+
"@eslint-react/core": "4.0.3-beta.0",
|
|
50
|
+
"@eslint-react/shared": "4.0.3-beta.0",
|
|
51
|
+
"@eslint-react/jsx": "4.0.3-beta.0",
|
|
52
|
+
"@eslint-react/var": "4.0.3-beta.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/react": "^19.2.14",
|