--
- Software engineer at Reaktor Amsterdam
- Writing Node.js, React & TypeScript by day
- Tinkering with compilers by night
- github.com/tkers
--
- Also called program transformations
- Metaprogramming
- Automatise programming / refactoring
- Can be applied to your entire code base
- Modifies program using its AST
--
Function name is updated:
export {
- foo
+ bar
}
Find and replace πͺ
import {
- foo
+ bar
} from './myLib.js'
// ...
- foo()
+ bar()
--
No access to the semantics of your code:
import { foo as fooA } from './myLib.js'
import { foo as fooB } from './otherLib.js'
obj.foo = 42
sendMessage('foo')
--
Binding manually:
class MyComponent extends React.Component {
constructor() {
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({ open: true })
}
}
Using arrow function as class property:
class MyComponent extends React.Component {
handleClick = () => this.setState({ open: true })
}
Modify the program instead of text!
--
class MyComponent extends React.Component {
constructor() {
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({ open: true })
}
}
- ClassDeclaration
MyComponent
- MethodDefinition
constructor
- AssignmentExpression
=
- MemberExpression
this.handleClick
- CallExpression
bind()
- MemberExpression
- AssignmentExpression
- MethodDefinition
handleClick
- CallExpression
setState()
- CallExpression
- MethodDefinition
--
--
Tools using the AST:
- ESLint reads (and reports issues)
- Prettier reprints (ignoring whitespaces)
- Babel transforms (ES6 to ES5)
- ...
--
- AST transformation tool
- Wrapper around Recast
- Preserves formatting
- Update React APIs
--
export default function transform(file, api) {
const j = api.jscodeshift
const root = j(file.source)
// ...transform root
return root.toSource()
}
--
- 100 KLOC
- 50 developers
- 20 merges/day
- A lot of time pressure π©
--
State assignment in constructor:
class MyComponent extends React.Component {
constructor() {
this.state = {
foo: 'hello',
bar: 42
}
}
}
State as class property:
class MyComponent extends React.Component {
state = {
foo: 'hello',
bar: 42
}
}
--
export default function transform(file, api) {
const j = api.jscodeshift
const root = j(file.source)
root
.find(j.ClassDeclaration)
.find(j.MethodDefinition, { kind: 'constructor' })
.find(j.AssignmentExpression, {
left: {
object: j.ThisExpression,
property: { name: 'state' }
}
})
.forEach(stateAssignment => {
const initialState = stateAssignment.node.right
const asProperty = j.classProperty(j.identifier('state'), initialState, null, false)
j(stateAssignment)
.closest(j.ClassBody)
.get('body')
.insertAt(0, asProperty)
j(stateAssignment).remove()
})
return root.toSource()
}
--
Almost what we wanted:
class MyComponent extends React.Component {
state = {
foo: 'hello',
bar: 42
}
constructor() {}
}
--
- Remove empty constructor
- Compose with other transforms
- astexplorer.net
--
- Manual
git diff
check - Automated tests
--
- Documentation is scarce
- JSCodeshift API a bit confusing
- JavaScript is messy
- A lot of edge cases
- Focus on the 99%
--
- Wow-factor
- Updating function interfaces
- Postpone design decisions
-- dark