Skip to content

Commit 391bb41

Browse files
feat!: Consolidate Metadata Transformation Options into descriptorFields (#2513)
1 parent 2f7b13d commit 391bb41

17 files changed

Lines changed: 242 additions & 163 deletions

File tree

packages/babel-plugin-extract-messages/test/extract-messages.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const transformCode = (
4747
[
4848
linguiMacroPlugin,
4949
{
50-
extract: true,
50+
descriptorFields: "all",
5151
} satisfies LinguiPluginOpts,
5252
],
5353
[plugin, pluginOpts],

packages/babel-plugin-lingui-macro/src/index.ts

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
1-
import type { PluginObj, Visitor, PluginPass } from "@babel/core"
1+
import type { PluginObj, PluginPass, Visitor } from "@babel/core"
22
import type * as babelTypes from "@babel/types"
3-
import { Program, Identifier } from "@babel/types"
3+
import { Identifier, Program } from "@babel/types"
44
import { MacroJSX } from "./macroJsx"
55
import type { NodePath } from "@babel/traverse"
66
import { MacroJs } from "./macroJs"
77
import { JsMacroName } from "./constants"
88
import {
9-
type LinguiConfigNormalized,
109
getConfig as loadConfig,
1110
LinguiConfig,
11+
type LinguiConfigNormalized,
1212
} from "@lingui/conf"
13+
import { ResolvedDescriptorFields } from "./messageDescriptorUtils"
1314

1415
let config: LinguiConfigNormalized
1516

1617
export type LinguiPluginOpts = {
17-
/*
18-
* When set `true` all auxiliary data such as `comment`, `context`,
19-
* and default message would be kept regardless of the current environment
20-
*
21-
* This flag explicitly set by Lingui CLI when running extraction process
22-
*/
23-
extract?: boolean
2418
/**
25-
* Setting `stripMessageField` to `true` will strip messages and comments from both development and production bundles.
26-
* Alternatively, set it to `false` to keep the original messages in both environments.
19+
* Controls which descriptor fields are preserved in the transformed code.
2720
*
28-
* If not set value would be determined based on `process.env.NODE_ENV === "production"`
21+
* - `"auto"` (default): In production (`NODE_ENV === "production"`), keeps only the `id`.
22+
* Otherwise, behaves like `"all"`.
23+
* - `"all"`: Keeps every field: `id`, `message`, `context`, and `comment`.
24+
* Used by Lingui CLI during extraction.
25+
* - `"id-only"`: Strips everything except the `id`. Most optimized for production.
26+
* - `"message"`: Keeps `id`, `message`, and `context` (but not `comment`).
27+
* Use when you need runtime access to message and context.
28+
*
29+
* @default "auto"
2930
*/
30-
stripMessageField?: boolean
31+
descriptorFields?: "auto" | "all" | "id-only" | "message"
3132

3233
/**
3334
* Resolved and normalized Lingui Configuration
@@ -58,13 +59,45 @@ function reportUnsupportedSyntax(path: NodePath, e: Error) {
5859
throw codeFrameError
5960
}
6061

61-
function shouldStripMessageProp(opts: LinguiPluginOpts) {
62-
if (typeof opts.stripMessageField === "boolean") {
63-
// if explicitly set in options, use it
64-
return opts.stripMessageField
62+
const VALID_DESCRIPTOR_FIELDS = [
63+
"auto",
64+
"all",
65+
"id-only",
66+
"message",
67+
] as LinguiPluginOpts["descriptorFields"][]
68+
69+
const REMOVED_OPTIONS: Record<string, string> = {
70+
extract:
71+
'Use `descriptorFields: "all"` instead of `extract: true` to preserve all fields during extraction.',
72+
stripMessageField:
73+
'Use `descriptorFields: "id-only"` instead of `stripMessageField: true`, ' +
74+
'or `descriptorFields: "message"` to keep message and context.',
75+
}
76+
77+
function resolveDescriptorFields(
78+
opts: LinguiPluginOpts,
79+
): ResolvedDescriptorFields {
80+
// introduced in v6, remove these hints in V7
81+
for (const [key, hint] of Object.entries(REMOVED_OPTIONS)) {
82+
if (key in (opts as Record<string, unknown>)) {
83+
throw new Error(`[lingui] Option "${key}" has been removed. ${hint}`)
84+
}
85+
}
86+
87+
const mode = opts.descriptorFields ?? "auto"
88+
89+
if (!VALID_DESCRIPTOR_FIELDS.includes(mode)) {
90+
throw new Error(
91+
`[lingui] Invalid descriptorFields value: "${mode}". ` +
92+
`Expected one of: ${VALID_DESCRIPTOR_FIELDS.join(", ")}.`,
93+
)
94+
}
95+
96+
if (mode !== "auto") {
97+
return mode as ResolvedDescriptorFields
6598
}
66-
// default to strip message in production if no explicit option is set and not during extract
67-
return process.env.NODE_ENV === "production" && !opts.extract
99+
// "auto": production → "id-only", otherwise → "all"
100+
return process.env.NODE_ENV === "production" ? "id-only" : "all"
68101
}
69102

70103
type LinguiSymbol = "Trans" | "useLingui" | "i18n"
@@ -236,10 +269,7 @@ export default function ({
236269
{ types: t },
237270
{
238271
transImportName: getSymbolIdentifier(state, "Trans").name,
239-
stripNonEssentialProps:
240-
process.env.NODE_ENV == "production" &&
241-
!(state.opts as LinguiPluginOpts).extract,
242-
stripMessageProp: shouldStripMessageProp(
272+
descriptorFields: resolveDescriptorFields(
243273
state.opts as LinguiPluginOpts,
244274
),
245275
isLinguiIdentifier: (node: Identifier, macro) =>
@@ -269,10 +299,7 @@ export default function ({
269299
state: PluginPass,
270300
) {
271301
const macro = new MacroJs({
272-
stripNonEssentialProps:
273-
process.env.NODE_ENV == "production" &&
274-
!(state.opts as LinguiPluginOpts).extract,
275-
stripMessageProp: shouldStripMessageProp(
302+
descriptorFields: resolveDescriptorFields(
276303
state.opts as LinguiPluginOpts,
277304
),
278305
i18nImportName: getSymbolIdentifier(state, "i18n").name,

packages/babel-plugin-lingui-macro/src/macroJs.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import type { NodePath } from "@babel/traverse"
1313

1414
import { Tokens } from "./icu"
1515
import { JsMacroName } from "./constants"
16-
import { createMessageDescriptorFromTokens } from "./messageDescriptorUtils"
16+
import {
17+
createMessageDescriptorFromTokens,
18+
ResolvedDescriptorFields,
19+
} from "./messageDescriptorUtils"
1720
import {
1821
isLinguiIdentifier,
1922
isDefineMessage,
@@ -28,8 +31,7 @@ export type MacroJsOpts = {
2831
i18nImportName: string
2932
useLinguiImportName: string
3033

31-
stripNonEssentialProps: boolean
32-
stripMessageProp: boolean
34+
descriptorFields: ResolvedDescriptorFields
3335
isLinguiIdentifier: (node: Identifier, macro: JsMacroName) => boolean
3436
}
3537

@@ -49,8 +51,7 @@ export class MacroJs {
4951

5052
this._ctx = createMacroJsContext(
5153
opts.isLinguiIdentifier,
52-
opts.stripNonEssentialProps,
53-
opts.stripMessageProp,
54+
opts.descriptorFields,
5455
)
5556
}
5657

@@ -63,8 +64,7 @@ export class MacroJs {
6364
createMessageDescriptorFromTokens(
6465
tokens,
6566
path.node.loc,
66-
this._ctx.stripNonEssentialProps,
67-
this._ctx.stripMessageProp,
67+
this._ctx.descriptorFields,
6868
),
6969
linguiInstance,
7070
)
@@ -94,8 +94,7 @@ export class MacroJs {
9494
return createMessageDescriptorFromTokens(
9595
tokens,
9696
path.node.loc,
97-
ctx.stripNonEssentialProps,
98-
ctx.stripMessageProp,
97+
ctx.descriptorFields,
9998
)
10099
}
101100

@@ -260,8 +259,7 @@ export class MacroJs {
260259

261260
const _ctx = createMacroJsContext(
262261
ctx.isLinguiIdentifier,
263-
ctx.stripNonEssentialProps,
264-
ctx.stripMessageProp,
262+
ctx.descriptorFields,
265263
)
266264

267265
// { t } = useLingui()
@@ -272,8 +270,7 @@ export class MacroJs {
272270
const descriptor = createMessageDescriptorFromTokens(
273271
tokens,
274272
currentPath.node.loc,
275-
_ctx.stripNonEssentialProps,
276-
_ctx.stripMessageProp,
273+
_ctx.descriptorFields,
277274
)
278275

279276
const callExpr = t.callExpression(

packages/babel-plugin-lingui-macro/src/macroJsAst.test.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,9 @@ const parseExpression = (expression: string) => {
3535
}
3636

3737
function createMacroCtx() {
38-
return createMacroJsContext(
39-
(identifier, macro) => {
40-
return identifier.name === macro
41-
},
42-
false, // stripNonEssentialProps
43-
false, // stripMessageProp
44-
)
38+
return createMacroJsContext((identifier, macro) => {
39+
return identifier.name === macro
40+
}, "all")
4541
}
4642

4743
describe("js macro", () => {

packages/babel-plugin-lingui-macro/src/macroJsAst.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,27 @@ import {
1111
} from "@babel/types"
1212
import { JsMacroName, MsgDescriptorPropKey } from "./constants"
1313
import { ArgToken, TextToken, Token } from "./icu"
14-
import { createMessageDescriptorFromTokens } from "./messageDescriptorUtils"
14+
import {
15+
createMessageDescriptorFromTokens,
16+
ResolvedDescriptorFields,
17+
} from "./messageDescriptorUtils"
1518
import { makeCounter } from "./utils"
1619

1720
export type MacroJsContext = {
1821
// Positional expressions counter (e.g. for placeholders `Hello {0}, today is {1}`)
1922
getExpressionIndex: () => number
20-
stripNonEssentialProps: boolean
21-
stripMessageProp: boolean
23+
descriptorFields: ResolvedDescriptorFields
2224
isLinguiIdentifier: (node: Identifier, macro: JsMacroName) => boolean
2325
}
2426

2527
export function createMacroJsContext(
2628
isLinguiIdentifier: MacroJsContext["isLinguiIdentifier"],
27-
stripNonEssentialProps: boolean,
28-
stripMessageProp: boolean,
29+
descriptorFields: ResolvedDescriptorFields,
2930
): MacroJsContext {
3031
return {
3132
getExpressionIndex: makeCounter(),
3233
isLinguiIdentifier,
33-
stripNonEssentialProps,
34-
stripMessageProp,
34+
descriptorFields,
3535
}
3636
}
3737

@@ -87,8 +87,7 @@ export function processDescriptor(
8787
return createMessageDescriptorFromTokens(
8888
tokens,
8989
descriptor.loc,
90-
ctx.stripNonEssentialProps,
91-
ctx.stripMessageProp,
90+
ctx.descriptorFields,
9291
{
9392
id: idProperty,
9493
context: contextProperty,

packages/babel-plugin-lingui-macro/src/macroJsx.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ function createMacro() {
3838
return new MacroJSX(
3939
{ types },
4040
{
41-
stripNonEssentialProps: false,
42-
stripMessageProp: false,
41+
descriptorFields: "all",
4342
transImportName: "Trans",
4443
isLinguiIdentifier: () => true,
4544
},

packages/babel-plugin-lingui-macro/src/macroJsx.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import { ArgToken, ElementToken, TextToken, Token } from "./icu"
2020
import { makeCounter } from "./utils"
2121
import { JsxMacroName, MsgDescriptorPropKey, JsMacroName } from "./constants"
2222
import cleanJSXElementLiteralChild from "./utils/cleanJSXElementLiteralChild"
23-
import { createMessageDescriptorFromTokens } from "./messageDescriptorUtils"
23+
import {
24+
createMessageDescriptorFromTokens,
25+
ResolvedDescriptorFields,
26+
} from "./messageDescriptorUtils"
2427
import {
2528
createMacroJsContext,
2629
MacroJsContext,
@@ -52,8 +55,7 @@ export type MacroJsxContext = MacroJsContext & {
5255
}
5356

5457
export type MacroJsxOpts = {
55-
stripNonEssentialProps: boolean
56-
stripMessageProp: boolean
58+
descriptorFields: ResolvedDescriptorFields
5759
transImportName: string
5860
isLinguiIdentifier: (node: Identifier, macro: JsMacroName) => boolean
5961
}
@@ -79,11 +81,7 @@ export class MacroJSX {
7981
this.types = types
8082

8183
this.ctx = {
82-
...createMacroJsContext(
83-
opts.isLinguiIdentifier,
84-
opts.stripNonEssentialProps,
85-
opts.stripMessageProp,
86-
),
84+
...createMacroJsContext(opts.isLinguiIdentifier, opts.descriptorFields),
8785
transImportName: opts.transImportName,
8886
elementIndex: makeCounter(),
8987
}
@@ -111,8 +109,7 @@ export class MacroJSX {
111109
const messageDescriptor = createMessageDescriptorFromTokens(
112110
tokens,
113111
path.node.loc,
114-
this.ctx.stripNonEssentialProps,
115-
this.ctx.stripMessageProp,
112+
this.ctx.descriptorFields,
116113
{
117114
id,
118115
context,

0 commit comments

Comments
 (0)