Babel: A refactoring tool

@NicoloRibaudo NICOLÒ RIBAUDO Babel team

@NicoloRibaudo [email protected] @nicolo-ribaudo

@NicoloRibaudo 2 https://opencollective.com/babel

@babeljs https://babeljs.io @babel

@NicoloRibaudo 3 What is Babel?

@NicoloRibaudo 4 What is Babel? Babel is a JavaScript

@NicoloRibaudo 5 It’s a JavaScript to JavaScript compiler

(_player$level = player.level) != null player.level ??= 70_000; ? _player$level : player.level = 70000;

@NicoloRibaudo 6 @NicoloRibaudo 7 @babel/how-to

https://www.youtube.com/watch?v=UeVq_U5obnE

@NicoloRibaudo 8 What is Babel?

@NicoloRibaudo 9 What is Babel? Babel is a customizable JavaScript compiler

@NicoloRibaudo 10 So… what can I do?

@NicoloRibaudo 11 So… what can I do?

● Compile modern ECMAScript syntax to older (and more supported) syntax ● Compile TypeScript, Flow and JSX to plain JavaScript

@NicoloRibaudo 12 So… what can I do?

● Compile modern ECMAScript syntax to older (and more supported) syntax ● Compile TypeScript, Flow and JSX to plain JavaScript

● Minify your code

@NicoloRibaudo 13 So… what can I do?

● Compile modern ECMAScript syntax to older (and more supported) syntax ● Compile TypeScript, Flow and JSX to plain JavaScript

● Minify your code

● Statically evaluate parts of your program

@NicoloRibaudo 14 So… what can I do?

● Compile modern ECMAScript syntax to older (and more supported) syntax ● Compile TypeScript, Flow and JSX to plain JavaScript

● Minify your code

● Statically evaluate parts of your program

● Perform static analysis on your code

@NicoloRibaudo 15 So… what can I do?

● Compile modern ECMAScript syntax to older (and more supported) syntax ● Compile TypeScript, Flow and JSX to plain JavaScript

● Minify your code

● Statically evaluate parts of your program

● Perform static analysis on your code

● Run one-time transforms and automate refactorings

@NicoloRibaudo 16 So… what can I do?

● Run one-time transforms and automate refactorings

@NicoloRibaudo 17 Codemods One-time transforms and automate refactorings

@NicoloRibaudo 18 Codemods

@NicoloRibaudo 19 Codemods Refactors… happen. 路

@NicoloRibaudo 20 Codemods Refactors… happen. 路

Sometimes ... And they take ... … they are self-contained, without impacting … a few hours other parts or your program

@NicoloRibaudo 21 Codemods Refactors… happen. 路

Sometimes ... And they take ... … they are self-contained, without impacting … a few hours other parts or your program

… they affects your whole codebase … a week

@NicoloRibaudo 22 Codemods Refactors… happen. 路

Sometimes ... And they take ... … they are self-contained, without impacting … a few hours other parts or your program

… they affects your whole codebase … a week

… you need to introduce changes accross … many weeks, maybe applications maintained by different teams months months

@NicoloRibaudo 23 Codemods Refactors… happen. 路

Sometimes ... And they take ... … they are self-contained, without impacting … a few hours other parts or your program

… they affects your whole codebase … a week

… you need to introduce changes accross … many weeks, maybe applications maintained by different teams months months

… you maintain a popular library that is used … ? by many users outside of your company

@NicoloRibaudo 24 What can you use codemods for?

@NicoloRibaudo 25 What can you use codemods for? What did we use them for in Babel?

@NicoloRibaudo 26 What can you use codemods for? What did we use them for in Babel?

● Migrate our tests to Jest 

@NicoloRibaudo 27 What can you use codemods for? What did we use them for in Babel?

● Migrate our tests to Jest 

● Remove unused catch bindings 

@NicoloRibaudo 28 What can you use codemods for? What did we use them for in Babel?

● Migrate our tests to Jest 

● Remove unused catch bindings 

● Remove the resolve dependency 

@NicoloRibaudo 29 What can you use codemods for? What did we use them for in Babel?

● Migrate our tests to Jest 

● Remove unused catch bindings 

● Remove the resolve dependency 

● Migrate from Flow to TypeScript 

@NicoloRibaudo 30 What can you use codemods for? What other companies use them for?

@NicoloRibaudo 31 What can you use codemods for? What other companies use them for? ● Facebook publishes codemods to migrate

away from legacy React versions 

@NicoloRibaudo 32 What can you use codemods for? What other companies use them for? ● Facebook publishes codemods to migrate

away from legacy React versions 

● Gatsby provides codemods to migrate to

new versions  

@NicoloRibaudo 33 What can you use codemods for? What other companies use them for? ● Facebook publishes codemods to migrate

away from legacy React versions 

● Gatsby provides codemods to migrate to

new versions  

● Next.js provides codemods to upgrade

when a feature is deprecated 

@NicoloRibaudo 34 vs Codemods

@NicoloRibaudo 35 Compilers vs Codemods

@NicoloRibaudo 36 Compilers vs Codemods The usual Babel transforms are based on strictly defined semantics

@NicoloRibaudo 37 Compilers vs Codemods The usual Babel transforms are based on strictly defined semantics

person?.address.city?.size

person == null ? void 0 : (_tmp = person.address.city) == null ? void 0 : _tmp.size

@NicoloRibaudo 38 Compilers vs Codemods The usual Babel transforms are based on strictly Codemods are based on assumptions about defined semantics your coding style

person?.address.city?.size

person == null ? void 0 : (_tmp = person.address.city) == null ? void 0 : _tmp.size

@NicoloRibaudo 39 Compilers vs Codemods The usual Babel transforms are based on strictly Codemods are based on assumptions about defined semantics your coding style

person?.address.city?.size person && person.address.city && person.address.city.size

person == null

? void 0 person?.address.city?.size : (_tmp = person.address.city) == null ? void 0 : _tmp.size

@NicoloRibaudo 40 Compilers vs Codemods The usual Babel transforms are based on strictly Codemods are based on assumptions about defined semantics your coding style

person?.address.city?.size person && person.address.city && person.address.city.size

person == null

? void 0 person?.address.city?.size : (_tmp = person.address.city) == null

? void 0 false?.address.city?.size : _tmp.size ��

@NicoloRibaudo 41 Compilers vs Codemods

The usual Babel transforms are based on strictly Codemods are based on assumptions about defined semantics your coding style

@NicoloRibaudo 42 Compilers vs Codemods The usual Babel transforms are based on strictly Codemods are based on assumptions about defined semantics your coding style

They need to be precise and infallible.

Developers should be able to trust the compiler output without checking it every time.

@NicoloRibaudo 43 Compilers vs Codemods The usual Babel transforms are based on strictly Codemods are based on assumptions about defined semantics your coding style

They need to be precise and infallible. They need to work in most cases, but it's not necessary that they work always. Developers should be able to trust the compiler output without checking it every time.

@NicoloRibaudo 44 Compilers vs Codemods The usual Babel transforms are based on strictly Codemods are based on assumptions about defined semantics your coding style

They need to be precise and infallible. They need to work in most cases, but it's not necessary that they work always. Developers should be able to trust the compiler output without checking it every time. They rely on human intervention.

@NicoloRibaudo 45 Human-assisted automated transforms

Why don't we just create the perfect codemod?

@NicoloRibaudo 46 Human-assisted automated transforms

https://xkcd.com/1319/ @NicoloRibaudo 47 Human-assisted automated transforms const fs = require("fs"); const { join } = require("path");

@NicoloRibaudo 48 Human-assisted automated transforms const fs = require("fs"); import fs from "fs"; const { join } = require("path"); import { join } from "path";

@NicoloRibaudo 49 Human-assisted automated transforms const fs = require("fs"); import fs from "fs"; const { join } = require("path"); import { join } from "path"; exports.readLocal = (file) => { return fs.readFileSync( join(__dirname, file) ); };

@NicoloRibaudo 50 Human-assisted automated transforms const fs = require("fs"); import fs from "fs"; const { join } = require("path"); import { join } from "path"; exports.readLocal = (file) => { export const readLocal = (file) => { return fs.readFileSync( return fs.readFileSync( join(__dirname, file) join(__dirname, file) ); ); }; }

@NicoloRibaudo 51 Human-assisted automated transforms const fs = require("fs"); import fs from "fs"; const { join } = require("path"); import { join } from "path"; exports.readLocal = (file) => { export const readLocal = (file) => { return fs.readFileSync( return fs.readFileSync( join(__dirname, file) join(__dirname, file) ); ); }; }

// "writeLocal" exports[`${fs.write.name}Local`] = (file, contents) => { return fs.writeFileSync( join(__dirname, file), contents ); };

@NicoloRibaudo 52 Human-assisted automated transforms const fs = require("fs"); import fs from "fs"; const { join } = require("path"); import { join } from "path"; exports.readLocal = (file) => { export const readLocal = (file) => { return fs.readFileSync( return fs.readFileSync( join(__dirname, file) join(__dirname, file) ); ); }; }

// "writeLocal" // "writeLocal" exports[`${fs.write.name}Local`] = export const UNKNOWN = (file, contents) => { (file, contents) => { return fs.writeFileSync( return fs.writeFileSync( join(__dirname, file), contents join(__dirname, file), contents ); ); }; };

@NicoloRibaudo 53 Human-assisted automated transforms

��

@NicoloRibaudo 54 Alternatives

@NicoloRibaudo 55 Alternatives

Regular Expressions Good for simple, self-contained refactors

@NicoloRibaudo 56 Alternatives

JSCodeshift The most popular tool to build JavaScript codemods

@NicoloRibaudo 57 Alternatives

JSCodeshift The most popular tool to build JavaScript codemods

● Provides a jQuery-like API to navigate the AST

@NicoloRibaudo 58 Alternatives

JSCodeshift The most popular tool to build JavaScript codemods

● Provides a jQuery-like API to navigate the AST

● Includes different tools (@babel/parser for parsing, recast for printing)

@NicoloRibaudo 59 Alternatives

JSCodeshift The most popular tool to build JavaScript codemods

● Provides a jQuery-like API to navigate the AST

● Includes different tools (@babel/parser for parsing, recast for printing)

● It can be used with any AST transform tool you like (Babel included!)

@NicoloRibaudo 60 Alternatives

JSCodeshift The most popular tool to build JavaScript codemods

So… why Babel?

@NicoloRibaudo 61 Alternatives

JSCodeshift The most popular tool to build JavaScript codemods

So… why Babel? ● It provides a massive set of built-in utilities to analyze the AST and the Scope

@NicoloRibaudo 62 Alternatives

JSCodeshift The most popular tool to build JavaScript codemods

So… why Babel? ● It provides a massive set of built-in utilities to analyze the AST and the Scope

● You can re-use the same knowledge to modify your build process with custom plugins

@NicoloRibaudo 63 A practical example

@NicoloRibaudo 64 A practical example Transforming React class components to functions with hooks

@NicoloRibaudo 65 @NicoloRibaudo 66 React class components are still supported and won't go away soon.

@NicoloRibaudo 67 React class components are still supported and won't go away soon.

This codemod is for illustrative purposes, you don't actually need to do it!

@NicoloRibaudo 68 class Counter extends React.Component { const Counter = (props) => { state = { name: "Unknown", count: 0 }; const [name, setName] = useState("Unknown"); const [count, setCount] = useState(0); incrementCount = () => { this.setState((state) => ({ count: state.count - 1 })); const incrementCount = () => { }; setCount((count) => count - 1); }; decrementCount = () => { this.setState((state) => ({ count: state.count - 1 })); const decrementCount = () => { }; setCount((count) => count - 1); }; updateName = (e) => { this.setState({ name: e.target.value }); const updateName = (e) => { }; setName(e.target.value); }; render() { return ( return (

{this.state.count} {count}

); ); } }; } @NicoloRibaudo 69 @NicoloRibaudo 70 AST Explorer A plugin development playground

@NicoloRibaudo 71 AST Explorer: a plugin playground

@NicoloRibaudo 72 AST Explorer: a plugin playground

Input code Input AST

Babel plugin Output code

@NicoloRibaudo 73 @NicoloRibaudo 74 Steps

@NicoloRibaudo 75 Steps

1. Detecting React class components

Complexity

@NicoloRibaudo 76 Steps

1. Detecting React class components 2. Transforming the render() method

Complexity

@NicoloRibaudo 77 Steps

1. Detecting React class components 2. Transforming the render() method 3. Adding support for props

Complexity

@NicoloRibaudo 78 Steps

1. Detecting React class components 2. Transforming the render() method 3. Adding support for props

4. Adding support for state Complexity

@NicoloRibaudo 79 Steps

1. Detecting React class components 2. Transforming the render() method 3. Adding support for props

4. Adding support for state

a. Not adding support for complex state (with useReducer()) Complexity

@NicoloRibaudo 80 Steps

1. Detecting React class components 2. Transforming the render() method 3. Adding support for props

4. Adding support for state

a. Not adding support for complex state (with useReducer()) Complexity b. Analytics: Should we support defaultProps?

@NicoloRibaudo 81 Steps

1. Detecting React class components 2. Transforming the render() method 3. Adding support for props

4. Adding support for state

a. Not adding support for complex state (with useReducer()) Complexity b. Analytics: Should we support defaultProps?

5. Adding support for class fields

@NicoloRibaudo 82 Steps

1. Detecting React class components 2. Transforming the render() method 3. Adding support for props

4. Adding support for state

a. Not adding support for complex state (with useReducer()) Complexity b. Analytics: Should we support defaultProps?

5. Adding support for class fields 6. Injecting imports to hooks

@NicoloRibaudo 83 The anatomy of a Babel plugin function codemod({ types: t, template }) {

// ...

return { visitor: {

// ...

} }; }

@NicoloRibaudo 84 The anatomy of a Babel plugin function codemod({ types: t, template }) {

// ... Get different utilities from Babel, ... return { visitor: {

// ...

} }; }

@NicoloRibaudo 85 The anatomy of a Babel plugin function codemod({ types: t, template }) {

// ... Get different utilities from Babel, ... return { visitor: { … define helper functions to // ... analyze and transform ...

} }; }

@NicoloRibaudo 86 The anatomy of a Babel plugin function codemod({ types: t, template }) {

// ... Get different utilities from Babel, ... return { visitor: { … define helper functions to // ... analyze and transform ...

} }; … and intercept the AST nodes to handle. }

@NicoloRibaudo 87 1. Detecting React class components

class MyComponent extends React.Component { // ... }

@NicoloRibaudo 88 1. Detecting React class components

class MyComponent extends React.Component { // ... }

@NicoloRibaudo 89 1. Detecting React class components

@NicoloRibaudo 90 1. Detecting React class components return { visitor: {

// ...

} };

@NicoloRibaudo 91 1. Detecting React class components return { visitor: { ClassDeclaration(path) { if (!isReactComponent(path.node)) { return; }

alert("We have a React component!"); } } };

@NicoloRibaudo 92 1. Detecting React class components function isReactComponent(node) { return t.matchesPattern( node.superClass, "React.Component" ); } return { visitor: { ClassDeclaration(path) { /* ... */ } } };

@NicoloRibaudo 93 2. Extracting the render() method

@NicoloRibaudo 94 2. Extracting the render() method

class Counter extends Component { render() { return

It works!

; } }

@NicoloRibaudo 95 2. Extracting the render() method

class Counter extends Component { render() { return

It works!

; } }

@NicoloRibaudo 96 2. Extracting the render() method function findMethod(node, name) {

} findMethod(path.node, "render");

@NicoloRibaudo 97 2. Extracting the render() method function findMethod(node, name) { const elem = node.body.body

} findMethod(path.node, "render");

@NicoloRibaudo 98 2. Extracting the render() method function findMethod(node, name) { const elem = node.body.body.find(el => t.isClassMethod(el)

);

} findMethod(path.node, "render");

@NicoloRibaudo 99 2. Extracting the render() method function findMethod(node, name) { const elem = node.body.body.find(el => t.isClassMethod(el, { static: false, computed: false })

);

} findMethod(path.node, "render");

@NicoloRibaudo 100 2. Extracting the render() method function findMethod(node, name) { const elem = node.body.body.find(el => t.isClassMethod(el, { static: false, computed: false }) && t.isIdentifier(el.key, { name }) );

} findMethod(path.node, "render");

@NicoloRibaudo 101 2. Extracting the render() method function findMethod(node, name) { const elem = node.body.body.find(el => t.isClassMethod(el, { static: false, computed: false }) && t.isIdentifier(el.key, { name }) ); if (elem) return elem.body.body; } findMethod(path.node, "render");

@NicoloRibaudo 102 2. Extracting the render() method function findMethod(node, name) { const elem = node.body.body.find(el => t.isClassMethod(el, { static: false, computed: false }) && t.isIdentifier(el.key, { name }) ); if (elem) return elem.body.body; } findMethod(path.node, "render");

@NicoloRibaudo 103 2. Extracting the render() method visitor: { ClassDeclaration(path) { if (!isReactComponent(path.node)) return;

path.replaceWith(template.ast` const ${path.node.id} = (props) => { ${findMethod(path.node, "render")}; }; `); }, },

@NicoloRibaudo 104 2. Extracting the render() method visitor: { class Counter extends Component { ClassDeclaration(path) { render() { if (!isReactComponent(path.node)) return; return

It works!

;

path.replaceWith(template.ast` } const ${path.node.id} = (props) => { } ${findMethod(path.node, "render")}; }; `); }, },

@NicoloRibaudo 105 2. Extracting the render() method visitor: { class Counter extends Component { ClassDeclaration(path) { render() { if (!isReactComponent(path.node)) return; return

It works!

;

path.replaceWith(template.ast` } const ${path.node.id} = (props) => { } ${findMethod(path.node, "render")}; }; `); const Counter = (props) => { }, return

It works!

; }, };

@NicoloRibaudo 106 3. Rewriting this.props usage

class Counter extends Component { render() { return

Hi, {this.props.name}!

} }

@NicoloRibaudo 107 3. Rewriting this.props usage

class Counter extends Component { render() { return

Hi, {this.props.name}!

} }

@NicoloRibaudo 108 3. Rewriting this.props usage

class Counter extends Component { render() { return

Hi, {this.props.name}!

} }

@NicoloRibaudo 109 3. Rewriting this.props usage function rewritePropsUsage(path) {

}

@NicoloRibaudo 110 3. Rewriting this.props usage function rewritePropsUsage(path) { path.traverse({ MemberExpression(path) {

}, }); }

@NicoloRibaudo 111 3. Rewriting this.props usage function rewritePropsUsage(path) { path.traverse({ MemberExpression(path) { const { node } = path; if ( !node.computed

) {

} }, }); }

@NicoloRibaudo 112 3. Rewriting this.props usage function rewritePropsUsage(path) { path.traverse({ MemberExpression(path) { const { node } = path; if ( !node.computed && t.isThisExpression(node.object)

) {

} }, }); }

@NicoloRibaudo 113 3. Rewriting this.props usage function rewritePropsUsage(path) { path.traverse({ MemberExpression(path) { const { node } = path; if ( !node.computed && t.isThisExpression(node.object) && t.isIdentifier(node.property, { name: "props" }) ) {

} }, }); }

@NicoloRibaudo 114 3. Rewriting this.props usage function rewritePropsUsage(path) { path.traverse({ MemberExpression(path) { const { node } = path; if ( !node.computed && t.isThisExpression(node.object) && t.isIdentifier(node.property, { name: "props" }) ) { path.replaceWith(t.identifier("props")); } }, }); }

@NicoloRibaudo 115 3. Rewriting this.props usage visitor: { ClassDeclaration(path) { if (!isReactComponent(path.node)) return;

path.replaceWith(template.ast` const ${path.node.id} = (props) => { ${findMethod(path.node, "render")}; }; `); }, },

@NicoloRibaudo 116 3. Rewriting this.props usage visitor: { ClassDeclaration(path) { if (!isReactComponent(path.node)) return;

rewritePropsUsage(path);

path.replaceWith(template.ast` const ${path.node.id} = (props) => { ${findMethod(path.node, "render")}; }; `); }, }, @NicoloRibaudo 3. Rewriting this.props usage visitor: { class Counter extends Component { ClassDeclaration(path) { render() { if (!isReactComponent(path.node)) return; return

Hi, {this.props.name}! rewritePropsUsage(path);

; } path.replaceWith(template.ast` } const ${path.node.id} = (props) => { ${findMethod(path.node, "render")}; }; `); }, },

@NicoloRibaudo 118 3. Rewriting this.props usage visitor: { class Counter extends Component { ClassDeclaration(path) { render() { if (!isReactComponent(path.node)) return; return

Hi, {this.props.name}! rewritePropsUsage(path);

; } path.replaceWith(template.ast` } const ${path.node.id} = (props) => { ${findMethod(path.node, "render")}; }; `); const Counter = (props) => { }, return

Hi, {props.name}!

; }, };

@NicoloRibaudo 119 4. Convert state = {…} to useState()

class Counter extends Component { state = { count: 0 };

render() { return

Total: {this.state.count}

; } }

@NicoloRibaudo 120 4. Convert state = {…} to useState()

class Counter extends Component { state = { count: 0 };

render() { return

Total: {this.state.count}

; } }

@NicoloRibaudo 121 4. Convert state = {…} to useState()

class Counter extends Component { state = { count: 0 };

render() { return

Total: {this.state.count}

; } }

@NicoloRibaudo 122 4. Convert state = {…} to useState() function findField(node, name) { const elem = node.body.body.find(el => t.isClassProperty(el, { static: false, computed: false }) && t.isIdentifier(el.key, { name }) ); if (elem) return elem.value; }

findField(path.node, "state");

@NicoloRibaudo 123 4. Convert state = {…} to useState()

class Counter extends Component {

state = { count: 0 }; count get : Identifier { "count" } set : Identifier { "setCount" } render() { init : NumericLiteral { 0 } return

Total: {this.state.count} ...

; } }

@NicoloRibaudo 124 4. Convert state = {…} to useState()

@NicoloRibaudo 125 4. Convert state = {…} to useState() function findInitialState(node) { const stateNode = findField(node, "state"); if (!stateNode) return;

}

@NicoloRibaudo 126 4. Convert state = {…} to useState() function findInitialState(node) { const stateNode = findField(node, "state"); if (!stateNode) return;

const state = new Map(); for (const prop of stateNode.properties) {

} return state; }

@NicoloRibaudo 127 4. Convert state = {…} to useState() function findInitialState(node) { const stateNode = findField(node, "state"); if (!stateNode) return;

const state = new Map(); for (const prop of stateNode.properties) { state.set(prop.key.name, { get: t.identifier(prop.key.name), set: t.identifier(`set${upper(prop.key.name)}`), init: prop.value, }); } return state; }

@NicoloRibaudo 128 4. Convert state = {…} to useState()

get : Identifier { … }

set : Identifier { … } init : NumericLiteral { … }

path.replaceWith(template.ast` ... const ${componentId} = (props) => {

${findMethod(path, "render")}; }; `);

@NicoloRibaudo 129 4. Convert state = {…} to useState() const state = findInitialState(path.node);

get : Identifier { … }

set : Identifier { … } init : NumericLiteral { … }

path.replaceWith(template.ast` ... const ${componentId} = (props) => {

${findMethod(path, "render")}; }; `);

@NicoloRibaudo 130 4. Convert state = {…} to useState() const state = findInitialState(path.node); const useStateCalls = []; for (let { get, set, init } of state.values()) get : Identifier { … } useStateCalls.push(template.ast` const [${get}, ${set}] = useState(${init}) set : Identifier { … } `); init : NumericLiteral { … }

path.replaceWith(template.ast` ... const ${componentId} = (props) => {

${findMethod(path, "render")}; }; `);

@NicoloRibaudo 131 4. Convert state = {…} to useState() const state = findInitialState(path.node); const useStateCalls = []; for (let { get, set, init } of state.values()) get : Identifier { … } useStateCalls.push(template.ast` const [${get}, ${set}] = useState(${init}) set : Identifier { … } `); init : NumericLiteral { … }

path.replaceWith(template.ast` ... const ${componentId} = (props) => { ${useStateCalls}; ${findMethod(path, "render")}; }; `);

@NicoloRibaudo 132 4. Convert state = {…} to useState() const state = findInitialState(path.node); class Counter extends Component { const useStateCalls = []; state = { count: 0 }; for (let { get, set, init } of state.values()) render() { useStateCalls.push(template.ast` return /* ... */; const [${get}, ${set}] = useState(${init}) } `); }

path.replaceWith(template.ast` const ${componentId} = (props) => { ${useStateCalls}; ${findMethod(path, "render")}; }; `);

@NicoloRibaudo 133 4. Convert state = {…} to useState() const state = findInitialState(path.node); class Counter extends Component { const useStateCalls = []; state = { count: 0 }; for (let { get, set, init } of state.values()) render() { useStateCalls.push(template.ast` return /* ... */; const [${get}, ${set}] = useState(${init}) } `); }

path.replaceWith(template.ast` const ${componentId} = (props) => { ${useStateCalls}; const Counter = (props) => { ${findMethod(path, "render")}; const [count, setCount] = useState(0); }; return /* ... */; `); };

@NicoloRibaudo 134 4. Convert this.state.count to count

class Counter extends Component { state = { count: 0 }; render() { return

Total: {this.state.count}

; } }

const Counter = (props) => { const [count, setCount] = useState(0); return

Total: {count}

; };

@NicoloRibaudo 135 4. Convert this.setState to setCount

render() { const inc = () => this.setState(prev => ({ count: prev.count + 1 })); return

Total: {this.state.count}

; }

const Counter = (props) => { const [count, setCount] = useState(0); const inc = () => setCount(count => count + 1); return

Total: {count}

; }; @NicoloRibaudo 136 <>

@NicoloRibaudo 137 <> Does our codemod cover every case?

@NicoloRibaudo 138  Ignoring unsupported patterns

@NicoloRibaudo 139  Ignoring unsupported patterns

Complex state initialization

@NicoloRibaudo 140  Complex state initialization class Counter extends Component { state = { count: 0, label: "Likes" }; render() { return

{this.state.label}: {this.state.count}

; } }

@NicoloRibaudo 141  Complex state initialization class Counter extends Component { state = { count: 0, label: "Likes" }; render() { return

{this.state.label}: {this.state.count}

;

} const Counter = (props) => { } const [count, setCount] = useState(0); const [label, setLabel] = useState("Likes");

return

{label}: {count}

; }; @NicoloRibaudo 142  Complex state initialization class Counter extends Component { state = getInitialState(); render() { return

{this.state.label}: {this.state.count}

;

} const Counter = (props) => { } const [count, setCount] = ??????; const [label, setLabel] = ??????;

return

{label}: {count}

; }; @NicoloRibaudo 143  Complex state initialization function findInitialState(node) { const stateNode = findField(node, "state"); if (!stateNode) return;

const state = new Map(); for (const prop of stateNode.properties) { /* ... */ } return state; }

@NicoloRibaudo 144  Complex state initialization function findInitialState(node) { const stateNode = findField(node, "state"); if (!stateNode) return;

const state = new Map(); for (const prop of stateNode.properties) { /* ... */ } return state; }

@NicoloRibaudo 145  Complex state initialization function findInitialState(node) { const stateNode = findField(node, "state"); if (!stateNode) return;

if (!t.isObjectExpression(stateNode)) {

return; }

const state = new Map(); for (const prop of stateNode.properties) { /* ... */

@NicoloRibaudo 146  Complex state initialization function findInitialState(node) { const stateNode = findField(node, "state"); if (!stateNode) return;

if (!t.isObjectExpression(stateNode)) { t.addComment( stateNode.node, "leading", " @warning: Unable to refactor complex state initialization ", ); return; }

const state = new Map(); for (const prop of stateNode.properties) { /* ... */

@NicoloRibaudo 147  Complex state initialization class Counter extends Component { state = getInisialState(); render() { return

{this.state.label}: {this.state.count}

; } }

@NicoloRibaudo 148  Complex state initialization class Counter extends Component { state class= getInisialState Counter extends(); Component { render () state { = return

/* @warning: Unable to refactor complex state initialization */ {this .getInisialStatestate.label}: {this(); .state.count}

; } render() { } return

{this.state.label}: {this.state.count}

; } }

@NicoloRibaudo 149  Analyzing a pattern's frequency

@NicoloRibaudo 150  Analyzing a pattern's frequency

Is it worth to implement support for defaultProps?

class Counter extends Component { static defaultProps = { name: "Nicolò" };

render() { return

Hi, {this.props.name}!

; } } @NicoloRibaudo 151  Analyzing a pattern's frequency

class Person { constructor(name) { this.name = name; }

} class Btn extends Component { render() { return