fix: unwrap non-null assertion (x!) / satisfies in placeholders (parity with babel macro) - #247
Merged
andrii-bodnar merged 2 commits intoJul 21, 2026
Conversation
`unwrap_ts_only_expr` (formerly `unwrap_ts_as_expr`) only unwrapped `TsAs`, so a
placeholder like `t`${x!}`` was treated as a complex expression and assigned an
indexed placeholder (`{0}`) instead of a named one (`{x}`) — diverging from
`@lingui/babel-plugin-lingui-macro` and breaking catalog lookups (mismatched
message ids) when extracting with the JS macro but running with the SWC plugin.
Unwrap `TsNonNull` and `TsSatisfies` alongside `TsAs` so all TS-only wrappers let
the inner expression name the placeholder. Companion to lingui/js-lingui#2622.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #247 +/- ##
=======================================
Coverage 94.83% 94.84%
=======================================
Files 9 9
Lines 2325 2327 +2
=======================================
+ Hits 2205 2207 +2
Misses 120 120
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR updates the Lingui SWC macro plugin’s placeholder-name inference so TypeScript-only expression wrappers don’t force placeholders into the numeric-index path, aligning behavior with the Babel macro and making the direct-AST test harness reflect real-world expectations.
Changes:
- Generalize TS-only unwrapping from just
TsAsto also includeTsNonNull(x!) andTsSatisfies(x satisfies T) when deriving placeholder names. - Add a regression test + snapshot asserting
${name!}yields a named placeholder ({name}) inttemplate literals.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| crates/lingui_macro/src/macro_utils.rs | Extend TS-only expression unwrapping used for placeholder naming/value handling to include TsNonNull and TsSatisfies. |
| crates/lingui_macro/tests/js_t.rs | Add a regression test covering non-null assertion in template placeholders. |
| crates/lingui_macro/tests/snapshots/js_t__js_non_null_assertion_gets_named_placeholder.snap | New snapshot validating {name} placeholder output for ${name!}. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Covers `${x satisfies T}` producing a named placeholder (`{x}`), matching the
non-null case. Addresses review feedback that `TsSatisfies` handling was
otherwise untested.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
timofei-iatsenko
approved these changes
Jul 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Companion to lingui/js-lingui#2622.
unwrap_ts_only_expr(renamed fromunwrap_ts_as_expr) only unwrappedTsAs, so a placeholder whose expression is wrapped in a non-null assertion (x!) orsatisfiesfell through to the numeric-index branch and got an indexed placeholder ({0}) instead of a named one ({x}), diverging from@lingui/babel-plugin-lingui-macro. Same class as #239.This unwraps
TsNonNullandTsSatisfiesalongsideTsAsso all TS-only wrappers let the inner expression name the placeholder.In practice
@swc/corestrips TS-only operators before this plugin runs, so consumers going through@swc/core(rspack, Next.js, Vite, rsbuild, …) already get the named placeholder today — the plugin never sees the!. Verified locally with@swc/core@1.15.43+@lingui/swc-plugin@6.5.1:So the
{0}only surfaces when the plugin transforms a not-yet-stripped TS AST — e.g. this crate's ownto!test harness (which feeds the parsed AST directly to the visitor). Before this change, the added test snapshotsVariable {0}; after it,Variable {name}.Why fix it anyway: it makes the plugin's placeholder naming correct independent of pipeline ordering, keeps it consistent with the babel macro (js-lingui#2622), and is what lets the regression test assert
{name}(parity) rather than a misleading{0}. It does not change output for the common@swc/corepipelines.Test
Adds
js_non_null_assertion_gets_named_placeholderinjs_t.rs(mirrors the js-lingui regression test). Fulllingui_macrosuite passes with no other snapshot changes.🤖 Generated with Claude Code