diff --git a/crates/lingui_macro/src/macro_utils.rs b/crates/lingui_macro/src/macro_utils.rs index c92eebe..735bd96 100644 --- a/crates/lingui_macro/src/macro_utils.rs +++ b/crates/lingui_macro/src/macro_utils.rs @@ -9,7 +9,7 @@ use swc_core::ecma::{ast::*, atoms::Atom}; use swc_core::plugin::errors::HANDLER; fn expression_to_name(expr: &Expr, ctx: &mut MacroCtx) -> String { - let expr = unwrap_ts_as_expr(expr); + let expr = unwrap_ts_only_expr(expr); match expr { Expr::Ident(ident) => ident.sym.to_string(), @@ -33,7 +33,7 @@ fn expression_to_name(expr: &Expr, ctx: &mut MacroCtx) -> String { } fn expression_to_value(expr: Box) -> Box { - let unwrapped = unwrap_ts_as_expr(&expr); + let unwrapped = unwrap_ts_only_expr(&expr); match unwrapped { Expr::Object(object) => { @@ -66,14 +66,18 @@ fn expression_to_value(expr: Box) -> Box { } } -// recursively expands TypeScript's as expressions until it reaches a real value -fn unwrap_ts_as_expr(expr: &Expr) -> &Expr { +// recursively unwraps TypeScript-only expression wrappers (`x as T`, `x!`, +// `x satisfies T`) until it reaches a real value, so the inner expression drives +// placeholder naming (e.g. `${x!}` → `{x}`, not `{0}`). +fn unwrap_ts_only_expr(expr: &Expr) -> &Expr { let mut current = expr; - while let Expr::TsAs(TsAsExpr { - expr: inner_expr, .. - }) = current - { - current = inner_expr; + loop { + current = match current { + Expr::TsAs(TsAsExpr { expr, .. }) + | Expr::TsNonNull(TsNonNullExpr { expr, .. }) + | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => expr, + _ => break, + }; } current } diff --git a/crates/lingui_macro/tests/js_t.rs b/crates/lingui_macro/tests/js_t.rs index 4fc3623..68a2dc5 100644 --- a/crates/lingui_macro/tests/js_t.rs +++ b/crates/lingui_macro/tests/js_t.rs @@ -228,3 +228,21 @@ to!( t`Hello World` "# ); + +// TS-only expression wrappers unwrap to a named placeholder (parity with `as`), +// matching @lingui/babel-plugin-lingui-macro (see lingui/js-lingui#2622). +to!( + js_non_null_assertion_gets_named_placeholder, + r#" + import { t } from '@lingui/core/macro'; + t`Variable ${name!}`; + "# +); + +to!( + js_satisfies_gets_named_placeholder, + r#" + import { t } from '@lingui/core/macro'; + t`Variable ${name satisfies string}`; + "# +); diff --git a/crates/lingui_macro/tests/snapshots/js_t__js_non_null_assertion_gets_named_placeholder.snap b/crates/lingui_macro/tests/snapshots/js_t__js_non_null_assertion_gets_named_placeholder.snap new file mode 100644 index 0000000..7ae2a5b --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/js_t__js_non_null_assertion_gets_named_placeholder.snap @@ -0,0 +1,16 @@ +--- +source: crates/lingui_macro/tests/js_t.rs +--- +import { t } from '@lingui/core/macro'; +t`Variable ${name!}`; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { i18n as $_i18n } from "@lingui/core"; +$_i18n._(/*i18n*/ { + id: "xRRkAE", + message: "Variable {name}", + values: { + name: name! + } +}); diff --git a/crates/lingui_macro/tests/snapshots/js_t__js_satisfies_gets_named_placeholder.snap b/crates/lingui_macro/tests/snapshots/js_t__js_satisfies_gets_named_placeholder.snap new file mode 100644 index 0000000..a33aeec --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/js_t__js_satisfies_gets_named_placeholder.snap @@ -0,0 +1,16 @@ +--- +source: crates/lingui_macro/tests/js_t.rs +--- +import { t } from '@lingui/core/macro'; +t`Variable ${name satisfies string}`; + +↓ ↓ ↓ ↓ ↓ ↓ + +import { i18n as $_i18n } from "@lingui/core"; +$_i18n._(/*i18n*/ { + id: "xRRkAE", + message: "Variable {name}", + values: { + name: name satisfies string + } +});