Skip to content
This repository was archived by the owner on May 4, 2020. It is now read-only.

Commit 8549258

Browse files
fsmaialonglho
andauthored
feat(babel-plugin-react-intl): add destructured formatMessage su… (#582)
Co-authored-by: Long ͭ̊Ho <[email protected]>
1 parent 8693456 commit 8549258

File tree

5 files changed

+160
-5
lines changed

5 files changed

+160
-5
lines changed

packages/babel-plugin-react-intl/src/index.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import * as p from 'path';
88
import {outputJSONSync} from 'fs-extra';
99
import {parse} from 'intl-messageformat-parser/dist';
1010
const {declare} = require('@babel/helper-plugin-utils') as any;
11-
import {types as t, PluginObj} from '@babel/core';
11+
import {PluginObj, types as t} from '@babel/core';
12+
1213
import {
1314
ObjectExpression,
1415
JSXAttribute,
1516
StringLiteral,
1617
JSXIdentifier,
1718
JSXExpressionContainer,
1819
Identifier,
20+
ObjectPattern,
1921
ObjectProperty,
2022
SourceLocation,
2123
Expression,
@@ -24,7 +26,7 @@ import {
2426
isTypeCastExpression,
2527
isTSTypeAssertion,
2628
} from '@babel/types';
27-
import {NodePath} from '@babel/traverse';
29+
import {NodePath, Scope} from '@babel/traverse';
2830
import validate from 'schema-utils';
2931
import OPTIONS_SCHEMA from './options.schema.json';
3032
import {OptionsSchema} from './options.js';
@@ -257,9 +259,40 @@ function referencesImport(
257259
return importedNames.some(name => path.referencesImport(mod, name));
258260
}
259261

262+
function isFormatMessageDestructuring(scope: Scope) {
263+
const binding = scope.getBinding('formatMessage');
264+
const block = scope.block as t.FunctionDeclaration;
265+
266+
// things like `const {formatMessage} = intl; formatMessage(...)`
267+
if (binding && t.isVariableDeclarator(binding.path.node)) {
268+
const nodeObject = binding.path.node.id as ObjectPattern;
269+
return nodeObject.properties.find(
270+
(value: any) => value.key.name === 'intl'
271+
);
272+
}
273+
274+
// things like const fn = ({ intl: { formatMessage }}) => { formatMessage(...) }
275+
if (t.isObjectPattern(block.params[0])) {
276+
return block.params[0].properties.find(
277+
(value: any) => value.key.name === 'intl'
278+
);
279+
}
280+
281+
return false;
282+
}
283+
260284
function isFormatMessageCall(
261-
callee: NodePath<Expression | V8IntrinsicIdentifier>
285+
callee: NodePath<Expression | V8IntrinsicIdentifier>,
286+
path: any
262287
) {
288+
if (
289+
callee.isIdentifier() &&
290+
callee.node.name === 'formatMessage' &&
291+
isFormatMessageDestructuring(path.scope)
292+
) {
293+
return true;
294+
}
295+
263296
if (!callee.isMemberExpression()) {
264297
return false;
265298
}
@@ -583,7 +616,7 @@ export default declare((api: any, options: OptionsSchema) => {
583616
}
584617

585618
// Check that this is `intl.formatMessage` call
586-
if (extractFromFormatMessageCall && isFormatMessageCall(callee)) {
619+
if (extractFromFormatMessageCall && isFormatMessageCall(callee, path)) {
587620
const messageDescriptor = path.get('arguments')[0];
588621
if (messageDescriptor.isObjectExpression()) {
589622
processMessageObject(messageDescriptor);

packages/babel-plugin-react-intl/test/__snapshots__/index.test.ts.snap

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,65 @@ Array [
334334
]
335335
`;
336336

337+
exports[`emit asserts for: output match: extractFromFormatMessageCallStateless 1`] = `
338+
Object {
339+
"messages": Array [
340+
Object {
341+
"defaultMessage": "bar",
342+
"description": "baz",
343+
"id": "foo",
344+
},
345+
],
346+
}
347+
`;
348+
349+
exports[`emit asserts for: output match: extractFromFormatMessageCallStateless 2`] = `
350+
"\\"use strict\\";
351+
352+
Object.defineProperty(exports, \\"__esModule\\", {
353+
value: true
354+
});
355+
exports.default = void 0;
356+
357+
var _reactIntl = require(\\"react-intl\\");
358+
359+
var _react = _interopRequireDefault(require(\\"react\\"));
360+
361+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
362+
363+
const Foo = ({
364+
intl: {
365+
formatMessage
366+
}
367+
}) => {
368+
const msgs = {
369+
qux: formatMessage({
370+
id: 'foo.bar.quux',
371+
defaultMessage: 'Hello Stateless!',
372+
description: 'A stateless message'
373+
})
374+
};
375+
return _react.default.createElement(\\"div\\", null, _react.default.createElement(\\"h1\\", null, msgs.header), _react.default.createElement(\\"p\\", null, msgs.content), _react.default.createElement(\\"span\\", null, _react.default.createElement(_reactIntl.FormattedMessage, {
376+
id: \\"foo\\",
377+
defaultMessage: \\"bar\\"
378+
})));
379+
};
380+
381+
var _default = (0, _reactIntl.injectIntl)(Foo);
382+
383+
exports.default = _default;"
384+
`;
385+
386+
exports[`emit asserts for: output match: extractFromFormatMessageCallStateless 3`] = `
387+
Array [
388+
Object {
389+
"defaultMessage": "bar",
390+
"description": "baz",
391+
"id": "foo",
392+
},
393+
]
394+
`;
395+
337396
exports[`emit asserts for: output match: formatMessageCall 1`] = `
338397
Object {
339398
"messages": Array [
@@ -582,6 +641,21 @@ Array [
582641
"description": "Another message",
583642
"id": "foo.bar.biff",
584643
},
644+
Object {
645+
"defaultMessage": "Hello Stranger!",
646+
"description": "A different message",
647+
"id": "foo.bar.qux",
648+
},
649+
Object {
650+
"defaultMessage": "bar",
651+
"description": "baz",
652+
"id": "foo",
653+
},
654+
]
655+
`;
656+
657+
exports[`options respects extractFromFormatMessageCall from stateless components 1`] = `
658+
Array [
585659
Object {
586660
"defaultMessage": "bar",
587661
"description": "baz",

packages/babel-plugin-react-intl/test/fixtures/extractFromFormatMessageCall/actual.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import {FormattedMessage, injectIntl} from 'react-intl';
12
import React, {Component} from 'react';
2-
import {injectIntl, FormattedMessage} from 'react-intl';
33

44
const objectPointer = {
55
id: 'foo.bar.invalid',
@@ -10,6 +10,9 @@ const objectPointer = {
1010
class Foo extends Component {
1111
render() {
1212
const {intl} = this.props;
13+
const {
14+
intl: {formatMessage},
15+
} = this.props;
1316
const msgs = {
1417
baz: this.props.intl.formatMessage({
1518
id: 'foo.bar.baz',
@@ -21,6 +24,11 @@ class Foo extends Component {
2124
defaultMessage: 'Hello Nurse!',
2225
description: 'Another message',
2326
}),
27+
qux: formatMessage({
28+
id: 'foo.bar.qux',
29+
defaultMessage: 'Hello Stranger!',
30+
description: 'A different message',
31+
}),
2432
invalid: this.props.intl.formatMessage(objectPointer),
2533
};
2634

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {FormattedMessage, injectIntl} from 'react-intl';
2+
3+
import React from 'react';
4+
5+
const Foo = ({intl: {formatMessage}}) => {
6+
const msgs = {
7+
qux: formatMessage({
8+
id: 'foo.bar.quux',
9+
defaultMessage: 'Hello Stateless!',
10+
description: 'A stateless message',
11+
}),
12+
};
13+
14+
return (
15+
<div>
16+
<h1>{msgs.header}</h1>
17+
<p>{msgs.content}</p>
18+
<span>
19+
<FormattedMessage id="foo" defaultMessage="bar" description="baz" />
20+
</span>
21+
</div>
22+
);
23+
};
24+
25+
export default injectIntl(Foo);

packages/babel-plugin-react-intl/test/index.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,21 @@ describe('options', () => {
173173
expect(require(join(fixtureDir, 'actual.json'))).toMatchSnapshot();
174174
});
175175

176+
it('respects extractFromFormatMessageCall from stateless components', () => {
177+
const fixtureDir = join(
178+
fixturesDir,
179+
'extractFromFormatMessageCallStateless'
180+
);
181+
expect(() =>
182+
transform(join(fixtureDir, 'actual.js'), {
183+
extractFromFormatMessageCall: true,
184+
})
185+
).not.toThrow();
186+
187+
// Check message output
188+
expect(require(join(fixtureDir, 'actual.json'))).toMatchSnapshot();
189+
});
190+
176191
it('additionalComponentNames', () => {
177192
const fixtureDir = join(fixturesDir, 'additionalComponentNames');
178193
expect(() =>

0 commit comments

Comments
 (0)