diff --git a/crates/lingui_macro/src/ast_utils.rs b/crates/lingui_macro/src/ast_utils.rs index 4ae1248..c944a07 100644 --- a/crates/lingui_macro/src/ast_utils.rs +++ b/crates/lingui_macro/src/ast_utils.rs @@ -185,18 +185,6 @@ pub fn get_prop_key(prop: &KeyValueProp) -> Option { } } -// recursively expands TypeScript's as expressions until it reaches a real value -pub fn expand_ts_as_expr(mut expr: Box) -> Box { - while let Expr::TsAs(TsAsExpr { - expr: inner_expr, .. - }) = *expr - { - expr = inner_expr; - } - - expr -} - pub fn create_key_value_prop(key: &str, value: Box) -> PropOrSpread { PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { key: PropName::Ident(quote_ident!(key)), diff --git a/crates/lingui_macro/src/builder.rs b/crates/lingui_macro/src/builder.rs index 82b5250..2e1855c 100644 --- a/crates/lingui_macro/src/builder.rs +++ b/crates/lingui_macro/src/builder.rs @@ -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()) @@ -87,13 +83,10 @@ pub struct MessageBuilder<'a> { components: Vec, values: Vec, - values_indexed: Vec, options: &'a LinguiOptions, elements_tracking: Vec<(String, JSXOpeningElement)>, element_index: usize, - // Counter for auto-generated numeric placeholders - numeric_index: usize, } impl<'a> MessageBuilder<'a> { @@ -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 { @@ -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 { @@ -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) => { @@ -176,9 +164,6 @@ impl<'a> MessageBuilder<'a> { MsgToken::TagClosing => { self.push_tag_closing(); } - MsgToken::IcuChoice(icu) => { - self.push_icu(icu); - } } } } @@ -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) -> 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("}"); } } diff --git a/crates/lingui_macro/src/js_macro_folder.rs b/crates/lingui_macro/src/js_macro_folder.rs index b760a26..e65124b 100644 --- a/crates/lingui_macro/src/js_macro_folder.rs +++ b/crates/lingui_macro/src/js_macro_folder.rs @@ -19,7 +19,7 @@ pub struct JsMacroFolder<'a, C> where C: Comments, { - pub ctx: &'a mut MacroCtx, + pub ctx: &'a mut TransformCtx, pub comments: &'a Option, } @@ -27,7 +27,7 @@ impl<'a, C> JsMacroFolder<'a, C> where C: Comments, { - pub fn new(ctx: &'a mut MacroCtx, comments: &'a Option) -> JsMacroFolder<'a, C> { + pub fn new(ctx: &'a mut TransformCtx, comments: &'a Option) -> JsMacroFolder<'a, C> { JsMacroFolder { ctx, comments } } @@ -126,9 +126,9 @@ where } // take {message: "", id: "", ...} object literal, process message and return updated props - fn update_msg_descriptor_props(&self, expr: Box, span: Span) -> Box { + fn update_msg_descriptor_props(&mut self, expr: Box, span: Span) -> Box { 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"); @@ -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( @@ -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( @@ -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())); } @@ -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())); } @@ -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), + ); } } @@ -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, @@ -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( diff --git a/crates/lingui_macro/src/jsx_visitor.rs b/crates/lingui_macro/src/jsx_visitor.rs index f1727c3..58bc553 100644 --- a/crates/lingui_macro/src/jsx_visitor.rs +++ b/crates/lingui_macro/src/jsx_visitor.rs @@ -1,24 +1,48 @@ -use crate::ast_utils::{get_jsx_attr, get_jsx_attr_value_as_string}; -use crate::macro_utils::MacroCtx; -use crate::tokens::{CaseOrOffset, ChoiceCase, IcuChoice, MsgToken, TagOpening}; -use swc_core::common::DUMMY_SP; +use crate::ast_utils::get_jsx_attr_value_as_string; +use crate::macro_utils::{ + tokenize_expr_to_arg, tokenize_tpl, try_tokenize_call_expr_as_choice_cmp, MacroCtx, +}; +use crate::tokens::{CaseOrOffset, ChoiceCase, MsgArg, MsgToken, TagOpening}; use swc_core::ecma::ast::*; use swc_core::ecma::atoms::Atom; -use swc_core::ecma::visit::{Visit, VisitWith}; use swc_core::plugin::errors::HANDLER; pub struct TransJSXVisitor<'a> { pub tokens: Vec, - ctx: &'a MacroCtx, + pub ctx: MacroCtx<'a>, } impl<'a> TransJSXVisitor<'a> { - pub fn new(ctx: &'a MacroCtx) -> TransJSXVisitor<'a> { + pub fn new(ctx: MacroCtx<'a>) -> TransJSXVisitor<'a> { TransJSXVisitor { tokens: Vec::new(), ctx, } } + + pub fn visit_jsx_children(&mut self, children: &Vec) { + for child in children { + match child { + JSXElementChild::JSXText(el) => self.visit_jsx_text(el), + JSXElementChild::JSXExprContainer(cont) => self.visit_jsx_expr_container(cont), + JSXElementChild::JSXElement(el) => { + self.visit_jsx_element(el); + } + JSXElementChild::JSXFragment(frag) => { + self.visit_jsx_children(&frag.children); + } + _ => {} + } + } + } + + pub fn visit_jsx_element(&mut self, el: &JSXElement) { + self.visit_jsx_opening_element(&el.opening); + if !el.opening.self_closing { + self.visit_jsx_children(&el.children); + self.visit_jsx_closing_element(); + } + } } // taken from babel repo -> packages/babel-types/src/utils/react/cleanJSXElementLiteralChild.ts @@ -112,16 +136,26 @@ fn is_allowed_plural_option(key: &str) -> Option { impl TransJSXVisitor<'_> { //