From 96fc3b1370eafbbab52f1de4c4d477f9ff2510a0 Mon Sep 17 00:00:00 2001 From: Long Ho Date: Sun, 3 Jan 2021 21:58:40 -0500 Subject: [PATCH] feat(@formatjs/ts-transformer): allow setting `additionalFunctionNames` to extract from custom function calls --- packages/ts-transformer/src/transform.ts | 17 +++++++++--- .../tests/__snapshots__/index.test.ts.snap | 27 +++++++++++++++++++ .../fixtures/additionalFunctionNames.tsx | 20 ++++++++++++++ packages/ts-transformer/tests/index.test.ts | 5 ++++ website/docs/tooling/ts-transformer.md | 4 +++ 5 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 packages/ts-transformer/tests/fixtures/additionalFunctionNames.tsx diff --git a/packages/ts-transformer/src/transform.ts b/packages/ts-transformer/src/transform.ts index d592cd63ac..1db06e8785 100644 --- a/packages/ts-transformer/src/transform.ts +++ b/packages/ts-transformer/src/transform.ts @@ -97,6 +97,12 @@ export interface Opts { * e.g: `['FormattedFooBarMessage']`. */ additionalComponentNames?: string[]; + /** + * Additional function names to extract messages from, + * e.g: `['formatMessage', '$t']` + * Default to `['formatMessage']` + */ + additionalFunctionNames?: string[]; /** * Opt-in to extract from `intl.formatMessage` call with the same restrictions, * e.g: has to be called with object literal such as @@ -352,8 +358,10 @@ function extractMessageDescriptor( */ function isIntlFormatMessageCall( ts: TypeScript, - node: typescript.CallExpression + node: typescript.CallExpression, + additionalFunctionNames: string[] ) { + const fnNames = new Set(['formatMessage', ...additionalFunctionNames]); const method = node.expression; // Handle intl.formatMessage() @@ -368,7 +376,7 @@ function isIntlFormatMessageCall( } // Handle formatMessage() - return ts.isIdentifier(method) && method.text === 'formatMessage'; + return ts.isIdentifier(method) && fnNames.has(method.text); } function extractMessageFromJsxComponent( @@ -506,7 +514,7 @@ function extractMessagesFromCallExpression( opts: Opts, sf: typescript.SourceFile ): typeof node { - const {onMsgExtracted} = opts; + const {onMsgExtracted, additionalFunctionNames} = opts; if (isMultipleMessageDecl(ts, node)) { const [arg, ...restArgs] = node.arguments; let descriptorsObj: typescript.ObjectLiteralExpression | undefined; @@ -576,7 +584,8 @@ function extractMessagesFromCallExpression( } } else if ( isSingularMessageDecl(ts, node, opts.additionalComponentNames || []) || - (opts.extractFromFormatMessageCall && isIntlFormatMessageCall(ts, node)) + (opts.extractFromFormatMessageCall && + isIntlFormatMessageCall(ts, node, additionalFunctionNames || [])) ) { const [descriptorsObj, ...restArgs] = node.arguments; if (ts.isObjectLiteralExpression(descriptorsObj)) { diff --git a/packages/ts-transformer/tests/__snapshots__/index.test.ts.snap b/packages/ts-transformer/tests/__snapshots__/index.test.ts.snap index 5410bb885f..dfbac6ff10 100644 --- a/packages/ts-transformer/tests/__snapshots__/index.test.ts.snap +++ b/packages/ts-transformer/tests/__snapshots__/index.test.ts.snap @@ -78,6 +78,33 @@ 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 (); + } +} +", + "meta": Object { + "project": "foo", + }, + "msgs": Array [ + Object { + "defaultMessage": "foo", + "id": "rL0Y20zC+F", + }, + Object { + "defaultMessage": "foo", + "id": "rL0Y20zC+F", + }, + ], +} +`; + exports[`emit asserts for ast 1`] = ` Object { "code": "import React, { Component } from 'react'; diff --git a/packages/ts-transformer/tests/fixtures/additionalFunctionNames.tsx b/packages/ts-transformer/tests/fixtures/additionalFunctionNames.tsx new file mode 100644 index 0000000000..2a5199501b --- /dev/null +++ b/packages/ts-transformer/tests/fixtures/additionalFunctionNames.tsx @@ -0,0 +1,20 @@ +// @react-intl project:foo +import React, {Component} from 'react'; + +function CustomMessage() {} + +export default class Foo extends Component { + render() { + return ( + + ); + } +} diff --git a/packages/ts-transformer/tests/index.test.ts b/packages/ts-transformer/tests/index.test.ts index d762d39203..534592acfe 100644 --- a/packages/ts-transformer/tests/index.test.ts +++ b/packages/ts-transformer/tests/index.test.ts @@ -11,6 +11,11 @@ const FILES_TO_TESTS: Record> = { additionalComponentNames: ['CustomMessage'], pragma: 'react-intl', }, + additionalFunctionNames: { + additionalFunctionNames: ['$formatMessage'], + pragma: 'react-intl', + extractFromFormatMessageCall: true, + }, defineMessages: { pragma: 'react-intl', }, diff --git a/website/docs/tooling/ts-transformer.md b/website/docs/tooling/ts-transformer.md index 685857d45d..f25f8e9e3a 100644 --- a/website/docs/tooling/ts-transformer.md +++ b/website/docs/tooling/ts-transformer.md @@ -176,6 +176,10 @@ Opt-in to extract from `intl.formatMessage` call with the same restrictions, e.g 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: