Skip to content

Commit 26d8564

Browse files
committed
Support PureComponent
1 parent 36040b0 commit 26d8564

File tree

5 files changed

+61
-9
lines changed

5 files changed

+61
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- Added
44
- Add support for more type annotations on methods
55
- Add support for modifying types reflecting `defaultProps`
6+
- Add support for `React.PureComponent`
67

78
## 0.1.5
89

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ export const C = props => {
147147
## Progress
148148

149149
- [x] Convert render function (basic feature)
150-
- [ ] Superclass detection
150+
- [x] Superclass detection
151151
- [x] Support `React.Component`
152-
- [ ] Support `React.PureComponent`
152+
- [x] Support `React.PureComponent`
153153
- [ ] Class node type
154154
- [x] Support class declarations
155155
- [x] Support `export default class` declarations

src/analysis/head.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import type { NodePath } from "@babel/core";
2-
import type { BlockStatement, ClassDeclaration, Program, TSInterfaceBody, TSMethodSignature, TSPropertySignature, TSType } from "@babel/types";
2+
import type { BlockStatement, ClassDeclaration, Identifier, Program, TSInterfaceBody, TSMethodSignature, TSPropertySignature, TSType } from "@babel/types";
33
import { memberName } from "../utils.js";
44
import { analyzeLibRef, isReactRef, LibRef } from "./lib.js";
55

66
export type ComponentHead = {
7+
name?: Identifier | undefined;
78
superClassRef: LibRef;
9+
isPure: boolean;
810
props: NodePath<TSType> | undefined;
911
propsEach: Map<string, NodePath<TSPropertySignature | TSMethodSignature>>;
1012
states: Map<string, NodePath<TSPropertySignature | TSMethodSignature>>;
@@ -35,6 +37,8 @@ export function analyzeHead(path: NodePath<ClassDeclaration>): ComponentHead | u
3537
return;
3638
}
3739
if (superClassRef.name === "Component" || superClassRef.name === "PureComponent") {
40+
const name = path.node.id;
41+
const isPure = superClassRef.name === "PureComponent";
3842
let props: NodePath<TSType> | undefined;
3943
let propsEach: Map<string, NodePath<TSPropertySignature | TSMethodSignature>> | undefined = undefined;
4044
let states: Map<string, NodePath<TSPropertySignature | TSMethodSignature>> | undefined = undefined;
@@ -52,7 +56,7 @@ export function analyzeHead(path: NodePath<ClassDeclaration>): ComponentHead | u
5256
}
5357
propsEach ??= new Map();
5458
states ??= new Map();
55-
return { superClassRef, props, propsEach, states };
59+
return { name, superClassRef, isPure, props, propsEach, states };
5660
}
5761
}
5862

src/index.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ describe("react-declassify", () => {
146146
`;
147147
const output = dedent`\
148148
import React from "react";
149-
const C = () => {};
149+
const C = React.memo(function C() {});
150150
`;
151151
expect(transform(input)).toBe(output);
152152
});
@@ -1021,6 +1021,40 @@ describe("react-declassify", () => {
10211021
});
10221022
});
10231023

1024+
describe("Memoization", () => {
1025+
it("transforms PureComponent to React.memo", () => {
1026+
const input = dedent`\
1027+
class C extends React.PureComponent {
1028+
render() {
1029+
return <div>Hello, world!</div>;
1030+
}
1031+
}
1032+
`;
1033+
const output = dedent`\
1034+
const C = React.memo(function C() {
1035+
return <div>Hello, world!</div>;
1036+
});
1037+
`;
1038+
expect(transform(input)).toBe(output);
1039+
});
1040+
1041+
it("Places types on const", () => {
1042+
const input = dedent`\
1043+
class C extends React.PureComponent {
1044+
render() {
1045+
return <div>Hello, world!</div>;
1046+
}
1047+
}
1048+
`;
1049+
const output = dedent`\
1050+
const C: React.FC = React.memo(function C() {
1051+
return <div>Hello, world!</div>;
1052+
});
1053+
`;
1054+
expect(transform(input, { ts: true })).toBe(output);
1055+
});
1056+
});
1057+
10241058
test("readme example 1", () => {
10251059
const input = dedent`\
10261060
import React from "react";

src/index.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default function plugin(babel: typeof import("@babel/core")): PluginObj<P
7777
}
7878

7979
type TransformResult = {
80-
funcNode: ArrowFunctionExpression;
80+
funcNode: Expression;
8181
typeNode?: TSType | undefined;
8282
};
8383

@@ -311,11 +311,24 @@ function transformClass(head: ComponentHead, body: ComponentBody, options: { ts:
311311
}
312312
const bodyNode = body.render.path.node.body;
313313
bodyNode.body.splice(0, 0, ...preamble);
314-
return {
315-
funcNode: t.arrowFunctionExpression(
314+
const functionNeeded = head.isPure;
315+
const funcNode = functionNeeded
316+
? t.functionExpression(
317+
head.name ? t.cloneNode(head.name) : undefined,
316318
needsProps(body) ? [t.identifier("props")] : [],
317319
bodyNode
318-
),
320+
)
321+
: t.arrowFunctionExpression(
322+
needsProps(body) ? [t.identifier("props")] : [],
323+
bodyNode
324+
);
325+
return {
326+
funcNode: head.isPure
327+
? t.callExpression(
328+
getReactImport("memo", babel, head.superClassRef),
329+
[funcNode]
330+
)
331+
: funcNode,
319332
typeNode: ts
320333
? t.tsTypeReference(
321334
toTSEntity(getReactImport("FC", babel, head.superClassRef), babel),

0 commit comments

Comments
 (0)