fusion-cli/build/babel-plugins/babel-plugin-i18n/index.js (129 lines of code) (raw):
/** Copyright (c) 2018 Uber Technologies, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
/* eslint-env node */
const createModuleVisitor = require('../babel-plugin-utils/visit-named-module');
const PACKAGE_NAME = ['fusion-plugin-i18n-react', 'fusion-plugin-i18n-preact'];
const COMPONENT_IDENTIFIER = [
'Translate',
'withTranslations',
'useTranslations',
];
module.exports = i18nPlugin;
/*::
type PluginOpts = {translationIds: Set<string>}
*/
function i18nPlugin(babel /*: Object */, {translationIds} /*: PluginOpts */) {
const t /*: Object */ = babel.types;
const visitor = createModuleVisitor(
t,
COMPONENT_IDENTIFIER,
PACKAGE_NAME,
refsHandler
);
function refsHandler(t, context, refs = [], specifierName) {
refs.forEach(refPath => {
const parentPath = refPath.parentPath;
if (t.isCallExpression(refPath.parent)) {
const firstArg = refPath.parent.arguments[0];
if (specifierName === 'withTranslations') {
const errorMessage =
'The withTranslations hoc must be called with an array of string literal translation keys';
if (!t.isArrayExpression(firstArg)) {
throw parentPath.buildCodeFrameError(errorMessage);
}
const elements = firstArg.elements;
elements.forEach(element => {
if (!t.isStringLiteral(element)) {
throw parentPath.buildCodeFrameError(errorMessage);
}
translationIds.add(element.value);
});
} else if (specifierName === 'useTranslations') {
if (!t.isVariableDeclarator(refPath.parentPath.parent)) {
throw parentPath.buildCodeFrameError(
'Unexpected assignment of useTranslations return function'
);
}
const localName = refPath.parentPath.parent.id.name;
const translationPaths =
refPath.parentPath.scope.bindings[localName].referencePaths;
translationPaths.forEach(translationPath => {
if (
// translate()
t.isCallExpression(translationPath.parentPath) &&
translationPath.parentKey === 'callee'
) {
const arg = translationPath.parentPath.node.arguments[0];
const errorMessage =
'useTranslations result function must be passed string literal or hinted template literal';
if (t.isStringLiteral(arg)) {
translationIds.add(arg.value);
} else if (t.isTemplateLiteral(arg)) {
const literalSections = arg.quasis.map(q => q.value.cooked);
if (literalSections.join('') === '') {
// template literal not hinted, i.e. translate(`${foo}`)
throw translationPath.parentPath.buildCodeFrameError(
errorMessage
);
} else {
translationIds.add(literalSections);
}
} else {
throw translationPath.parentPath.buildCodeFrameError(
errorMessage
);
}
} else if (
// React.useEffect(() => {}, [translate])
t.isArrayExpression(translationPath.parentPath) &&
t.isCallExpression(translationPath.parentPath.parentPath)
) {
const reactHooksWithCallbacks = [
'useEffect',
'useCallback',
'useMemo',
];
const arrayArg = translationPath.parentPath.node;
const hookCall = translationPath.parentPath.parentPath.node;
const isSecondArg =
hookCall.arguments &&
hookCall.arguments.length > 1 &&
hookCall.arguments[1] === arrayArg;
const isValidIdentifierCall =
t.isIdentifier(hookCall.callee) &&
reactHooksWithCallbacks.includes(hookCall.callee.name);
const isValidMemberCall =
t.isMemberExpression(hookCall.callee) &&
reactHooksWithCallbacks.includes(hookCall.callee.property.name);
if (
!(isSecondArg && (isValidIdentifierCall || isValidMemberCall))
) {
throw translationPath.parentPath.buildCodeFrameError(
'Unexpected usage of useTranslations return function'
);
}
} else {
throw translationPath.parentPath.buildCodeFrameError(
'Unexpected usage of useTranslations return function'
);
}
});
}
return;
}
if (!t.isJSXOpeningElement(refPath.parent)) {
return;
}
refPath.parent.attributes.forEach(attr => {
if (!t.isJSXAttribute(attr)) {
return;
}
if (!t.isJSXIdentifier(attr.name)) {
return;
}
if (attr.name.name !== 'id') {
return;
}
if (!t.isStringLiteral(attr.value)) {
throw parentPath.buildCodeFrameError(
'The translate component must have props.id be a string literal.'
);
}
const translationKeyId = attr.value.value;
translationIds.add(translationKeyId);
});
});
}
return {visitor};
}