Skip to content

Commit 9c2d345

Browse files
committed
refactor: simplify class components rules implementation
1 parent 3b48a6c commit 9c2d345

File tree

4 files changed

+92
-120
lines changed

4 files changed

+92
-120
lines changed
Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { NodeType, type TSESTreeClass } from "@eslint-react/ast";
2-
import { isClassComponent } from "@eslint-react/core";
3-
import { MutList } from "@eslint-react/tools";
1+
import { isOneOf, NodeType } from "@eslint-react/ast";
2+
import { componentCollectorLegacy } from "@eslint-react/core";
3+
import { E } from "@eslint-react/tools";
44
import type { TSESTree } from "@typescript-eslint/utils";
55
import { ESLintUtils } from "@typescript-eslint/utils";
66
import type { ConstantCase } from "string-ts";
@@ -11,6 +11,12 @@ export const RULE_NAME = "no-component-will-mount";
1111

1212
export type MessageID = ConstantCase<typeof RULE_NAME>;
1313

14+
function isComponentWillMount(node: TSESTree.ClassElement) {
15+
return isOneOf([NodeType.MethodDefinition, NodeType.PropertyDefinition])(node)
16+
&& node.key.type === NodeType.Identifier
17+
&& node.key.name === "componentWillMount";
18+
}
19+
1420
export default createRule<[], MessageID>({
1521
name: RULE_NAME,
1622
meta: {
@@ -27,45 +33,30 @@ export default createRule<[], MessageID>({
2733
},
2834
defaultOptions: [],
2935
create(context) {
30-
const classStack = MutList.make<[TSESTreeClass, boolean]>();
31-
const onClassEnter = (node: TSESTreeClass) => {
32-
MutList.append(classStack, [
33-
node,
34-
isClassComponent(node, context),
35-
]);
36-
};
37-
const onClassExit = () => MutList.pop(classStack);
38-
39-
function checkPropertyOrMethod(node: TSESTree.MethodDefinition | TSESTree.PropertyDefinition) {
40-
if (node.key.type !== NodeType.Identifier || node.key.name !== "componentWillMount") {
41-
return;
42-
}
43-
44-
const [classNode, isPureClassComponent] = MutList.tail(classStack) ?? [];
45-
46-
if (!classNode || !isPureClassComponent) {
47-
return;
48-
}
49-
50-
context.report({
51-
node,
52-
messageId: "NO_COMPONENT_WILL_MOUNT",
53-
});
54-
}
36+
const { ctx, listeners } = componentCollectorLegacy(context);
5537

5638
return {
57-
ClassDeclaration: onClassEnter,
58-
"ClassDeclaration:exit": onClassExit,
59-
ClassExpression: onClassEnter,
60-
"ClassExpression:exit": onClassExit,
61-
MethodDefinition(node) {
62-
if (node.kind !== "method") {
39+
...listeners,
40+
"Program:exit"() {
41+
const maybeComponents = ctx.getAllComponents();
42+
if (E.isLeft(maybeComponents)) {
6343
return;
6444
}
65-
66-
checkPropertyOrMethod(node);
45+
const components = maybeComponents.right;
46+
47+
for (const { node: component } of components.values()) {
48+
const { body } = component.body;
49+
50+
for (const member of body) {
51+
if (isComponentWillMount(member)) {
52+
context.report({
53+
messageId: "NO_COMPONENT_WILL_MOUNT",
54+
node: member,
55+
});
56+
}
57+
}
58+
}
6759
},
68-
PropertyDefinition: checkPropertyOrMethod,
6960
};
7061
},
7162
});
Lines changed: 34 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { getClassIdentifier, NodeType, type TSESTreeClass } from "@eslint-react/ast";
2-
import { isPureComponent } from "@eslint-react/core";
3-
import { F, MutList, O } from "@eslint-react/tools";
1+
import { isOneOf, NodeType } from "@eslint-react/ast";
2+
import { componentCollectorLegacy, ExRClassComponentFlag } from "@eslint-react/core";
3+
import { E, O } from "@eslint-react/tools";
44
import type { TSESTree } from "@typescript-eslint/utils";
55
import { ESLintUtils } from "@typescript-eslint/utils";
66
import type { ConstantCase } from "string-ts";
@@ -11,6 +11,12 @@ export const RULE_NAME = "no-redundant-should-component-update";
1111

1212
export type MessageID = ConstantCase<typeof RULE_NAME>;
1313

14+
function isShouldComponentUpdate(node: TSESTree.ClassElement) {
15+
return isOneOf([NodeType.MethodDefinition, NodeType.PropertyDefinition])(node)
16+
&& node.key.type === NodeType.Identifier
17+
&& node.key.name === "shouldComponentUpdate";
18+
}
19+
1420
export default createRule<[], MessageID>({
1521
name: RULE_NAME,
1622
meta: {
@@ -28,54 +34,37 @@ export default createRule<[], MessageID>({
2834
},
2935
defaultOptions: [],
3036
create(context) {
31-
const classStack = MutList.make<[TSESTreeClass, boolean]>();
32-
const onClassEnter = (node: TSESTreeClass) => {
33-
MutList.append(classStack, [
34-
node,
35-
isPureComponent(node, context),
36-
]);
37-
};
38-
const onClassExit = () => MutList.pop(classStack);
39-
40-
function checkPropertyOrMethod(node: TSESTree.MethodDefinition | TSESTree.PropertyDefinition) {
41-
if (node.key.type !== NodeType.Identifier || node.key.name !== "shouldComponentUpdate") {
42-
return;
43-
}
44-
45-
const [classNode, isPureClassComponent] = MutList.tail(classStack) ?? [];
46-
47-
if (!classNode || !isPureClassComponent) {
48-
return;
49-
}
50-
51-
const componentName = F.pipe(
52-
getClassIdentifier(classNode),
53-
O.map(id => id.name),
54-
O.getOrElse(() => "PureComponent"),
55-
);
56-
57-
context.report({
58-
node,
59-
messageId: "NO_REDUNDANT_SHOULD_COMPONENT_UPDATE",
60-
data: {
61-
componentName,
62-
},
63-
});
64-
}
37+
const { ctx, listeners } = componentCollectorLegacy(context);
6538

6639
return {
67-
ClassDeclaration: onClassEnter,
68-
"ClassDeclaration:exit": onClassExit,
69-
ClassExpression: onClassEnter,
70-
"ClassExpression:exit": onClassExit,
71-
MethodDefinition(node) {
72-
if (node.kind !== "method") {
40+
...listeners,
41+
"Program:exit"() {
42+
const maybeComponents = ctx.getAllComponents();
43+
if (E.isLeft(maybeComponents)) {
7344
return;
7445
}
46+
const components = maybeComponents.right;
47+
48+
for (const { name, flag, node: component } of components.values()) {
49+
if (!(flag & ExRClassComponentFlag.PureComponent)) {
50+
continue;
51+
}
7552

76-
checkPropertyOrMethod(node);
53+
const { body } = component.body;
54+
55+
for (const member of body) {
56+
if (isShouldComponentUpdate(member)) {
57+
context.report({
58+
messageId: "NO_REDUNDANT_SHOULD_COMPONENT_UPDATE",
59+
node: member,
60+
data: {
61+
componentName: O.getOrElse(() => "PureComponent")(name),
62+
},
63+
});
64+
}
65+
}
66+
}
7767
},
78-
PropertyDefinition: checkPropertyOrMethod,
7968
};
8069
},
8170
});
Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { NodeType, type TSESTreeClass } from "@eslint-react/ast";
2-
import { isClassComponent } from "@eslint-react/core";
3-
import { MutList } from "@eslint-react/tools";
1+
import { isOneOf, NodeType } from "@eslint-react/ast";
2+
import { componentCollectorLegacy } from "@eslint-react/core";
3+
import { E } from "@eslint-react/tools";
44
import type { TSESTree } from "@typescript-eslint/utils";
55
import { ESLintUtils } from "@typescript-eslint/utils";
66
import type { ConstantCase } from "string-ts";
@@ -11,10 +11,16 @@ export const RULE_NAME = "no-unsafe-component-will-mount";
1111

1212
export type MessageID = ConstantCase<typeof RULE_NAME>;
1313

14+
function isUnsafeComponentWillMount(node: TSESTree.ClassElement) {
15+
return isOneOf([NodeType.MethodDefinition, NodeType.PropertyDefinition])(node)
16+
&& node.key.type === NodeType.Identifier
17+
&& node.key.name === "UNSAFE_componentWillMount";
18+
}
19+
1420
export default createRule<[], MessageID>({
1521
name: RULE_NAME,
1622
meta: {
17-
type: "problem",
23+
type: "suggestion",
1824
docs: {
1925
description: "disallow usage of `UNSAFE_componentWillMount`",
2026
recommended: "recommended",
@@ -27,45 +33,30 @@ export default createRule<[], MessageID>({
2733
},
2834
defaultOptions: [],
2935
create(context) {
30-
const classStack = MutList.make<[TSESTreeClass, boolean]>();
31-
const onClassEnter = (node: TSESTreeClass) => {
32-
MutList.append(classStack, [
33-
node,
34-
isClassComponent(node, context),
35-
]);
36-
};
37-
const onClassExit = () => MutList.pop(classStack);
38-
39-
function checkPropertyOrMethod(node: TSESTree.MethodDefinition | TSESTree.PropertyDefinition) {
40-
if (node.key.type !== NodeType.Identifier || node.key.name !== "UNSAFE_componentWillMount") {
41-
return;
42-
}
43-
44-
const [classNode, isPureClassComponent] = MutList.tail(classStack) ?? [];
45-
46-
if (!classNode || !isPureClassComponent) {
47-
return;
48-
}
49-
50-
context.report({
51-
node,
52-
messageId: "NO_UNSAFE_COMPONENT_WILL_MOUNT",
53-
});
54-
}
36+
const { ctx, listeners } = componentCollectorLegacy(context);
5537

5638
return {
57-
ClassDeclaration: onClassEnter,
58-
"ClassDeclaration:exit": onClassExit,
59-
ClassExpression: onClassEnter,
60-
"ClassExpression:exit": onClassExit,
61-
MethodDefinition(node) {
62-
if (node.kind !== "method") {
39+
...listeners,
40+
"Program:exit"() {
41+
const maybeComponents = ctx.getAllComponents();
42+
if (E.isLeft(maybeComponents)) {
6343
return;
6444
}
65-
66-
checkPropertyOrMethod(node);
45+
const components = maybeComponents.right;
46+
47+
for (const { node: component } of components.values()) {
48+
const { body } = component.body;
49+
50+
for (const member of body) {
51+
if (isUnsafeComponentWillMount(member)) {
52+
context.report({
53+
messageId: "NO_UNSAFE_COMPONENT_WILL_MOUNT",
54+
node: member,
55+
});
56+
}
57+
}
58+
}
6759
},
68-
PropertyDefinition: checkPropertyOrMethod,
6960
};
7061
},
7162
});

packages/eslint-plugin/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const recommendedPreset = {
9797
"react/no-render-return-value": "error",
9898
"react/no-script-url": "warn",
9999
"react/no-string-refs": "error",
100+
"react/no-unsafe-component-will-mount": "warn",
100101
"react/no-unsafe-iframe-sandbox": "warn",
101102
"react/no-unsafe-target-blank": "warn",
102103
"react/no-unstable-default-props": "warn",

0 commit comments

Comments
 (0)