@@ -4,18 +4,54 @@ import { memberName, nonNullPath } from "../utils.js";
44import { analyzeLibRef , isReactRef , LibRef } from "./lib.js" ;
55
66export 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+ */
1646export function preanalyzeClass ( path : NodePath < ClassDeclaration > ) : PreAnalysisResult | undefined {
1747 if ( path . node . leadingComments ?. some ( ( comment ) => / r e a c t - d e c l a s s i f y - d i s a b l e / . 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+ */
66126function 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+ */
88154function 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