diff --git a/src/index.ts b/src/index.ts index 91d41ef0..aee95c13 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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, 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, 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 - - 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 { diff --git a/src/getPrefix.ts b/src/utils/getPrefix.ts similarity index 93% rename from src/getPrefix.ts rename to src/utils/getPrefix.ts index 917c43b3..8d3f07cc 100644 --- a/src/getPrefix.ts +++ b/src/utils/getPrefix.ts @@ -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 = ( { diff --git a/src/utils.ts b/src/utils/index.ts similarity index 100% rename from src/utils.ts rename to src/utils/index.ts diff --git a/src/isImportLocalName.ts b/src/utils/isImportLocalName.ts similarity index 96% rename from src/isImportLocalName.ts rename to src/utils/isImportLocalName.ts index 043fcafe..7b5afdb3 100644 --- a/src/isImportLocalName.ts +++ b/src/utils/isImportLocalName.ts @@ -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, diff --git a/src/visitors/addIdToDefineMessage.ts b/src/visitors/addIdToDefineMessage.ts index adade8f6..f7a5a4ff 100644 --- a/src/visitors/addIdToDefineMessage.ts +++ b/src/visitors/addIdToDefineMessage.ts @@ -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) => { diff --git a/src/visitors/addIdToFormatMessage.ts b/src/visitors/addIdToFormatMessage.ts new file mode 100644 index 00000000..0e1a05eb --- /dev/null +++ b/src/visitors/addIdToFormatMessage.ts @@ -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, 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, + 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 + + 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))) + ) + } + } +} diff --git a/src/visitors/jsx.ts b/src/visitors/jsx.ts index f1f2e046..95095cf7 100644 --- a/src/visitors/jsx.ts +++ b/src/visitors/jsx.ts @@ -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