tomchentw/react-google-maps

View on GitHub
src/tx/MapChild.js

Summary

Maintainability
D
1 day
Test Coverage
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 = [],
    KlassNameOverrride,
    eventMapOverrides,
    getInstanceFromComponent,
  } = JSON.parse(configString)
  exportConfig.remove()
  const eventNamesOverrides = _.values(eventMapOverrides)

  const KlassName =
    KlassNameOverrride ||
    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.Program).forEach(path => {
    j(path).replaceWith({
      ...path.node.__clone(),
      body: [
        {
          ...path.node.body[0],
          comments: [
            j.commentBlock(AUTO_GENERATED_HEADER, true),
            ...(path.node.body[0].comments || []),
          ],
        },
        ...path.node.body.slice(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 * @public \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)),
              ])
            ),
          ])
        )
      }),
    ]
  }
}

const AUTO_GENERATED_HEADER = `
 * -----------------------------------------------------------------------------
 * This file is auto-generated from the corresponding file at \`src/macros/\`.
 * Please **DO NOT** edit this file directly when creating PRs.
 * -----------------------------------------------------------------------------
 `