Skip to content

Commit

Permalink
feat(@formatjs/swc-plugin): support using object as description in me…
Browse files Browse the repository at this point in the history
…ssage
  • Loading branch information
longlho committed Jan 20, 2022
1 parent d463673 commit 914f766
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 27 deletions.
72 changes: 52 additions & 20 deletions packages/swc-plugin/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export type MetaExtractor = (
export type InterpolateNameFn = (
id?: string,
defaultMessage?: string,
description?: string,
description?: string | object,
filePath?: string
) => string

Expand Down Expand Up @@ -240,6 +240,29 @@ function isMultipleMessageDecl(node: CallExpression) {
)
}

function literalToPrimitive(n: Expression) {
if (n.type === 'StringLiteral') {
return n.value
}
if (n.type === 'BooleanLiteral') {
return n.value
}
if (n.type === 'NumericLiteral') {
return n.value
}
}

function expressionToObject(obj: ObjectExpression) {
return obj.properties.reduce((all: Record<string, any>, prop) => {
if (prop.type === 'KeyValueProperty') {
if (prop.key.type === 'Identifier' || prop.key.type === 'StringLiteral') {
all[prop.key.value] = literalToPrimitive(prop.value)
}
}
return all
}, {})
}

function isSingularMessageDecl(
node: CallExpression | JSXOpeningElement,
additionalComponentNames: string[]
Expand Down Expand Up @@ -317,10 +340,12 @@ function extractMessageDescriptor(
| StringLiteral
| JSXExpressionContainer
| BinaryExpression
| ObjectExpression
| undefined =
prop.type === 'KeyValueProperty' &&
(prop.value.type === 'StringLiteral' ||
prop.value.type === 'TemplateLiteral' ||
prop.value.type === 'ObjectExpression' ||
prop.value.type === 'BinaryExpression')
? prop.value
: prop.type === 'JSXAttribute' &&
Expand All @@ -330,25 +355,8 @@ function extractMessageDescriptor(
: undefined

if (name && name.type === 'Identifier' && value) {
// <FormattedMessage foo={'barbaz'} />
if (
value.type === 'JSXExpressionContainer' &&
value.expression.type === 'StringLiteral'
) {
switch (name.value) {
case 'id':
msg.id = value.expression.value
break
case 'defaultMessage':
msg.defaultMessage = value.expression.value
break
case 'description':
msg.description = value.expression.value
break
}
}
// {id: 'id'}
else if (value.type === 'StringLiteral') {
if (value.type === 'StringLiteral') {
switch (name.value) {
case 'id':
msg.id = value.value
Expand All @@ -375,8 +383,27 @@ function extractMessageDescriptor(
break
}
} else if (value.type === 'JSXExpressionContainer') {
// <FormattedMessage foo={'barbaz'} />
if (value.expression.type === 'StringLiteral') {
switch (name.value) {
case 'id':
msg.id = value.expression.value
break
case 'defaultMessage':
msg.defaultMessage = value.expression.value
break
case 'description':
msg.description = value.expression.value
break
}
} else if (
value.expression.type === 'ObjectExpression' &&
name.value === 'description'
) {
msg.description = expressionToObject(value.expression)
}
// <FormattedMessage foo={`bar`} />
if (value.expression.type === 'TemplateLiteral') {
else if (value.expression.type === 'TemplateLiteral') {
const {expression} = value
switch (name.value) {
case 'id':
Expand Down Expand Up @@ -425,6 +452,11 @@ function extractMessageDescriptor(
break
}
}
} else if (
value.type === 'ObjectExpression' &&
name.value === 'description'
) {
msg.description = expressionToObject(value)
}
}
})
Expand Down
2 changes: 1 addition & 1 deletion packages/swc-plugin/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface MessageDescriptor {
id: string
description?: string
description?: string | object
defaultMessage?: string
file?: string
start?: number
Expand Down
149 changes: 143 additions & 6 deletions packages/swc-plugin/tests/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,131 @@ export { Foo as default };
}
`;

exports[`emit asserts for descriptionsAsObjects 1`] = `
Object {
"code": "import React, { Component } from 'react';
import { FormattedMessage } from 'react-intl';
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(\\"this hasn't been initialised - super() hasn't been called\\");
}
return self;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError(\\"Cannot call a class as a function\\");
}
}
function _defineProperties(target, props) {
for(var i = 0; i < props.length; i++){
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if (\\"value\\" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== \\"function\\" && superClass !== null) {
throw new TypeError(\\"Super expression must either be null or a function\\");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === \\"object\\" || typeof call === \\"function\\")) {
return call;
}
return _assertThisInitialized(self);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
var _typeof = function(obj) {
return obj && typeof Symbol !== \\"undefined\\" && obj.constructor === Symbol ? \\"symbol\\" : typeof obj;
};
function _isNativeReflectConstruct() {
if (typeof Reflect === \\"undefined\\" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === \\"function\\") return true;
try {
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {
}));
return true;
} catch (e) {
return false;
}
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived), result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
var Foo = /*#__PURE__*/ function(Component1) {
\\"use strict\\";
_inherits(Foo, Component1);
var _super = _createSuper(Foo);
function Foo() {
_classCallCheck(this, Foo);
return _super.apply(this, arguments);
}
_createClass(Foo, [
{
key: \\"render\\",
value: function render() {
return(/*#__PURE__*/ React.createElement(FormattedMessage, {
id: \\"foo.bar.baz\\",
defaultMessage: \\"Hello World!\\"
}));
}
}
]);
return Foo;
}(Component);
export { Foo as default };
",
"msgs": Array [
Object {
"defaultMessage": "Hello World!",
"description": Object {
"metadata": "Additional metadata content.",
"text": "Something for the translator.",
},
"id": "foo.bar.baz",
},
],
}
`;

exports[`emit asserts for extractFromFormatMessage 1`] = `
Object {
"code": "import React, { Component } from 'react';
Expand Down Expand Up @@ -1710,7 +1835,7 @@ var msgs = defineMessages({
defaultMessage: \\"Hello World!\\"
}, _defineProperty(_obj, \\"id\\", 'foo.bar.baz'), _defineProperty(_obj, \\"defaultMessage\\", 'Hello World!'), _defineProperty(_obj, \\"description\\", 'The default message'), _obj),
content: (_obj1 = {
id: \\"HELLO.foo.bar.biff.12.undefined\\",
id: \\"HELLO.foo.bar.biff.12.object\\",
defaultMessage: \\"Hello Nurse!\\"
}, _defineProperty(_obj1, \\"id\\", 'foo.bar.biff'), _defineProperty(_obj1, \\"defaultMessage\\", 'Hello Nurse!'), _defineProperty(_obj1, \\"description\\", {
text: 'Something for the translator.',
Expand Down Expand Up @@ -1752,13 +1877,13 @@ var Foo = /*#__PURE__*/ function(Component1) {
return(/*#__PURE__*/ React.createElement(\\"div\\", null, /*#__PURE__*/ React.createElement(\\"h1\\", null, /*#__PURE__*/ React.createElement(FormattedMessage, _extends({
}, msgs.header))), /*#__PURE__*/ React.createElement(\\"p\\", null, /*#__PURE__*/ React.createElement(FormattedMessage, _extends({
}, msgs.content))), /*#__PURE__*/ React.createElement(FormattedMessage, {
id: \\"HELLO.foo.bar.zoo.18.undefined\\",
id: \\"HELLO.foo.bar.zoo.18.object\\",
defaultMessage: \\"Hello World! {abc}\\",
values: {
abc: 2
}
}), /*#__PURE__*/ React.createElement(FormattedMessage, {
id: \\"HELLO..18.undefined\\",
id: \\"HELLO..18.object\\",
defaultMessage: \\"Hello World! {abc}\\",
values: {
abc: 2
Expand All @@ -1779,7 +1904,11 @@ export { Foo as default };
},
Object {
"defaultMessage": "Hello Nurse!",
"id": "HELLO.foo.bar.biff.12.undefined",
"description": Object {
"metadata": "Additional metadata content.",
"text": "Something for the translator.",
},
"id": "HELLO.foo.bar.biff.12.object",
},
Object {
"defaultMessage": "defineMessage",
Expand All @@ -1803,11 +1932,19 @@ export { Foo as default };
},
Object {
"defaultMessage": "Hello World! {abc}",
"id": "HELLO.foo.bar.zoo.18.undefined",
"description": Object {
"metadata": "Additional metadata content.",
"text": "Something for the translator. Another description",
},
"id": "HELLO.foo.bar.zoo.18.object",
},
Object {
"defaultMessage": "Hello World! {abc}",
"id": "HELLO..18.undefined",
"description": Object {
"metadata": "Additional metadata content.",
"text": "Something for the translator. Another description",
},
"id": "HELLO..18.object",
},
],
}
Expand Down
1 change: 1 addition & 0 deletions packages/swc-plugin/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const FILES_TO_TESTS: Record<string, Opts> = {
extractSourceLocation: {
extractSourceLocation: true,
},
descriptionsAsObjects: {},
formatMessageCall: {},
FormattedMessage: {},
inline: {},
Expand Down

0 comments on commit 914f766

Please sign in to comment.