Skip to content

Commit

Permalink
refactor: addIdToFormatMessage
Browse files Browse the repository at this point in the history
  • Loading branch information
akameco committed Sep 6, 2019
1 parent 86b0b26 commit 61602a6
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 79 deletions.
74 changes: 2 additions & 72 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,8 @@
import * as t from '@babel/types'
import { NodePath, PluginObj } from '@babel/core'
import { PluginObj } from '@babel/core'
import { State } from './types'
import { isImportLocalName } from './isImportLocalName'
import { visitJSXElement } from './visitors/jsx'
import { addIdToDefineMessage } from './visitors/addIdToDefineMessage'
import { createHash, objectProperty, getObjectProperties } from './utils'
import { getPrefix } from './getPrefix'
// import blog from 'babel-log'

// check if given path is related to intl.formatMessage call
function isFormatMessageCall(path: NodePath<t.CallExpression>, state: State) {
const callee = path.get('callee')
const property = callee.get('property')
const objectPath = callee.get('object')

return (
// injectIntl or useIntl imported
isImportLocalName(null, ['injectIntl', 'useIntl'], state) &&
callee.isMemberExpression() &&
Boolean(path.get('arguments.0')) &&
// intl object
objectPath &&
t.isIdentifier(objectPath) &&
(objectPath as any).node.name === 'intl' &&
// formatMessage property
property &&
t.isIdentifier(property) &&
(property as any).node.name === 'formatMessage'
)
}

// add automatic ID to intl.formatMessage calls
function addIdToFormatMessage(path: NodePath<t.CallExpression>, state: State) {
if (!isFormatMessageCall(path, state)) {
// skip path if this is not intl.formatMessage call
return
}

// intl.formatMessage first argument is the one which we would like to modify
const arg0 = path.get('arguments.0') as NodePath<t.ObjectExpression>

const properties = getObjectProperties(arg0)
// blog(properties)

// at least defaultMessage property is required to do anything useful
if (!properties) {
return
}

// if "id" property is already added by a developer or by this script just skip this node
if (properties.find((arg: any) => arg.get('key').node.name === 'id')) {
return
}

// Apparently a bug in eslint
// eslint-disable-next-line no-unused-vars
for (const prop of properties) {
if ((prop.get('key') as any).node.name === 'defaultMessage') {
// try to statically evaluate defaultMessage to generate hash
const evaluated = prop.get('value').evaluate()

if (!evaluated.confident || typeof evaluated.value !== 'string') {
throw prop
.get('value')
.buildCodeFrameError(
'[React Intl Auto] defaultMessage must be statically evaluate-able for extraction.'
)
}
prop.insertAfter(
objectProperty('id', getPrefix(state, createHash(evaluated.value)))
)
}
}
}
import { addIdToFormatMessage } from './visitors/addIdToFormatMessage'

export default function() {
return {
Expand Down
4 changes: 2 additions & 2 deletions src/getPrefix.ts → src/utils/getPrefix.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { relative, dirname, sep } from 'path'
import { State } from './types'
import { dotPath } from './utils'
import { State } from '../types'
import { dotPath } from '.'

export const getPrefix = (
{
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NodePath } from '@babel/core'
import * as t from '@babel/types'
import { State } from './types'
import { State } from '../types'

export const isImportLocalName = (
name: string | null | undefined,
Expand Down
4 changes: 2 additions & 2 deletions src/visitors/addIdToDefineMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { NodePath } from '@babel/core'
import * as t from '@babel/types'
import { State } from '../types'
import { dotPath, objectProperty, getObjectProperties } from '../utils'
import { isImportLocalName } from '../isImportLocalName'
import { getPrefix } from '../getPrefix'
import { isImportLocalName } from '../utils/isImportLocalName'
import { getPrefix } from '../utils/getPrefix'
// import blog from 'babel-log'

const getId = (path: NodePath, prefix: string) => {
Expand Down
101 changes: 101 additions & 0 deletions src/visitors/addIdToFormatMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { NodePath } from '@babel/core'
import * as t from '@babel/types'
import { State } from '../types'
import { getPrefix } from '../utils/getPrefix'
import { isImportLocalName } from '../utils/isImportLocalName'
import { createHash, objectProperty, getObjectProperties } from '../utils'
// import blog from 'babel-log'

// check if given path is related to intl.formatMessage call
function isFormatMessageCall(path: NodePath<t.CallExpression>, state: State) {
// injectIntl or useIntl imported
if (!isImportLocalName(null, ['injectIntl', 'useIntl'], state)) {
return false
}

/*
Path "MemberExpression"
computed: false
object: Node "Identifier"
name: "intl"
property: Node "Identifier"
name: "formatMessage"
*/
const callee = path.get('callee')
if (!callee.isMemberExpression()) {
return false
}

/*
Path "Identifier"
name: "formatMessage"
*/
const property = callee.get('property')
const isFormatMessage =
!Array.isArray(property) &&
property.isIdentifier() &&
property.node.name === 'formatMessage'

/*
Path "Identifier"
name: "intl"
*/
const objectPath = callee.get('object')
const isIntl =
!Array.isArray(objectPath) &&
objectPath.isIdentifier() &&
objectPath.node.name === 'intl'

return Boolean(path.get('arguments.0')) && isIntl && isFormatMessage
}

// add automatic ID to intl.formatMessage calls
export function addIdToFormatMessage(
path: NodePath<t.CallExpression>,
state: State
) {
if (!isFormatMessageCall(path, state)) {
// skip path if this is not intl.formatMessage call
return
}

// intl.formatMessage first argument is the one which we would like to modify
const arg0 = path.get('arguments.0') as NodePath<t.ObjectExpression>

const properties = getObjectProperties(arg0)
// blog(properties)

// at least defaultMessage property is required to do anything useful
if (!properties) {
return
}

// if "id" property is already added by a developer or by this script just skip this node
if (
properties.find(arg => {
const keyPath = arg.get('key')
return !Array.isArray(keyPath) && keyPath.node.name === 'id'
})
) {
return
}

for (const prop of properties) {
const keyPath = prop.get('key')
if (!Array.isArray(keyPath) && keyPath.node.name === 'defaultMessage') {
// try to statically evaluate defaultMessage to generate hash
const evaluated = prop.get('value').evaluate()

if (!evaluated.confident || typeof evaluated.value !== 'string') {
throw prop
.get('value')
.buildCodeFrameError(
'[React Intl Auto] defaultMessage must be statically evaluate-able for extraction.'
)
}
prop.insertAfter(
objectProperty('id', getPrefix(state, createHash(evaluated.value)))
)
}
}
}
4 changes: 2 additions & 2 deletions src/visitors/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { NodePath } from '@babel/core'
import * as t from '@babel/types'
import { State } from '../types'
import { createHash } from '../utils'
import { isImportLocalName } from '../isImportLocalName'
import { getPrefix } from '../getPrefix'
import { isImportLocalName } from '../utils/isImportLocalName'
import { getPrefix } from '../utils/getPrefix'
// import blog from 'babel-log'

// Process react-intl components
Expand Down

0 comments on commit 61602a6

Please sign in to comment.