Skip to content

Commit

Permalink
feat(babel-plugin-formatjs): add additionalFunctionNames feature to e…
Browse files Browse the repository at this point in the history
…xtract from custom function calls
  • Loading branch information
longlho committed Jan 4, 2021
1 parent 385694f commit 043ac50
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 46 deletions.
53 changes: 13 additions & 40 deletions packages/babel-plugin-formatjs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
isTSTypeAssertion,
TemplateLiteral,
} from '@babel/types';
import {NodePath, Scope} from '@babel/traverse';
import {NodePath} from '@babel/traverse';
import {validate} from 'schema-utils';
import * as OPTIONS_SCHEMA from './options.schema.json';
import {OptionsSchema} from './options';
Expand Down Expand Up @@ -281,47 +281,16 @@ function referencesImport(
return importedNames.some(name => path.referencesImport(mod, name));
}

function isFormatMessageDestructuring(scope: Scope) {
const binding = scope.getBinding('formatMessage');
const {block} = scope;
const declNode = binding?.path.node;
// things like `const {formatMessage} = intl; formatMessage(...)`
if (t.isVariableDeclarator(declNode)) {
// things like `const {formatMessage} = useIntl(); formatMessage(...)`
if (t.isCallExpression(declNode.init)) {
if (t.isIdentifier(declNode.init.callee)) {
return declNode.init.callee.name === 'useIntl';
}
}
return (
t.isObjectPattern(declNode.id) &&
declNode.id.properties.find((value: any) => value.key.name === 'intl')
);
}

// things like const fn = ({ intl: { formatMessage }}) => { formatMessage(...) }
if (
t.isFunctionDeclaration(block) &&
block.params.length &&
t.isObjectPattern(block.params[0])
) {
return block.params[0].properties.find(
(value: any) => value.key.name === 'intl'
);
}

return false;
}

function isFormatMessageCall(
callee: NodePath<Expression | V8IntrinsicIdentifier>,
path: NodePath<any>
additionalFunctionNames: string[]
) {
if (
callee.isIdentifier() &&
callee.node.name === 'formatMessage' &&
isFormatMessageDestructuring(path.scope)
) {
const fnNames = new Set([
'formatMessage',
'$formatMessage',
...additionalFunctionNames,
]);
if (callee.isIdentifier() && fnNames.has(callee.node.name)) {
return true;
}

Expand Down Expand Up @@ -593,6 +562,7 @@ export default declare((api: any, options: OptionsSchema) => {
idInterpolationPattern,
removeDefaultMessage,
extractFromFormatMessageCall,
additionalFunctionNames = [],
ast,
} = opts;
const callee = path.get('callee');
Expand Down Expand Up @@ -675,7 +645,10 @@ export default declare((api: any, options: OptionsSchema) => {
}

// Check that this is `intl.formatMessage` call
if (extractFromFormatMessageCall && isFormatMessageCall(callee, path)) {
if (
extractFromFormatMessageCall &&
isFormatMessageCall(callee, additionalFunctionNames)
) {
const messageDescriptor = path.get('arguments')[0];
if (messageDescriptor.isObjectExpression()) {
processMessageObject(messageDescriptor);
Expand Down
1 change: 1 addition & 0 deletions packages/babel-plugin-formatjs/options.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"idInterpolationPattern": {"type": "string"},
"removeDefaultMessage": {"type": "boolean"},
"additionalComponentNames": {"type": "array", "items": {"type": "string"}},
"additionalFunctionNames": {"type": "array", "items": {"type": "string"}},
"pragma": {"type": "string"},
"extractFromFormatMessageCall": {"type": "boolean"},
"extractSourceLocation": {"type": "boolean"},
Expand Down
1 change: 1 addition & 0 deletions packages/babel-plugin-formatjs/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface OptionsSchema {
idInterpolationPattern?: string;
removeDefaultMessage?: boolean;
additionalComponentNames?: string[];
additionalFunctionNames?: string[];
pragma?: string;
extractFromFormatMessageCall?: boolean;
extractSourceLocation?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,45 @@ export default class Foo extends Component {
}
`;

exports[`emit asserts for: additionalFunctionNames 1`] = `
Object {
"code": "// @react-intl project:foo
import React, { Component } from 'react';
function CustomMessage() {}
export default class Foo extends Component {
render() {
return <CustomMessage id={formatMessage({
\\"id\\": \\"acbd1\\",
\\"defaultMessage\\": \\"foo\\"
})} description={$formatMessage({
\\"id\\": \\"92e00\\",
\\"defaultMessage\\": \\"foo2\\"
})} defaultMessage=\\"Hello World!\\" />;
}
}",
"data": Object {
"messages": Array [
Object {
"defaultMessage": "foo",
"description": undefined,
"id": "acbd1",
},
Object {
"defaultMessage": "foo2",
"description": undefined,
"id": "92e00",
},
],
"meta": Object {
"project": "foo",
},
},
}
`;

exports[`emit asserts for: ast 1`] = `
Object {
"code": "import React, { Component } from 'react';
Expand Down Expand Up @@ -477,9 +516,8 @@ function myFunction(param1, {
formatDate
}) {
return formatMessage({
id: 'inline1',
defaultMessage: 'Hello params!',
description: 'A stateless message'
\\"id\\": \\"inline1\\",
\\"defaultMessage\\": \\"Hello params!\\"
}) + formatDate(new Date());
}
Expand All @@ -502,9 +540,8 @@ const Foo = ({
}) => {
const msgs = {
qux: formatMessage({
id: 'foo.bar.quux',
defaultMessage: 'Hello <b>Stateless!</b>',
description: 'A stateless message'
\\"id\\": \\"foo.bar.quux\\",
\\"defaultMessage\\": \\"Hello <b>Stateless!</b>\\"
})
};
return <div>
Expand All @@ -519,11 +556,21 @@ const Foo = ({
export default injectIntl(Foo);",
"data": Object {
"messages": Array [
Object {
"defaultMessage": "Hello params!",
"description": "A stateless message",
"id": "inline1",
},
Object {
"defaultMessage": "hook <b>foo</b>",
"description": "hook",
"id": "hook",
},
Object {
"defaultMessage": "Hello <b>Stateless!</b>",
"description": "A stateless message",
"id": "foo.bar.quux",
},
Object {
"defaultMessage": "bar",
"description": "baz",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// @react-intl project:foo
import React, {Component} from 'react';

function CustomMessage() {}

export default class Foo extends Component {
render() {
return (
<CustomMessage
id={formatMessage({
defaultMessage: 'foo',
})}
description={$formatMessage({
defaultMessage: 'foo2',
})}
defaultMessage="Hello World!"
/>
);
}
}
4 changes: 4 additions & 0 deletions packages/babel-plugin-formatjs/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const TESTS: Record<string, OptionsSchema> = {
additionalComponentNames: {
additionalComponentNames: ['CustomMessage'],
},
additionalFunctionNames: {
additionalFunctionNames: [],
extractFromFormatMessageCall: true,
},
ast: {
ast: true,
extractFromFormatMessageCall: true,
Expand Down
4 changes: 4 additions & 0 deletions website/docs/tooling/babel-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Opt-in to compile `intl.formatMessage` call with the same restrictions, e.g: has

Additional component names to extract messages from, e.g: `['FormattedFooBarMessage']`. **NOTE**: By default we check for the fact that `FormattedMessage` are imported from `moduleSourceName` to make sure variable alias works. This option does not do that so it's less safe.

### **`additionalFunctionNames`**

Additional function names to extract messages from, e.g: `['$formatMessage']`. Use this if you prefer to alias `formatMessage` to something shorter like `$t`.

### **`pragma`**

parse specific additional custom pragma. This allows you to tag certain file with metadata such as `project`. For example with this file:
Expand Down

0 comments on commit 043ac50

Please sign in to comment.