Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions crates/lingui_macro/src/ast_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,18 +185,6 @@ pub fn get_prop_key(prop: &KeyValueProp) -> Option<Atom> {
}
}

// recursively expands TypeScript's as expressions until it reaches a real value
pub fn expand_ts_as_expr(mut expr: Box<Expr>) -> Box<Expr> {
while let Expr::TsAs(TsAsExpr {
expr: inner_expr, ..
}) = *expr
{
expr = inner_expr;
}

expr
}

pub fn create_key_value_prop(key: &str, value: Box<Expr>) -> PropOrSpread {
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(quote_ident!(key)),
Expand Down
133 changes: 29 additions & 104 deletions crates/lingui_macro/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
use crate::ast_utils::{
expand_ts_as_expr, get_jsx_attr, get_jsx_attr_value_as_string, is_jsx_elements_equal,
omit_jsx_attrs,
get_jsx_attr, get_jsx_attr_value_as_string, is_jsx_elements_equal, omit_jsx_attrs,
};
use crate::options::LinguiOptions;
use crate::tokens::{CaseOrOffset, IcuChoice, MsgToken};
use crate::tokens::{CaseOrOffset, MsgArg, MsgToken};
use std::collections::HashSet;
use swc_core::{
common::{SyntaxContext, DUMMY_SP},
ecma::ast::*,
};
use swc_core::{common::DUMMY_SP, ecma::ast::*};

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

values: Vec<ValueWithPlaceholder>,
values_indexed: Vec<ValueWithPlaceholder>,

options: &'a LinguiOptions,
elements_tracking: Vec<(String, JSXOpeningElement)>,
element_index: usize,
// Counter for auto-generated numeric placeholders
numeric_index: usize,
}

impl<'a> MessageBuilder<'a> {
Expand All @@ -103,18 +96,16 @@ impl<'a> MessageBuilder<'a> {
components_stack: Vec::new(),
components: Vec::new(),
values: Vec::new(),
values_indexed: Vec::new(),
options,
elements_tracking: Vec::new(),
element_index: 0,
numeric_index: 0,
};

builder.process_tokens(tokens);
builder.into_args()
}

pub fn into_args(mut self) -> MessageBuilderResult {
pub fn into_args(self) -> MessageBuilderResult {
let message_str = self.message;

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

self.values.append(&mut self.values_indexed);

let values = if self.values.is_empty() {
None
} else {
Expand Down Expand Up @@ -165,9 +154,8 @@ impl<'a> MessageBuilder<'a> {
self.push_msg(&str);
}

MsgToken::Expression(val) => {
let placeholder = self.push_exp(val);
self.push_msg(&format!("{{{placeholder}}}"));
MsgToken::Arg(arg) => {
self.push_arg(arg);
}

MsgToken::TagOpening(val) => {
Expand All @@ -176,9 +164,6 @@ impl<'a> MessageBuilder<'a> {
MsgToken::TagClosing => {
self.push_tag_closing();
}
MsgToken::IcuChoice(icu) => {
self.push_icu(icu);
}
}
}
}
Expand Down Expand Up @@ -295,97 +280,37 @@ impl<'a> MessageBuilder<'a> {
}
}

fn next_numeric_index(&mut self) -> String {
let index = self.numeric_index.to_string();
self.numeric_index += 1;
index
}
fn push_arg(&mut self, arg: MsgArg) {
let placeholder = arg.name.clone();

fn push_exp(&mut self, mut exp: Box<Expr>) -> String {
exp = expand_ts_as_expr(exp);
self.values.push(ValueWithPlaceholder {
placeholder: placeholder.clone(),
value: arg.value,
});

match exp.as_ref() {
Expr::Ident(ident) => {
self.values.push(ValueWithPlaceholder {
placeholder: ident.sym.to_string().clone(),
value: exp.clone(),
});
if let Some(format) = arg.format {
self.push_msg(&format!("{{{placeholder}, {format},"));

ident.sym.to_string()
}
Expr::Object(object) => {
if let Some(PropOrSpread::Prop(prop)) = object.props.first() {
// {foo}
if let Some(short) = prop.as_shorthand() {
self.values_indexed.push(ValueWithPlaceholder {
placeholder: short.sym.to_string(),
value: Box::new(Expr::Ident(Ident {
span: DUMMY_SP,
sym: short.sym.clone(),
ctxt: SyntaxContext::empty(),
optional: false,
})),
});

return short.sym.to_string();
}
// {foo: bar}
if let Prop::KeyValue(kv) = prop.as_ref() {
if let PropName::Ident(ident) = &kv.key {
self.values_indexed.push(ValueWithPlaceholder {
placeholder: ident.sym.to_string(),
value: kv.value.clone(),
});

return ident.sym.to_string();
if let Some(cases) = arg.cases {
for choice in cases {
match choice {
// produce offset:{number}
CaseOrOffset::Offset(val) => {
self.push_msg(&format!(" offset:{val}"));
}
CaseOrOffset::Case(choice) => {
let key = choice.key;
self.push_msg(&format!(" {key} {{"));
self.process_tokens(choice.tokens);
self.push_msg("}");
}
}
}

// fallback for {...spread} or {}
let index = self.next_numeric_index();

self.values_indexed.push(ValueWithPlaceholder {
placeholder: index.clone(),
value: exp.clone(),
});

index
}
_ => {
let index = self.next_numeric_index();

self.values_indexed.push(ValueWithPlaceholder {
placeholder: index.clone(),
value: exp.clone(),
});

index
}
}
}

fn push_icu(&mut self, icu: IcuChoice) {
let value_placeholder = self.push_exp(icu.value);
let method = icu.format;
self.push_msg(&format!("{{{value_placeholder}, {method},"));

for choice in icu.cases {
match choice {
// produce offset:{number}
CaseOrOffset::Offset(val) => {
self.push_msg(&format!(" offset:{val}"));
}
CaseOrOffset::Case(choice) => {
let key = choice.key;

self.push_msg(&format!(" {key} {{"));
self.process_tokens(choice.tokens);
self.push_msg("}");
}
}
self.push_msg("}");
} else {
self.push_msg(&format!("{{{placeholder}}}"));
}

self.push_msg("}");
}
}
50 changes: 30 additions & 20 deletions crates/lingui_macro/src/js_macro_folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ pub struct JsMacroFolder<'a, C>
where
C: Comments,
{
pub ctx: &'a mut MacroCtx,
pub ctx: &'a mut TransformCtx,
pub comments: &'a Option<C>,
}

impl<'a, C> JsMacroFolder<'a, C>
where
C: Comments,
{
pub fn new(ctx: &'a mut MacroCtx, comments: &'a Option<C>) -> JsMacroFolder<'a, C> {
pub fn new(ctx: &'a mut TransformCtx, comments: &'a Option<C>) -> JsMacroFolder<'a, C> {
JsMacroFolder { ctx, comments }
}

Expand Down Expand Up @@ -126,9 +126,9 @@ where
}

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

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

if let Some(id_prop) = id_prop {
if let Some(value) = get_expr_as_string(&id_prop.value) {
let value =
build_prefixed_id(&self.ctx.options, &value, defaults).unwrap_or(value);
let value = build_prefixed_id(&self.ctx.options, &value, defaults.as_ref())
.unwrap_or(value);
new_props.push(create_key_value_prop("id", value.into()));
} else {
new_props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
Expand All @@ -153,14 +153,19 @@ where
}

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

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

if id_prop.is_none() {
let resolved_context = context_val
.as_deref()
.or_else(|| defaults.and_then(|defaults| defaults.context.as_deref()))
.or_else(|| {
defaults
.as_ref()
.and_then(|defaults| defaults.context.as_deref())
})
.unwrap_or_default();

new_props.push(create_key_value_prop(
Expand Down Expand Up @@ -188,8 +193,9 @@ where
new_props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
context_prop.clone(),
))));
} else if let Some(context) =
defaults.and_then(|defaults| defaults.context.as_deref())
} else if let Some(context) = defaults
.as_ref()
.and_then(|defaults| defaults.context.as_deref())
{
new_props.push(create_key_value_prop("context", context.into()));
}
Expand All @@ -200,8 +206,9 @@ where
new_props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
comment_prop.clone(),
))));
} else if let Some(value) =
defaults.and_then(|defaults| defaults.comment.as_deref())
} else if let Some(value) = defaults
.as_ref()
.and_then(|defaults| defaults.comment.as_deref())
{
new_props.push(create_key_value_prop("comment", value.into()));
}
Expand Down Expand Up @@ -231,12 +238,13 @@ where
let (is_t, callee) = self.ctx.is_lingui_t_call_expr(&tagged_tpl.tag);

if is_t {
return Expr::Call(self.create_i18n_fn_call_from_tokens(
callee,
self.ctx.tokenize_tpl(&tagged_tpl.tpl),
tagged_tpl.tpl.span(),
expr.span(),
));
let mut macro_ctx = MacroCtx::new(self.ctx);
let tokens = tokenize_tpl(&mut macro_ctx, &tagged_tpl.tpl);
let tpl_span = tagged_tpl.tpl.span();
let expr_span = expr.span();
return Expr::Call(
self.create_i18n_fn_call_from_tokens(callee, tokens, tpl_span, expr_span),
);
}
}

Expand All @@ -245,7 +253,8 @@ where
let span = tagged_tpl.span();
if let Expr::Ident(ident) = tagged_tpl.tag.as_ref() {
if self.ctx.is_define_message_ident(ident) {
let tokens = self.ctx.tokenize_tpl(&tagged_tpl.tpl);
let mut macro_ctx = MacroCtx::new(self.ctx);
let tokens = tokenize_tpl(&mut macro_ctx, &tagged_tpl.tpl);
let defaults = self.ctx.get_comment_directive(span.lo).cloned();
return self.create_message_descriptor_from_tokens(
tokens,
Expand Down Expand Up @@ -291,7 +300,8 @@ where
}

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

return self.create_i18n_fn_call_from_tokens(
Expand Down
Loading
Loading