diff --git a/packages/babel-plugin-extract-messages/src/index.ts b/packages/babel-plugin-extract-messages/src/index.ts index 997503841..a70522d4f 100644 --- a/packages/babel-plugin-extract-messages/src/index.ts +++ b/packages/babel-plugin-extract-messages/src/index.ts @@ -7,7 +7,6 @@ import { Node, ObjectExpression, ObjectProperty, - StringLiteral, } from "@babel/types" import type { PluginObj, PluginPass } from "@babel/core" import type { NodePath } from "@babel/core" @@ -63,7 +62,8 @@ function collectMessage( function getTextFromExpression( t: BabelTypes, exp: Expression, - hub: Hub + hub: Hub, + emitErrorOnVariable = true ): string { if (t.isStringLiteral(exp)) { return exp.value @@ -71,8 +71,18 @@ function getTextFromExpression( if (t.isBinaryExpression(exp)) { return ( - getTextFromExpression(t, exp.left as Expression, hub) + - getTextFromExpression(t, exp.right as Expression, hub) + getTextFromExpression( + t, + exp.left as Expression, + hub, + emitErrorOnVariable + ) + + getTextFromExpression( + t, + exp.right as Expression, + hub, + emitErrorOnVariable + ) ) } @@ -91,13 +101,15 @@ function getTextFromExpression( return exp.quasis[0]?.value?.cooked } - console.warn( - hub.buildError( - exp, - "Only strings or template literals could be extracted.", - SyntaxError - ).message - ) + if (emitErrorOnVariable) { + console.warn( + hub.buildError( + exp, + "Only strings or template literals could be extracted.", + SyntaxError + ).message + ) + } } function extractFromObjectExpression( @@ -121,11 +133,6 @@ function extractFromObjectExpression( export default function ({ types: t }: { types: BabelTypes }): PluginObj { let localTransComponentName: string - // We need to remember all processed nodes. When JSX expressions are - // replaced with CallExpressions, all children are traversed for each CallExpression. - // Then, i18n._ methods are visited multiple times for each parent CallExpression. - const visitedNodes = new WeakSet() - function isTransComponent(node: Node) { return ( t.isJSXElement(node) && @@ -213,8 +220,6 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj { }, CallExpression(path, ctx) { - if (visitedNodes.has(path.node)) return - const hasComment = [path.node, path.parent].some((node) => hasI18nComment(node) ) @@ -230,16 +235,15 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj { if (!hasComment && !isNonMacroI18n) return let props: Record = { - id: (firstArgument as StringLiteral).value, + id: getTextFromExpression( + t, + firstArgument as Expression, + ctx.file.hub, + false + ), } if (!props.id) { - // don't rise warning when translating from variables - if (!t.isIdentifier(firstArgument)) { - console.warn( - path.buildCodeFrameError("Missing message ID, skipping.").message - ) - } return } @@ -256,17 +260,14 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj { } } - visitedNodes.add(path.node) collectMessage(path, props, ctx) }, StringLiteral(path, ctx) { - if (!hasI18nComment(path.node) || visitedNodes.has(path.node)) { + if (!hasI18nComment(path.node)) { return } - visitedNodes.add(path.node) - const props = { id: path.node.value, } @@ -283,12 +284,10 @@ export default function ({ types: t }: { types: BabelTypes }): PluginObj { // Extract message descriptors ObjectExpression(path, ctx) { - if (!hasI18nComment(path.node) || visitedNodes.has(path.node)) { + if (!hasI18nComment(path.node)) { return } - visitedNodes.add(path.node) - const props = extractFromObjectExpression(t, path.node, ctx.file.hub, [ "id", "message", diff --git a/packages/babel-plugin-extract-messages/test/index.ts b/packages/babel-plugin-extract-messages/test/index.ts index 13faa4058..2cbf6ca46 100644 --- a/packages/babel-plugin-extract-messages/test/index.ts +++ b/packages/babel-plugin-extract-messages/test/index.ts @@ -92,10 +92,10 @@ describe("@lingui/babel-plugin-extract-messages", function () { import { Trans } from "@lingui/react"; ; +; ` expectNoConsole(() => { const messages = transformCode(code) - expect(messages.length).toBe(0) }) }) @@ -127,22 +127,14 @@ import { Trans } from "@lingui/react"; }) }) - it("Should log error when no ID provided", () => { - const code = `const msg = i18n._('', {}, {message: "My Message"})` - - return mockConsole((console) => { - const messages = transformCode(code) - - expect(messages.length).toBe(0) - expect(console.error).not.toBeCalled() - expect(console.warn).toBeCalledWith( - expect.stringContaining(`Missing message ID`) - ) - }) - }) - it("Should not rise warning when translation from variable", () => { - const code = `i18n._(message)` + const code = ` + i18n._(message); + // member expression + i18n._(foo.bar); + // function call + i18n._(getMessage()); + ` expectNoConsole(() => { const messages = transformCode(code) @@ -165,6 +157,23 @@ import { Trans } from "@lingui/react"; }) }) + it("Should support extract id from TplLiteral and Concatenation", () => { + const code = ` + const msg = i18n._(\`message.id\`); + const msg2 = i18n._("second" + '.' + "id") + ` + + expectNoConsole(() => { + const messages = transformCode(code) + expect(messages[0]).toMatchObject({ + id: "message.id", + }) + expect(messages[1]).toMatchObject({ + id: "second.id", + }) + }) + }) + it("Should support string concatenation", () => { const code = `const msg = i18n._('message.id', {}, {comment: "first " + "second " + "third"})`