-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add convertSimpleClassComponentsToFunctions transform
- Loading branch information
1 parent
3e341bd
commit 640282d
Showing
10 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { | ||
ASTPath, | ||
FileInfo, | ||
API, | ||
ClassDeclaration, | ||
ClassMethod, | ||
ClassProperty, | ||
} from 'jscodeshift' | ||
import findImports from 'jscodeshift-find-imports' | ||
|
||
module.exports = function convertSimpleClassComponentsToFunctions( | ||
fileInfo: FileInfo, | ||
api: API | ||
): string | null | undefined | void { | ||
const j = api.jscodeshift | ||
|
||
const root = j(fileInfo.source) | ||
const { Component } = findImports( | ||
root, | ||
j.template.statement`import { Component } from 'react'` | ||
) | ||
root | ||
.find(j.ClassDeclaration, { | ||
superClass: | ||
Component.type === 'Identifier' | ||
? { type: 'Identifier', name: Component.name } | ||
: Component.type === 'MemberExpression' | ||
? { | ||
type: 'MemberExpression', | ||
object: { | ||
type: 'Identifier', | ||
name: (Component.object as any).name, | ||
}, | ||
property: { | ||
type: 'Identifier', | ||
name: (Component.property as any).name, | ||
}, | ||
} | ||
: null, | ||
}) | ||
.forEach((path: ASTPath<ClassDeclaration>) => { | ||
const { id, superTypeParameters, body } = path.node | ||
const superTypeParams = superTypeParameters?.params || [] | ||
const renderMethodColl = j([path]).find(j.ClassMethod, { | ||
key: { type: 'Identifier', name: 'render' }, | ||
}) | ||
const renderMethod: ClassMethod | void = renderMethodColl.nodes()[0] | ||
const contextTypesColl = j([path]).find(j.ClassProperty, { | ||
static: true, | ||
key: { type: 'Identifier', name: 'contextTypes' }, | ||
}) | ||
const contextTypes: ClassProperty | void = contextTypesColl.nodes()[0] | ||
const propTypesColl = j([path]).find(j.ClassProperty, { | ||
static: true, | ||
key: { type: 'Identifier', name: 'propTypes' }, | ||
}) | ||
const propTypes: ClassProperty | void = propTypesColl.nodes()[0] | ||
const defaultPropsColl = j([path]).find(j.ClassProperty, { | ||
static: true, | ||
key: { type: 'Identifier', name: 'defaultProps' }, | ||
}) | ||
const defaultProps: ClassProperty | void = defaultPropsColl.nodes()[0] | ||
|
||
if ( | ||
!id || | ||
!renderMethod || | ||
superTypeParams.length > 1 || | ||
body.body.find( | ||
node => | ||
node !== renderMethod && | ||
node !== propTypes && | ||
node !== contextTypes && | ||
node !== defaultProps | ||
) | ||
) | ||
return | ||
|
||
j([path]) | ||
.find(j.MemberExpression, { | ||
object: { type: 'ThisExpression' }, | ||
property: { type: 'Identifier', name: 'props' }, | ||
}) | ||
.replaceWith(() => j.identifier('props')) | ||
j([path]) | ||
.find(j.MemberExpression, { | ||
object: { type: 'ThisExpression' }, | ||
property: { type: 'Identifier', name: 'context' }, | ||
}) | ||
.replaceWith(() => j.identifier('context')) | ||
const propsParam = j.identifier('props') | ||
if (superTypeParams[0]) { | ||
propsParam.typeAnnotation = | ||
(superTypeParameters as any).type === 'TSTypeParameterInstantiation' | ||
? j.tsTypeAnnotation(superTypeParams[0] as any) | ||
: j.typeAnnotation(superTypeParams[0] as any) | ||
} | ||
const params = [propsParam] | ||
if (contextTypes) { | ||
params.push(j.identifier('context')) | ||
} | ||
const func = j.functionDeclaration(id, params, renderMethod.body) | ||
func.returnType = renderMethod.returnType | ||
const [replaced] = path.replace(func) | ||
if (contextTypes) { | ||
j([replaced]) | ||
.closest(j.Statement) | ||
.insertAfter( | ||
j.template.statement`${id}.contextTypes = ${contextTypes.value}\n` | ||
) | ||
} | ||
if (defaultProps) { | ||
j([replaced]) | ||
.closest(j.Statement) | ||
.insertAfter( | ||
j.template.statement`${id}.defaultProps = ${defaultProps.value}\n` | ||
) | ||
} | ||
if (propTypes) { | ||
j([replaced]) | ||
.closest(j.Statement) | ||
.insertAfter( | ||
j.template.statement`${id}.propTypes = ${propTypes.value}\n` | ||
) | ||
} | ||
}) | ||
|
||
return root.toSource() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
export const file = 'test.js' | ||
export const parser = 'babylon' | ||
|
||
export const options = {} | ||
|
||
export const input = ` | ||
import * as React from 'react' | ||
export default class Foo extends React.Component { | ||
static propTypes = { | ||
title: PropTypes.string.isRequired, | ||
} | ||
static contextTypes = { | ||
temp: PropTypes.string, | ||
} | ||
static defaultProps = { | ||
title: 'Title', | ||
} | ||
render() { | ||
return <div>{this.props.title} {this.context.temp}</div> | ||
} | ||
} | ||
` | ||
|
||
export const expected = ` | ||
import * as React from 'react' | ||
export default function Foo(props, context) { | ||
return <div>{props.title} {context.temp}</div> | ||
} | ||
Foo.propTypes = { | ||
title: PropTypes.string.isRequired, | ||
} | ||
Foo.defaultProps = { | ||
title: 'Title', | ||
} | ||
Foo.contextTypes = { | ||
temp: PropTypes.string, | ||
} | ||
` |
16 changes: 16 additions & 0 deletions
16
test/convertSimpleClassComponentsToFunctions/cantConvertBasic.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export const file = 'test.js' | ||
export const parser = 'babylon' | ||
|
||
export const options = {} | ||
|
||
export const input = ` | ||
import * as React from 'react' | ||
class Bar extends React.Component { | ||
componentDidUpdate(prevProps) {} | ||
render() { | ||
return <div>{this.props.title}</div> | ||
} | ||
} | ||
` | ||
|
||
export const expected = input |
21 changes: 21 additions & 0 deletions
21
test/convertSimpleClassComponentsToFunctions/cantConvertFlow.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export const file = 'test.js' | ||
export const parser = 'babylon' | ||
|
||
export const options = {} | ||
|
||
export const input = ` | ||
import * as React from 'react' | ||
class Foo extends React.Component<Props, State> { | ||
render(): React.Node | null { | ||
return <div>{this.props.title}</div> | ||
} | ||
} | ||
class Bar extends React.Component<Props> { | ||
componentDidUpdate(prevProps: Props) {} | ||
render(): React.Node | null { | ||
return <div>{this.props.title}</div> | ||
} | ||
} | ||
` | ||
|
||
export const expected = input |
21 changes: 21 additions & 0 deletions
21
test/convertSimpleClassComponentsToFunctions/cantConvertTypescript.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export const file = 'test.js' | ||
export const parser = 'babylon' | ||
|
||
export const options = {} | ||
|
||
export const input = ` | ||
import * as React from 'react' | ||
class Foo extends React.Component<Props, State> { | ||
render(): React.ReactNode | null { | ||
return <div>{this.props.title}</div> | ||
} | ||
} | ||
class Bar extends React.Component<Props> { | ||
componentDidUpdate(prevProps: Props) {} | ||
render(): React.ReactNode | null { | ||
return <div>{this.props.title}</div> | ||
} | ||
} | ||
` | ||
|
||
export const expected = input |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
export const file = 'test.js' | ||
export const parser = 'babylon' | ||
|
||
export const options = {} | ||
|
||
export const input = ` | ||
import * as React from 'react' | ||
export default class Foo extends React.Component<Props> { | ||
static propTypes = { | ||
title: PropTypes.string.isRequired, | ||
} | ||
render(): React.Node | null { | ||
return <div>{this.props.title}</div> | ||
} | ||
} | ||
` | ||
|
||
export const expected = ` | ||
import * as React from 'react' | ||
export default function Foo(props: Props): React.Node | null { | ||
return <div>{props.title}</div> | ||
} | ||
Foo.propTypes = { | ||
title: PropTypes.string.isRequired, | ||
} | ||
` |
20 changes: 20 additions & 0 deletions
20
test/convertSimpleClassComponentsToFunctions/typescript.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export const file = 'test.tsx' | ||
export const parser = 'tsx' | ||
|
||
export const options = {} | ||
|
||
export const input = ` | ||
import * as React from 'react' | ||
export default class Foo extends React.Component<Props> { | ||
render(): React.ReactNode | null { | ||
return <div>{this.props.title}</div> | ||
} | ||
} | ||
` | ||
|
||
export const expected = ` | ||
import * as React from 'react' | ||
export default function Foo(props: Props): React.ReactNode | null { | ||
return <div>{props.title}</div> | ||
} | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1635,6 +1635,11 @@ assign-symbols@^1.0.0: | |
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" | ||
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= | ||
|
||
[email protected]: | ||
version "0.11.7" | ||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.7.tgz#f318bf44e339db6a320be0009ded64ec1471f46c" | ||
integrity sha512-2mP3TwtkY/aTv5X3ZsMpNAbOnyoC/aMJwJSoaELPkHId0nSQgFcnU4dRW3isxiz7+zBexk0ym3WNVjMiQBnJSw== | ||
|
||
[email protected]: | ||
version "0.12.1" | ||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.12.1.tgz#55d3737a8a68e1ccde131067005ce7ee3dd42b99" | ||
|
@@ -4873,13 +4878,44 @@ jsbn@~0.1.0: | |
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" | ||
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= | ||
|
||
jscodeshift-find-imports@^2.0.0: | ||
version "2.0.0" | ||
resolved "https://registry.yarnpkg.com/jscodeshift-find-imports/-/jscodeshift-find-imports-2.0.0.tgz#d9bddc3eddbc927017004664fefd5bcf49e432de" | ||
integrity sha512-ZYmZJ17pU/CGDaB6PWo4j7NVazVuwBDBXUrGnF57YcGvSA1toTToE/YitdBbCwariM4ky+gPWbM383rAmlbJQw== | ||
dependencies: | ||
jscodeshift "^0.6.4" | ||
|
||
jscodeshift-paths-in-range@^1.0.0: | ||
version "1.1.0" | ||
resolved "https://registry.yarnpkg.com/jscodeshift-paths-in-range/-/jscodeshift-paths-in-range-1.1.0.tgz#accc511afd807094f274a497f136b6d338efda8d" | ||
integrity sha512-Hy/wwoLxXxLQXnvo2wBnqMXgvQODaU5EdjJjQp/5sPhbBujSJa+8K+jV75uJC+TnPeOM7ofdvQu72P6l/idn9g== | ||
dependencies: | ||
"@babel/runtime" "^7.1.5" | ||
|
||
jscodeshift@^0.6.4: | ||
version "0.6.4" | ||
resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.6.4.tgz#e19ab86214edac86a75c4557fc88b3937d558a8e" | ||
integrity sha512-+NF/tlNbc2WEhXUuc4WEJLsJumF84tnaMUZW2hyJw3jThKKRvsPX4sPJVgO1lPE28z0gNL+gwniLG9d8mYvQCQ== | ||
dependencies: | ||
"@babel/core" "^7.1.6" | ||
"@babel/parser" "^7.1.6" | ||
"@babel/plugin-proposal-class-properties" "^7.1.0" | ||
"@babel/plugin-proposal-object-rest-spread" "^7.0.0" | ||
"@babel/preset-env" "^7.1.6" | ||
"@babel/preset-flow" "^7.0.0" | ||
"@babel/preset-typescript" "^7.1.0" | ||
"@babel/register" "^7.0.0" | ||
babel-core "^7.0.0-bridge.0" | ||
colors "^1.1.2" | ||
flow-parser "0.*" | ||
graceful-fs "^4.1.11" | ||
micromatch "^3.1.10" | ||
neo-async "^2.5.0" | ||
node-dir "^0.1.17" | ||
recast "^0.16.1" | ||
temp "^0.8.1" | ||
write-file-atomic "^2.3.0" | ||
|
||
jscodeshift@^0.7.0: | ||
version "0.7.0" | ||
resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.7.0.tgz#4eee7506fd4fdacbd80340287d61575af991fdab" | ||
|
@@ -7255,6 +7291,16 @@ [email protected]: | |
private "~0.1.5" | ||
source-map "~0.6.1" | ||
|
||
recast@^0.16.1: | ||
version "0.16.2" | ||
resolved "https://registry.yarnpkg.com/recast/-/recast-0.16.2.tgz#3796ebad5fe49ed85473b479cd6df554ad725dc2" | ||
integrity sha512-O/7qXi51DPjRVdbrpNzoBQH5dnAPQNbfoOFyRiUwreTMJfIHYOEBzwuH+c0+/BTSJ3CQyKs6ILSWXhESH6Op3A== | ||
dependencies: | ||
ast-types "0.11.7" | ||
esprima "~4.0.0" | ||
private "~0.1.5" | ||
source-map "~0.6.1" | ||
|
||
recast@^0.18.1: | ||
version "0.18.5" | ||
resolved "https://registry.yarnpkg.com/recast/-/recast-0.18.5.tgz#9d5adbc07983a3c8145f3034812374a493e0fe4d" | ||
|