From 94d9c78cea317af2be6da3518a3e613066037dee Mon Sep 17 00:00:00 2001 From: Timofei Iatsenko Date: Mon, 8 Apr 2024 09:51:42 +0200 Subject: [PATCH] refactor(macro): use message descriptor for Trans --- .../babel-plugin-lingui-macro/src/macroJs.ts | 27 +---- .../babel-plugin-lingui-macro/src/macroJsx.ts | 97 +++------------ .../src/messageDescriptorUtils.ts | 112 ++++++++++++++++++ 3 files changed, 133 insertions(+), 103 deletions(-) create mode 100644 packages/babel-plugin-lingui-macro/src/messageDescriptorUtils.ts diff --git a/packages/babel-plugin-lingui-macro/src/macroJs.ts b/packages/babel-plugin-lingui-macro/src/macroJs.ts index 0a1980a2d..12dfe9341 100644 --- a/packages/babel-plugin-lingui-macro/src/macroJs.ts +++ b/packages/babel-plugin-lingui-macro/src/macroJs.ts @@ -32,6 +32,7 @@ import { MACRO_LEGACY_PACKAGE, } from "./constants" import { generateMessageId } from "@lingui/message-utils/generateMessageId" +import { createMessageDescriptorFromTokens } from "./messageDescriptorUtils" function buildICUFromTokens(tokens: Tokens) { const messageFormat = new ICUMessageFormat() @@ -76,7 +77,7 @@ export class MacroJs { linguiInstance?: babelTypes.Expression ) => { return this.createI18nCall( - this.createMessageDescriptorFromTokens(tokens, path.node.loc), + createMessageDescriptorFromTokens(tokens, path.node.loc), linguiInstance ) } @@ -102,7 +103,7 @@ export class MacroJs { this.isDefineMessage(path.get("tag")) ) { const tokens = this.tokenizeTemplateLiteral(path.get("quasi")) - return this.createMessageDescriptorFromTokens(tokens, path.node.loc) + return createMessageDescriptorFromTokens(tokens, path.node.loc) } if (path.isTaggedTemplateExpression()) { @@ -255,7 +256,7 @@ export class MacroJs { if (currentPath.isTaggedTemplateExpression()) { const tokens = this.tokenizeTemplateLiteral(currentPath) - const descriptor = this.createMessageDescriptorFromTokens( + const descriptor = createMessageDescriptorFromTokens( tokens, currentPath.node.loc ) @@ -543,26 +544,6 @@ export class MacroJs { ) } - createMessageDescriptorFromTokens(tokens: Tokens, oldLoc?: SourceLocation) { - const { message, values } = buildICUFromTokens(tokens) - - const properties: ObjectProperty[] = [ - this.createIdProperty(message), - - !this.stripNonEssentialProps - ? this.createObjectProperty(MESSAGE, this.types.stringLiteral(message)) - : null, - - this.createValuesProperty(values), - ] - - return this.createMessageDescriptor( - properties, - // preserve line numbers for extractor - oldLoc - ) - } - createMessageDescriptor( properties: ObjectProperty[], oldLoc?: SourceLocation diff --git a/packages/babel-plugin-lingui-macro/src/macroJsx.ts b/packages/babel-plugin-lingui-macro/src/macroJsx.ts index 1ab59d782..a86623bca 100644 --- a/packages/babel-plugin-lingui-macro/src/macroJsx.ts +++ b/packages/babel-plugin-lingui-macro/src/macroJsx.ts @@ -14,12 +14,7 @@ import { } from "@babel/types" import { NodePath } from "@babel/traverse" -import ICUMessageFormat, { - ArgToken, - ElementToken, - TextToken, - Token, -} from "./icu" +import { ArgToken, ElementToken, TextToken, Token } from "./icu" import { makeCounter } from "./utils" import { COMMENT, @@ -30,8 +25,11 @@ import { MACRO_REACT_PACKAGE, MACRO_LEGACY_PACKAGE, } from "./constants" -import { generateMessageId } from "@lingui/message-utils/generateMessageId" import cleanJSXElementLiteralChild from "./utils/cleanJSXElementLiteralChild" +import { + createMessageDescriptorFromTokens, + createStringObjectProperty, +} from "./messageDescriptorUtils" const pluralRuleRe = /(_[\d\w]+|zero|one|two|few|many|other)/ const jsx2icuExactChoice = (value: string) => @@ -87,87 +85,26 @@ export class MacroJSX { return false } - const messageFormat = new ICUMessageFormat() - const { message, values, jsxElements } = messageFormat.fromTokens(tokens) const { attributes, id, comment, context } = this.stripMacroAttributes( path as NodePath ) - if (!id && !message) { - throw new Error("Incorrect usage of Trans") - } - - if (id) { - attributes.push( - this.types.jsxAttribute( - this.types.jsxIdentifier(ID), - this.types.stringLiteral(id) - ) - ) - } else { - attributes.push( - this.createStringJsxAttribute(ID, generateMessageId(message, context)) - ) - } - - if (!this.stripNonEssentialProps) { - if (message) { - attributes.push(this.createStringJsxAttribute(MESSAGE, message)) - } - - if (comment) { - attributes.push( - this.types.jsxAttribute( - this.types.jsxIdentifier(COMMENT), - this.types.stringLiteral(comment) - ) - ) - } - - if (context) { - attributes.push( - this.types.jsxAttribute( - this.types.jsxIdentifier(CONTEXT), - this.types.stringLiteral(context) - ) - ) - } - } - - // Parameters for variable substitution - const valuesObject = Object.keys(values).map((key) => - this.types.objectProperty(this.types.identifier(key), values[key]) + const messageDescriptor = createMessageDescriptorFromTokens( + tokens, + path.node.loc, + { + id, + context, + comment, + }, + this.stripNonEssentialProps ) - if (valuesObject.length) { - attributes.push( - this.types.jsxAttribute( - this.types.jsxIdentifier("values"), - this.types.jsxExpressionContainer( - this.types.objectExpression(valuesObject) - ) - ) - ) + if (!id && !tokens) { + throw new Error("Incorrect usage of Trans") } - // Inline elements - if (Object.keys(jsxElements).length) { - attributes.push( - this.types.jsxAttribute( - this.types.jsxIdentifier("components"), - this.types.jsxExpressionContainer( - this.types.objectExpression( - Object.keys(jsxElements).map((key) => - this.types.objectProperty( - this.types.identifier(key), - jsxElements[key] - ) - ) - ) - ) - ) - ) - } + attributes.push(this.types.jsxSpreadAttribute(messageDescriptor)) const newNode = this.types.jsxElement( this.types.jsxOpeningElement( diff --git a/packages/babel-plugin-lingui-macro/src/messageDescriptorUtils.ts b/packages/babel-plugin-lingui-macro/src/messageDescriptorUtils.ts new file mode 100644 index 000000000..1eced8fa5 --- /dev/null +++ b/packages/babel-plugin-lingui-macro/src/messageDescriptorUtils.ts @@ -0,0 +1,112 @@ +import ICUMessageFormat, { Tokens, ParsedResult } from "./icu" +import { SourceLocation, ObjectProperty, ObjectExpression } from "@babel/types" +import { MESSAGE, ID, EXTRACT_MARK, COMMENT, CONTEXT } from "./constants" +import * as types from "@babel/types" +import { generateMessageId } from "@lingui/message-utils/generateMessageId" + +function buildICUFromTokens(tokens: Tokens) { + const messageFormat = new ICUMessageFormat() + return messageFormat.fromTokens(tokens) +} + +export function createMessageDescriptorFromTokens( + tokens: Tokens, + oldLoc?: SourceLocation, + defaults: { id?: string; context?: string; comment?: string } = {}, + stripNonEssentialProps = true +) { + const { message, values, jsxElements } = buildICUFromTokens(tokens) + + const properties: ObjectProperty[] = [] + + properties.push( + defaults.id + ? createStringObjectProperty(ID, defaults.id) + : createIdProperty(message, defaults.context) + ) + + if (!stripNonEssentialProps) { + properties.push(createStringObjectProperty(MESSAGE, message)) + + if (defaults.comment) { + properties.push(createStringObjectProperty(COMMENT, defaults.comment)) + } + + if (defaults.context) { + properties.push(createStringObjectProperty(CONTEXT, defaults.context)) + } + } + + properties.push(createValuesProperty(values)) + properties.push(createComponentsProperty(jsxElements)) + + return createMessageDescriptor( + properties, + // preserve line numbers for extractor + oldLoc + ) +} + +function createIdProperty(message: string, context?: string) { + return createStringObjectProperty(ID, generateMessageId(message, context)) +} + +function createValuesProperty(values: ParsedResult["values"]) { + const valuesObject = Object.keys(values).map((key) => + types.objectProperty(types.identifier(key), values[key]) + ) + + if (!valuesObject.length) return + + return types.objectProperty( + types.identifier("values"), + types.objectExpression(valuesObject) + ) +} + +function createComponentsProperty(values: ParsedResult["jsxElements"]) { + const valuesObject = Object.keys(values).map((key) => + types.objectProperty(types.identifier(key), values[key]) + ) + + if (!valuesObject.length) return + + return types.objectProperty( + types.identifier("components"), + types.objectExpression(valuesObject) + ) +} + +// if (Object.keys(jsxElements).length) { +// attributes.push( +// this.types.jsxAttribute( +// this.types.jsxIdentifier("components"), +// this.types.jsxExpressionContainer( +// this.types.objectExpression( +// Object.keys(jsxElements).map((key) => +// this.types.objectProperty( +// this.types.identifier(key), +// jsxElements[key] +// ) +// ) +// ) +// ) +// ) +// ) +// } +export function createStringObjectProperty(key: string, value: string) { + return types.objectProperty(types.identifier(key), types.stringLiteral(value)) +} + +function createMessageDescriptor( + properties: ObjectProperty[], + oldLoc?: SourceLocation +): ObjectExpression { + const newDescriptor = types.objectExpression(properties.filter(Boolean)) + types.addComment(newDescriptor, "leading", EXTRACT_MARK) + if (oldLoc) { + newDescriptor.loc = oldLoc + } + + return newDescriptor +}