Skip to content

Commit

Permalink
feat(tx): add MapChild transform with jscodeshift
Browse files Browse the repository at this point in the history
  • Loading branch information
tomchentw committed Sep 13, 2017
1 parent 94104ed commit b63d9ee
Show file tree
Hide file tree
Showing 4 changed files with 339 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ logs
pids
*.pid
*.seed
.cache

# OSX
.DS_Store
Expand Down
85 changes: 85 additions & 0 deletions src/tx/ClassDefinition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { parse } from "url"
import makeFetchHappen from "make-fetch-happen"
import cheerio from "cheerio"
const fetch = makeFetchHappen.defaults({
cacheManager: ".cache/", // path where cache will be written (and read)
})

const KlassName = process.argv[2]

fetch(
"https://developers.google.com/maps/documentation/javascript/3.exp/reference"
)
.then(it => it.text())
.then(it => cheerio.load(it))
.then($ => {
const $content = $(`#${KlassName}`).parent()
return contentToJS(KlassName, $, $content)
})
.then(it => process.stdout.write(JSON.stringify(it)))
.catch(error => {
console.error(error)
process.exit(1)
})

function contentToJS(KlassName, $, $content) {
const constructor = $content
.find(`#${KlassName}`)
.next()
.find("code")
.text()

const $constructorTable = $content.find(
`[summary="class ${KlassName} - Constructor"]`
)
const [, constructorArgs] = $constructorTable
.find(`tr > td > code`)
.text()
.match(/\S+\((.*)\)/)

const $methodsTable = $content.find(
`[summary="class ${KlassName} - Methods"]`
)
const methods = $methodsTable
.find("tbody > tr")
.map((i, tr) => {
const $tr = $(tr)
const [, name, args] = $tr
.find("td:first-child")
.text()
.replace("\n", "")
.match(/(\S+)\((.*)\)/)

return {
name,
args,
returns: $tr.find("td:nth-child(2) > div > code").text(),
returnsDesc: $tr.find("td:nth-child(2) > div.desc").text(),
}
})
.get()

const $eventsTable = $content.find(`[summary="class ${KlassName} - Events"]`)
const events = $eventsTable
.find("tbody > tr")
.map((i, tr) => {
const $tr = $(tr)
const name = $tr.find("td:first-child").text()

return {
name,
args: $tr.find("td:nth-child(2) > div > code").text(),
returnsDesc: $tr.find("td:nth-child(2) > div.desc").text(),
}
})
.get()

return {
constructor: {
name: constructor,
args: constructorArgs,
},
methods,
events,
}
}
226 changes: 226 additions & 0 deletions src/tx/MapChild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import path from "path"
import { execSync } from "child_process"
import _ from "lodash"

function maybeTypeToPropType(maybeType) {
switch (maybeType) {
case "boolean":
return "bool"
case "number":
return maybeType
case "string":
return maybeType
default:
return "any"
}
}

export default function transformer(file, api) {
const j = api.jscodeshift
const wrap = j(file.source)

const exportConfig = wrap.find(j.ExportNamedDeclaration).at(0)
const configString = exportConfig.find(j.TemplateElement).get().node.value.raw
const {
prohibitedPropNames = [],
eventMapOverrides,
getInstanceFromComponent,
} = JSON.parse(configString)
exportConfig.remove()
const eventNamesOverrides = _.values(eventMapOverrides)

const KlassName = wrap
.find(j.ClassDeclaration)
.at(0)
.get().node.id.name

const result = execSync(
`./node_modules/.bin/babel-node ${path.resolve(
__dirname,
`./ClassDefinition.js`
)} "${KlassName}"`,
{
encoding: "utf-8",
}
)
const { constructor, methods, events } = JSON.parse(result)
const methodAsProps = methods.filter(({ name }) => {
const matchResult = name.match(/^set(\S+)/)
return (
name !== "setMap" &&
matchResult &&
!_.includes(prohibitedPropNames, _.lowerFirst(matchResult[1]))
)
})

const publicMethods = methods.filter(({ name }) => {
const matchResult = name.match(/^get(\S+)/)
return (
!name.match(/Map$/) &&
matchResult &&
!_.includes(prohibitedPropNames, _.lowerFirst(matchResult[1]))
)
})

wrap.find(j.ClassBody).forEach(path => {
j(path).replaceWith(
Object.assign(path.node.__clone(), {
body: [...path.node.body, ...txClassMethods()],
})
)
})

wrap.find(j.ObjectExpression).forEach(path => {
if (_.get(path, "parentPath.node.key.name") === "propTypes") {
j(path).replaceWith(
Object.assign(path.node.__clone(), {
properties: [
...path.node.properties.filter(
({ key: { name } }) => name !== "__jscodeshiftPlaceholder__"
),
...txPropTypes(),
],
})
)
} else if (_.get(path, "parentPath.node.id.name") === "eventMap") {
j(path).replaceWith(
Object.assign(path.node.__clone(), {
properties: [...path.node.properties, ...eventMap()],
})
)
} else if (_.get(path, "parentPath.node.id.name") === "updaterMap") {
j(path).replaceWith(
Object.assign(path.node.__clone(), {
properties: [...path.node.properties, ...updaterMap()],
})
)
}
})

return wrap.toSource()

function txPropTypes() {
return [
...methodAsProps.map(({ name, args, desc }) => {
const [, prop] = name.match(/^set(\S+)/)
const [, maybeType] = args.match(/\S+:(\S+)/)

return Object.assign(
j.objectProperty(
j.identifier(`default${prop}`),
j.identifier(`PropTypes.${maybeTypeToPropType(maybeType)}`)
),
{
comments: [j.commentBlock(`*\n * @type ${maybeType}\n `, true)],
}
)
}),
...methodAsProps.map(({ name, args, desc }) => {
const [, prop] = name.match(/^set(\S+)/)
const [, maybeType] = args.match(/\S+:(\S+)/)

return Object.assign(
j.objectProperty(
j.identifier(_.lowerFirst(prop)),
j.identifier(`PropTypes.${maybeTypeToPropType(maybeType)}`)
),
{
comments: [j.commentBlock(`*\n * @type ${maybeType}\n `, true)],
}
)
}),

..._.map(eventMapOverrides, (eventName, callbackName) =>
Object.assign(
j.objectProperty(
j.identifier(callbackName),
j.identifier(`PropTypes.func`)
),
{
comments: [j.commentBlock(`*\n * function\n `, true)],
}
)
),
...events
.filter(({ name }) => !_.includes(eventNamesOverrides, name))
.map(({ name }) =>
Object.assign(
j.objectProperty(
j.identifier(_.camelCase(`on${_.capitalize(name)}`)),
j.identifier(`PropTypes.func`)
),
{
comments: [j.commentBlock(`*\n * function\n `, true)],
}
)
),
]
}

function txClassMethods() {
return [
...publicMethods.map(({ name, returns, returnsDesc }) => {
return Object.assign(
j.classMethod(
"method",
j.identifier(name),
[],
j.blockStatement([
j.returnStatement(
j.callExpression(
j.identifier(`${getInstanceFromComponent}.${name}`),
[]
)
),
])
),
{
comments: [
j.commentBlock(
`*\n * ${returnsDesc}\n * @type ${returns}\n `,
true
),
],
}
)
}),
]
}

function eventMap() {
return [
..._.map(eventMapOverrides, (eventName, callbackName) =>
j.objectProperty(j.identifier(callbackName), j.stringLiteral(eventName))
),
...events
.filter(({ name }) => !_.includes(eventNamesOverrides, name))
.map(({ name }) =>
j.objectProperty(
j.identifier(_.camelCase(`on${_.capitalize(name)}`)),
j.stringLiteral(name)
)
),
]
}

function updaterMap() {
return [
...methodAsProps.map(({ name, args, desc }) => {
const [, prop] = name.match(/^set(\S+)/)

return j.objectMethod(
"method",
j.identifier(_.lowerFirst(prop)),
[j.identifier("instance"), j.identifier(_.lowerFirst(prop))],
j.blockStatement([
j.expressionStatement(
j.callExpression(j.identifier(`instance.${name}`), [
j.identifier(_.lowerFirst(prop)),
])
),
])
)
}),
]
}
}
27 changes: 27 additions & 0 deletions src/tx/jscodeshift.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import path from "path"
import fs from "fs"
import glob from "glob"
import mkdirp from "mkdirp"
import jscodeshift from "jscodeshift"

import tx from "./MapChild"

const relativeToCwd = it => path.relative(process.cwd(), it)

const files = glob.sync("**/*.jsx", {
cwd: path.resolve(__dirname, "../macros/"),
ignore: "*.spec.jsx",
})
// const files = ["Marker.jsx", "GoogleMap.jsx"]
files.map(it => {
const filename = path.resolve(__dirname, "../macros/", it)
const nextFilename = path.resolve(__dirname, "../components/", it)
console.log(
`Generating ${relativeToCwd(nextFilename)} from ${relativeToCwd(filename)}`
)

const source = fs.readFileSync(filename, "utf8")
const output = tx({ source }, { jscodeshift })
mkdirp.sync(path.dirname(nextFilename))
fs.writeFileSync(nextFilename, output)
})

0 comments on commit b63d9ee

Please sign in to comment.