Skip to content

Commit 03079b3

Browse files
committed
Add comments on pre.ts
1 parent e453424 commit 03079b3

File tree

1 file changed

+93
-25
lines changed

1 file changed

+93
-25
lines changed

src/analysis/pre.ts

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,54 @@ import { memberName, nonNullPath } from "../utils.js";
44
import { analyzeLibRef, isReactRef, LibRef } from "./lib.js";
55

66
export type PreAnalysisResult = {
7+
/**
8+
* The declared name of the class declaration/expression.
9+
*
10+
* May be absent if it is a class expression or a class declaration in an `export default` declaration.
11+
*/
712
name?: Identifier | undefined;
13+
/**
14+
* Generics on the class.
15+
*/
816
typeParameters?: NodePath<TSTypeParameterDeclaration> | undefined;
17+
/**
18+
* How does the component reference `React.Component`?
19+
* This is necessary to add another reference to React libraries, such as `React.FC` and `React.useState`.
20+
*/
921
superClassRef: LibRef;
22+
/**
23+
* Does it extend `PureComponent` instead of `Component`?
24+
*/
1025
isPure: boolean;
26+
/**
27+
* A node containing Props type (`P` as in `React.Component<P>`)
28+
*/
1129
props: NodePath<TSType> | undefined;
30+
/**
31+
* Decomposed Props type (`P` as in `React.Component<P>`)
32+
*/
1233
propsEach: Map<string, NodePath<TSPropertySignature | TSMethodSignature>>;
34+
/**
35+
* Decomposed State type (`S` as in `React.Component<P, S>`)
36+
*/
1337
states: Map<string, NodePath<TSPropertySignature | TSMethodSignature>>;
1438
};
1539

40+
/**
41+
* Analyzes a class header to determine if it should be transformed.
42+
*
43+
* @param path the pass to the class node
44+
* @returns an object containing analysis result, if the class should be transformed
45+
*/
1646
export function preanalyzeClass(path: NodePath<ClassDeclaration>): PreAnalysisResult | undefined {
1747
if (path.node.leadingComments?.some((comment) => /react-declassify-disable/.test(comment.value))) {
1848
// Explicitly disabled
49+
//
50+
// E.g.
51+
// ```js
52+
// /* react-declassify-disable */
53+
// class MyComponent extends Component {}
54+
// ```
1955
return;
2056
}
2157
if (
@@ -27,42 +63,66 @@ export function preanalyzeClass(path: NodePath<ClassDeclaration>): PreAnalysisRe
2763
|| path.node.abstract
2864
) {
2965
// This is an abstract class to be inherited; do not attempt transformation.
66+
//
67+
// E.g.
68+
// ```js
69+
// abstract class MyComponent extends Component {}
70+
// /** @abstract */
71+
// class MyComponent2 extends Component {}
72+
// ```
3073
return;
3174
}
32-
const superClass = path.get("superClass");
33-
if (!superClass.isExpression()) {
75+
76+
// Check if it extends React.Component or React.PureComponent
77+
const superClass = nonNullPath(path.get("superClass"));
78+
if (!superClass) {
79+
// Not a subclass
3480
return;
3581
}
3682
const superClassRef = analyzeLibRef(superClass);
37-
if (!superClassRef || !isReactRef(superClassRef)) {
83+
if (
84+
// Subclass of an unknown class
85+
!superClassRef
86+
// Not a react thing, presumably
87+
|| !isReactRef(superClassRef)
88+
// React.Something but I'm not sure what it is
89+
|| !(superClassRef.name === "Component" || superClassRef.name === "PureComponent")
90+
) {
3891
return;
3992
}
40-
if (superClassRef.name === "Component" || superClassRef.name === "PureComponent") {
41-
const name = path.node.id;
42-
const typeParameters_ = nonNullPath(path.get("typeParameters"));
43-
const typeParameters = typeParameters_?.isTSTypeParameterDeclaration() ? typeParameters_ : undefined;
44-
const isPure = superClassRef.name === "PureComponent";
45-
let props: NodePath<TSType> | undefined;
46-
let propsEach: Map<string, NodePath<TSPropertySignature | TSMethodSignature>> | undefined = undefined;
47-
let states: Map<string, NodePath<TSPropertySignature | TSMethodSignature>> | undefined = undefined;
48-
const superTypeParameters = path.get("superTypeParameters");
49-
if (superTypeParameters.isTSTypeParameterInstantiation()) {
50-
const params = superTypeParameters.get("params");
51-
if (params.length > 0) {
52-
props = params[0];
53-
propsEach = decompose(params[0]!);
54-
}
55-
if (params.length > 1) {
56-
const stateParamPath = params[1]!;
57-
states = decompose(stateParamPath);
58-
}
93+
94+
// OK, now we are going to transform the component
95+
const name = path.node.id;
96+
const typeParameters_ = nonNullPath(path.get("typeParameters"));
97+
const typeParameters = typeParameters_?.isTSTypeParameterDeclaration() ? typeParameters_ : undefined;
98+
const isPure = superClassRef.name === "PureComponent";
99+
let props: NodePath<TSType> | undefined;
100+
let propsEach: Map<string, NodePath<TSPropertySignature | TSMethodSignature>> | undefined = undefined;
101+
let states: Map<string, NodePath<TSPropertySignature | TSMethodSignature>> | undefined = undefined;
102+
const superTypeParameters = path.get("superTypeParameters");
103+
if (superTypeParameters.isTSTypeParameterInstantiation()) {
104+
// Analyze P and S as in React.Component<P, S>
105+
const params = superTypeParameters.get("params");
106+
if (params.length > 0) {
107+
props = params[0];
108+
propsEach = decompose(params[0]!);
109+
}
110+
if (params.length > 1) {
111+
const stateParamPath = params[1]!;
112+
states = decompose(stateParamPath);
59113
}
60-
propsEach ??= new Map();
61-
states ??= new Map();
62-
return { name, typeParameters, superClassRef, isPure, props, propsEach, states };
63114
}
115+
propsEach ??= new Map();
116+
states ??= new Map();
117+
return { name, typeParameters, superClassRef, isPure, props, propsEach, states };
64118
}
65119

120+
/**
121+
* Tries to decompose a type into a set of property signatures.
122+
*
123+
* @param path a type
124+
* @returns a map containing property signatures and method signatures
125+
*/
66126
function decompose(path: NodePath<TSType>): Map<string, NodePath<TSPropertySignature | TSMethodSignature>> {
67127
const aliasPath = resolveAlias(path);
68128
const members =
@@ -85,10 +145,18 @@ function decompose(path: NodePath<TSType>): Map<string, NodePath<TSPropertySigna
85145
return decomposed;
86146
}
87147

148+
/**
149+
* Jumps to the definition if the type node references other type.
150+
*
151+
* @param path a type to resolve
152+
* @returns A type node or a node containing an `interface` definition
153+
*/
88154
function resolveAlias(path: NodePath<TSType>): NodePath<TSType | TSInterfaceBody> {
89155
if (path.isTSTypeReference()) {
90156
const typeNamePath = path.get("typeName");
91157
if (typeNamePath.isIdentifier()) {
158+
// Resolve identifier using heuristics.
159+
// Babel does not have full scope resolver for types.
92160
const name = typeNamePath.node.name;
93161
let scope = typeNamePath.scope;
94162
while (scope) {

0 commit comments

Comments
 (0)