Skip to content

Commit bccd4a3

Browse files
refactor: labeled expressions implementation (#244)
1 parent 32416e4 commit bccd4a3

33 files changed

Lines changed: 855 additions & 508 deletions

crates/lingui_macro/src/ast_utils.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -185,18 +185,6 @@ pub fn get_prop_key(prop: &KeyValueProp) -> Option<Atom> {
185185
}
186186
}
187187

188-
// recursively expands TypeScript's as expressions until it reaches a real value
189-
pub fn expand_ts_as_expr(mut expr: Box<Expr>) -> Box<Expr> {
190-
while let Expr::TsAs(TsAsExpr {
191-
expr: inner_expr, ..
192-
}) = *expr
193-
{
194-
expr = inner_expr;
195-
}
196-
197-
expr
198-
}
199-
200188
pub fn create_key_value_prop(key: &str, value: Box<Expr>) -> PropOrSpread {
201189
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
202190
key: PropName::Ident(quote_ident!(key)),

crates/lingui_macro/src/builder.rs

Lines changed: 29 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
use crate::ast_utils::{
2-
expand_ts_as_expr, get_jsx_attr, get_jsx_attr_value_as_string, is_jsx_elements_equal,
3-
omit_jsx_attrs,
2+
get_jsx_attr, get_jsx_attr_value_as_string, is_jsx_elements_equal, omit_jsx_attrs,
43
};
54
use crate::options::LinguiOptions;
6-
use crate::tokens::{CaseOrOffset, IcuChoice, MsgToken};
5+
use crate::tokens::{CaseOrOffset, MsgArg, MsgToken};
76
use std::collections::HashSet;
8-
use swc_core::{
9-
common::{SyntaxContext, DUMMY_SP},
10-
ecma::ast::*,
11-
};
7+
use swc_core::{common::DUMMY_SP, ecma::ast::*};
128

139
fn is_numeric(s: &str) -> bool {
1410
!s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
@@ -87,13 +83,10 @@ pub struct MessageBuilder<'a> {
8783
components: Vec<ValueWithPlaceholder>,
8884

8985
values: Vec<ValueWithPlaceholder>,
90-
values_indexed: Vec<ValueWithPlaceholder>,
9186

9287
options: &'a LinguiOptions,
9388
elements_tracking: Vec<(String, JSXOpeningElement)>,
9489
element_index: usize,
95-
// Counter for auto-generated numeric placeholders
96-
numeric_index: usize,
9790
}
9891

9992
impl<'a> MessageBuilder<'a> {
@@ -103,18 +96,16 @@ impl<'a> MessageBuilder<'a> {
10396
components_stack: Vec::new(),
10497
components: Vec::new(),
10598
values: Vec::new(),
106-
values_indexed: Vec::new(),
10799
options,
108100
elements_tracking: Vec::new(),
109101
element_index: 0,
110-
numeric_index: 0,
111102
};
112103

113104
builder.process_tokens(tokens);
114105
builder.into_args()
115106
}
116107

117-
pub fn into_args(mut self) -> MessageBuilderResult {
108+
pub fn into_args(self) -> MessageBuilderResult {
118109
let message_str = self.message;
119110

120111
let message = Box::new(Expr::Lit(Lit::Str(Str {
@@ -123,8 +114,6 @@ impl<'a> MessageBuilder<'a> {
123114
raw: None,
124115
})));
125116

126-
self.values.append(&mut self.values_indexed);
127-
128117
let values = if self.values.is_empty() {
129118
None
130119
} else {
@@ -165,9 +154,8 @@ impl<'a> MessageBuilder<'a> {
165154
self.push_msg(&str);
166155
}
167156

168-
MsgToken::Expression(val) => {
169-
let placeholder = self.push_exp(val);
170-
self.push_msg(&format!("{{{placeholder}}}"));
157+
MsgToken::Arg(arg) => {
158+
self.push_arg(arg);
171159
}
172160

173161
MsgToken::TagOpening(val) => {
@@ -176,9 +164,6 @@ impl<'a> MessageBuilder<'a> {
176164
MsgToken::TagClosing => {
177165
self.push_tag_closing();
178166
}
179-
MsgToken::IcuChoice(icu) => {
180-
self.push_icu(icu);
181-
}
182167
}
183168
}
184169
}
@@ -295,97 +280,37 @@ impl<'a> MessageBuilder<'a> {
295280
}
296281
}
297282

298-
fn next_numeric_index(&mut self) -> String {
299-
let index = self.numeric_index.to_string();
300-
self.numeric_index += 1;
301-
index
302-
}
283+
fn push_arg(&mut self, arg: MsgArg) {
284+
let placeholder = arg.name.clone();
303285

304-
fn push_exp(&mut self, mut exp: Box<Expr>) -> String {
305-
exp = expand_ts_as_expr(exp);
286+
self.values.push(ValueWithPlaceholder {
287+
placeholder: placeholder.clone(),
288+
value: arg.value,
289+
});
306290

307-
match exp.as_ref() {
308-
Expr::Ident(ident) => {
309-
self.values.push(ValueWithPlaceholder {
310-
placeholder: ident.sym.to_string().clone(),
311-
value: exp.clone(),
312-
});
291+
if let Some(format) = arg.format {
292+
self.push_msg(&format!("{{{placeholder}, {format},"));
313293

314-
ident.sym.to_string()
315-
}
316-
Expr::Object(object) => {
317-
if let Some(PropOrSpread::Prop(prop)) = object.props.first() {
318-
// {foo}
319-
if let Some(short) = prop.as_shorthand() {
320-
self.values_indexed.push(ValueWithPlaceholder {
321-
placeholder: short.sym.to_string(),
322-
value: Box::new(Expr::Ident(Ident {
323-
span: DUMMY_SP,
324-
sym: short.sym.clone(),
325-
ctxt: SyntaxContext::empty(),
326-
optional: false,
327-
})),
328-
});
329-
330-
return short.sym.to_string();
331-
}
332-
// {foo: bar}
333-
if let Prop::KeyValue(kv) = prop.as_ref() {
334-
if let PropName::Ident(ident) = &kv.key {
335-
self.values_indexed.push(ValueWithPlaceholder {
336-
placeholder: ident.sym.to_string(),
337-
value: kv.value.clone(),
338-
});
339-
340-
return ident.sym.to_string();
294+
if let Some(cases) = arg.cases {
295+
for choice in cases {
296+
match choice {
297+
// produce offset:{number}
298+
CaseOrOffset::Offset(val) => {
299+
self.push_msg(&format!(" offset:{val}"));
300+
}
301+
CaseOrOffset::Case(choice) => {
302+
let key = choice.key;
303+
self.push_msg(&format!(" {key} {{"));
304+
self.process_tokens(choice.tokens);
305+
self.push_msg("}");
341306
}
342307
}
343308
}
344-
345-
// fallback for {...spread} or {}
346-
let index = self.next_numeric_index();
347-
348-
self.values_indexed.push(ValueWithPlaceholder {
349-
placeholder: index.clone(),
350-
value: exp.clone(),
351-
});
352-
353-
index
354309
}
355-
_ => {
356-
let index = self.next_numeric_index();
357310

358-
self.values_indexed.push(ValueWithPlaceholder {
359-
placeholder: index.clone(),
360-
value: exp.clone(),
361-
});
362-
363-
index
364-
}
365-
}
366-
}
367-
368-
fn push_icu(&mut self, icu: IcuChoice) {
369-
let value_placeholder = self.push_exp(icu.value);
370-
let method = icu.format;
371-
self.push_msg(&format!("{{{value_placeholder}, {method},"));
372-
373-
for choice in icu.cases {
374-
match choice {
375-
// produce offset:{number}
376-
CaseOrOffset::Offset(val) => {
377-
self.push_msg(&format!(" offset:{val}"));
378-
}
379-
CaseOrOffset::Case(choice) => {
380-
let key = choice.key;
381-
382-
self.push_msg(&format!(" {key} {{"));
383-
self.process_tokens(choice.tokens);
384-
self.push_msg("}");
385-
}
386-
}
311+
self.push_msg("}");
312+
} else {
313+
self.push_msg(&format!("{{{placeholder}}}"));
387314
}
388-
389-
self.push_msg("}");
390315
}
391316
}

crates/lingui_macro/src/js_macro_folder.rs

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ pub struct JsMacroFolder<'a, C>
1919
where
2020
C: Comments,
2121
{
22-
pub ctx: &'a mut MacroCtx,
22+
pub ctx: &'a mut TransformCtx,
2323
pub comments: &'a Option<C>,
2424
}
2525

2626
impl<'a, C> JsMacroFolder<'a, C>
2727
where
2828
C: Comments,
2929
{
30-
pub fn new(ctx: &'a mut MacroCtx, comments: &'a Option<C>) -> JsMacroFolder<'a, C> {
30+
pub fn new(ctx: &'a mut TransformCtx, comments: &'a Option<C>) -> JsMacroFolder<'a, C> {
3131
JsMacroFolder { ctx, comments }
3232
}
3333

@@ -126,9 +126,9 @@ where
126126
}
127127

128128
// take {message: "", id: "", ...} object literal, process message and return updated props
129-
fn update_msg_descriptor_props(&self, expr: Box<Expr>, span: Span) -> Box<Expr> {
129+
fn update_msg_descriptor_props(&mut self, expr: Box<Expr>, span: Span) -> Box<Expr> {
130130
if let Expr::Object(obj) = *expr {
131-
let defaults = self.ctx.get_comment_directive(span.lo);
131+
let defaults = self.ctx.get_comment_directive(span.lo).cloned();
132132
let id_prop = get_object_prop(&obj.props, "id");
133133

134134
let explicit_context_prop = get_object_prop(&obj.props, "context");
@@ -142,8 +142,8 @@ where
142142

143143
if let Some(id_prop) = id_prop {
144144
if let Some(value) = get_expr_as_string(&id_prop.value) {
145-
let value =
146-
build_prefixed_id(&self.ctx.options, &value, defaults).unwrap_or(value);
145+
let value = build_prefixed_id(&self.ctx.options, &value, defaults.as_ref())
146+
.unwrap_or(value);
147147
new_props.push(create_key_value_prop("id", value.into()));
148148
} else {
149149
new_props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
@@ -153,14 +153,19 @@ where
153153
}
154154

155155
if let Some(prop) = message_prop {
156-
let tokens = self.ctx.try_tokenize_expr(&prop.value).unwrap_or_default();
156+
let mut macro_ctx = MacroCtx::new(self.ctx);
157+
let tokens = try_tokenize_expr(&mut macro_ctx, &prop.value).unwrap_or_default();
157158

158159
let parsed = MessageBuilder::parse(tokens, &self.ctx.options);
159160

160161
if id_prop.is_none() {
161162
let resolved_context = context_val
162163
.as_deref()
163-
.or_else(|| defaults.and_then(|defaults| defaults.context.as_deref()))
164+
.or_else(|| {
165+
defaults
166+
.as_ref()
167+
.and_then(|defaults| defaults.context.as_deref())
168+
})
164169
.unwrap_or_default();
165170

166171
new_props.push(create_key_value_prop(
@@ -188,8 +193,9 @@ where
188193
new_props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
189194
context_prop.clone(),
190195
))));
191-
} else if let Some(context) =
192-
defaults.and_then(|defaults| defaults.context.as_deref())
196+
} else if let Some(context) = defaults
197+
.as_ref()
198+
.and_then(|defaults| defaults.context.as_deref())
193199
{
194200
new_props.push(create_key_value_prop("context", context.into()));
195201
}
@@ -200,8 +206,9 @@ where
200206
new_props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
201207
comment_prop.clone(),
202208
))));
203-
} else if let Some(value) =
204-
defaults.and_then(|defaults| defaults.comment.as_deref())
209+
} else if let Some(value) = defaults
210+
.as_ref()
211+
.and_then(|defaults| defaults.comment.as_deref())
205212
{
206213
new_props.push(create_key_value_prop("comment", value.into()));
207214
}
@@ -231,12 +238,13 @@ where
231238
let (is_t, callee) = self.ctx.is_lingui_t_call_expr(&tagged_tpl.tag);
232239

233240
if is_t {
234-
return Expr::Call(self.create_i18n_fn_call_from_tokens(
235-
callee,
236-
self.ctx.tokenize_tpl(&tagged_tpl.tpl),
237-
tagged_tpl.tpl.span(),
238-
expr.span(),
239-
));
241+
let mut macro_ctx = MacroCtx::new(self.ctx);
242+
let tokens = tokenize_tpl(&mut macro_ctx, &tagged_tpl.tpl);
243+
let tpl_span = tagged_tpl.tpl.span();
244+
let expr_span = expr.span();
245+
return Expr::Call(
246+
self.create_i18n_fn_call_from_tokens(callee, tokens, tpl_span, expr_span),
247+
);
240248
}
241249
}
242250

@@ -245,7 +253,8 @@ where
245253
let span = tagged_tpl.span();
246254
if let Expr::Ident(ident) = tagged_tpl.tag.as_ref() {
247255
if self.ctx.is_define_message_ident(ident) {
248-
let tokens = self.ctx.tokenize_tpl(&tagged_tpl.tpl);
256+
let mut macro_ctx = MacroCtx::new(self.ctx);
257+
let tokens = tokenize_tpl(&mut macro_ctx, &tagged_tpl.tpl);
249258
let defaults = self.ctx.get_comment_directive(span.lo).cloned();
250259
return self.create_message_descriptor_from_tokens(
251260
tokens,
@@ -291,7 +300,8 @@ where
291300
}
292301

293302
// plural / selectOrdinal / select
294-
if let Some(tokens) = self.ctx.try_tokenize_call_expr_as_choice_cmp(&expr) {
303+
let mut macro_ctx = MacroCtx::new(self.ctx);
304+
if let Some(tokens) = try_tokenize_call_expr_as_choice_cmp(&mut macro_ctx, &expr) {
295305
let msg_dscrptr_span = expr.args.first().map(|arg| arg.span()).unwrap_or(DUMMY_SP);
296306

297307
return self.create_i18n_fn_call_from_tokens(

0 commit comments

Comments
 (0)