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
33 changes: 21 additions & 12 deletions src/ast_utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::collections::HashSet;
use swc_core::common::DUMMY_SP;
use swc_core::atoms::atom;
use swc_core::common::comments::{Comment, CommentKind, Comments};
use swc_core::common::{BytePos, DUMMY_SP};
use swc_core::ecma::ast::*;
use swc_core::ecma::atoms::Atom;
use swc_core::ecma::utils::quote_ident;
Expand Down Expand Up @@ -99,17 +101,6 @@ pub fn pick_jsx_attrs(
attrs
}

pub fn create_jsx_attribute(name: &str, exp: Box<Expr>) -> JSXAttrOrSpread {
JSXAttrOrSpread::JSXAttr(JSXAttr {
span: DUMMY_SP,
name: JSXAttrName::Ident(IdentName::new(name.into(), DUMMY_SP)),
value: Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
span: DUMMY_SP,
expr: JSXExpr::Expr(exp),
})),
})
}

pub fn match_callee_name<F: Fn(&Ident) -> bool>(call: &CallExpr, predicate: F) -> Option<&Ident> {
if let Callee::Expr(expr) = &call.callee {
if let Expr::Ident(ident) = expr.as_ref() {
Expand Down Expand Up @@ -159,6 +150,7 @@ pub fn expand_ts_as_expr(mut expr: Box<Expr>) -> Box<Expr> {
{
expr = inner_expr;
}

expr
}

Expand All @@ -169,6 +161,10 @@ pub fn create_key_value_prop(key: &str, value: Box<Expr>) -> PropOrSpread {
})))
}

pub fn create_lingui_mark_prop() -> PropOrSpread {
create_key_value_prop("__lingui__", true.into())
}

pub fn create_import(source: Atom, imported: IdentName, local: IdentName) -> ModuleItem {
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
Expand All @@ -188,3 +184,16 @@ pub fn create_import(source: Atom, imported: IdentName, local: IdentName) -> Mod
type_only: false,
}))
}

pub fn add_i18n_comment<C: Comments>(comments: &Option<C>, span_lo: BytePos) {
if let Some(comments) = &comments {
comments.add_leading(
span_lo,
Comment {
kind: CommentKind::Block,
span: DUMMY_SP,
text: atom!("i18n"),
},
);
}
}
51 changes: 35 additions & 16 deletions src/js_macro_folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use crate::builder::MessageBuilder;
use crate::generate_id::generate_message_id;
use crate::macro_utils::*;
use crate::tokens::MsgToken;
use swc_core::common::SyntaxContext;
use swc_core::common::comments::Comments;
use swc_core::common::{Span, Spanned, SyntaxContext};
use swc_core::{
common::DUMMY_SP,
ecma::{
Expand All @@ -13,22 +14,29 @@ use swc_core::{
},
};

pub struct JsMacroFolder<'a> {
pub struct JsMacroFolder<'a, C>
where
C: Comments,
{
pub ctx: &'a mut MacroCtx,
pub comments: &'a Option<C>,
}

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

fn create_message_descriptor_from_tokens(&mut self, tokens: Vec<MsgToken>) -> Expr {
let parsed = MessageBuilder::parse(tokens);

let mut props: Vec<PropOrSpread> = vec![create_key_value_prop(
"id",
generate_message_id(&parsed.message_str, "").into(),
)];
let mut props: Vec<PropOrSpread> = vec![
create_lingui_mark_prop(),
create_key_value_prop("id", generate_message_id(&parsed.message_str, "").into()),
];

if !self.ctx.options.strip_non_essential_fields {
props.push(create_key_value_prop("message", parsed.message));
Expand All @@ -38,10 +46,14 @@ impl<'a> JsMacroFolder<'a> {
props.push(create_key_value_prop("values", v))
}

Expr::Object(ObjectLit {
span: DUMMY_SP,
let message_descriptor = Expr::Object(ObjectLit {
span: Span::dummy_with_cmt(),
props,
})
});

add_i18n_comment(self.comments, message_descriptor.span().lo);

message_descriptor
}

fn create_i18n_fn_call_from_tokens(
Expand Down Expand Up @@ -87,7 +99,7 @@ impl<'a> JsMacroFolder<'a> {

let message_prop = get_object_prop(&obj.props, "message");

let mut new_props: Vec<PropOrSpread> = vec![];
let mut new_props: Vec<PropOrSpread> = vec![create_lingui_mark_prop()];

if let Some(prop) = id_prop {
if let Some(value) = get_expr_as_string(&prop.value) {
Expand Down Expand Up @@ -120,17 +132,24 @@ impl<'a> JsMacroFolder<'a> {
}
}

return Box::new(Expr::Object(ObjectLit {
span: DUMMY_SP,
let message_descriptor = Box::new(Expr::Object(ObjectLit {
span: obj.span,
props: new_props,
}));

add_i18n_comment(self.comments, message_descriptor.span().lo);

return message_descriptor;
}

expr
}
}

impl Fold for JsMacroFolder<'_> {
impl<C> Fold for JsMacroFolder<'_, C>
where
C: Comments,
{
fn fold_expr(&mut self, expr: Expr) -> Expr {
// t`Message`
if let Expr::TaggedTpl(tagged_tpl) = &expr {
Expand Down
96 changes: 68 additions & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashSet;
use swc_core::common::{SyntaxContext, DUMMY_SP};
use swc_core::common::{Span, Spanned, SyntaxContext, DUMMY_SP};

use swc_core::common::comments::*;
use swc_core::ecma::utils::private_ident;
use swc_core::plugin::errors::HANDLER;
use swc_core::{
Expand Down Expand Up @@ -49,17 +50,24 @@ impl Fold for IdentReplacer {
}
}

#[derive(Default)]
pub struct LinguiMacroFolder {
pub struct LinguiMacroFolder<C>
where
C: Comments + Clone,
{
has_lingui_macro_imports: bool,
ctx: MacroCtx,
comments: Option<C>,
}

impl LinguiMacroFolder {
pub fn new(options: LinguiOptions) -> LinguiMacroFolder {
impl<C> LinguiMacroFolder<C>
where
C: Comments + Clone,
{
pub fn new(options: LinguiOptions, comments: Option<C>) -> LinguiMacroFolder<C> {
LinguiMacroFolder {
has_lingui_macro_imports: false,
ctx: MacroCtx::new(options),
comments,
}
}

Expand All @@ -75,42 +83,71 @@ impl LinguiMacroFolder {
}

let parsed = MessageBuilder::parse(trans_visitor.tokens);
let id_attr = get_jsx_attr(&el.opening, "id");
let id_attr = get_jsx_attr(&el.opening, "id").and_then(|attr| attr.value.as_ref());
let context_attr =
get_jsx_attr(&el.opening, "context").and_then(|attr| attr.value.as_ref());

let context_attr_val = get_jsx_attr(&el.opening, "context")
.and_then(|attr| attr.value.as_ref())
.and_then(get_jsx_attr_value_as_string);
let mut message_descriptor_props: Vec<PropOrSpread> = vec![create_lingui_mark_prop()];

let mut attrs = vec![create_jsx_attribute("message", parsed.message)];
if let Some(attr) = id_attr {
message_descriptor_props.push(create_key_value_prop(
"id",
get_jsx_attr_value_as_string(attr)
.unwrap_or_default()
.into(),
));
} else {
let context_attr_val = context_attr.and_then(get_jsx_attr_value_as_string);

if id_attr.is_none() {
attrs.push(create_jsx_attribute(
message_descriptor_props.push(create_key_value_prop(
"id",
generate_message_id(&parsed.message_str, &context_attr_val.unwrap_or_default())
.into(),
));
}

if let Some(exp) = parsed.values {
attrs.push(create_jsx_attribute("values", exp));
message_descriptor_props.push(create_key_value_prop("values", exp));
}

if let Some(exp) = parsed.components {
attrs.push(create_jsx_attribute("components", exp));
message_descriptor_props.push(create_key_value_prop("components", exp));
}

if !self.ctx.options.strip_non_essential_fields {
message_descriptor_props.push(create_key_value_prop("message", parsed.message));

if context_attr.is_some() {
let context_attr_val = context_attr.and_then(get_jsx_attr_value_as_string).unwrap();

message_descriptor_props.push(create_key_value_prop(
"context",
Box::new(Expr::Lit(Lit::Str(Str {
span: context_attr.span(),
value: context_attr_val.into(),
raw: None,
}))),
));
}
}

let message_descriptor = Expr::Object(ObjectLit {
span: Span::dummy_with_cmt(),
props: message_descriptor_props,
});

add_i18n_comment(&self.comments, message_descriptor.span().lo);

let mut attrs = vec![JSXAttrOrSpread::SpreadElement(SpreadElement {
dot3_token: DUMMY_SP,
expr: Box::new(message_descriptor),
})];

attrs.extend(pick_jsx_attrs(
el.opening.attrs,
HashSet::from(["id", "component", "render", "i18n"]),
HashSet::from(["component", "render", "i18n"]),
));

if self.ctx.options.strip_non_essential_fields {
attrs = pick_jsx_attrs(
attrs,
HashSet::from(["id", "component", "render", "i18n", "values", "components"]),
)
}

self.ctx.should_add_trans_import = true;

JSXElement {
Expand Down Expand Up @@ -262,7 +299,7 @@ r#"You have to destructure `t` when using the `useLingui` macro, i.e:
// use lingui matched above
if ident_replacer.is_some() {
block = block
.fold_children_with(&mut JsMacroFolder::new(&mut ctx))
.fold_children_with(&mut JsMacroFolder::new(&mut ctx, &self.comments))
// replace other
.fold_children_with(&mut ident_replacer.unwrap());
}
Expand All @@ -271,7 +308,10 @@ r#"You have to destructure `t` when using the `useLingui` macro, i.e:
}
}

impl Fold for LinguiMacroFolder {
impl<C> Fold for LinguiMacroFolder<C>
where
C: Comments + Clone,
{
fn fold_module_items(&mut self, mut n: Vec<ModuleItem>) -> Vec<ModuleItem> {
let (i18n_source, i18n_export) = self.ctx.options.runtime_modules.i18n.clone();
let (trans_source, trans_export) = self.ctx.options.runtime_modules.trans.clone();
Expand Down Expand Up @@ -387,7 +427,7 @@ impl Fold for LinguiMacroFolder {
return Expr::Arrow(self.fold_arrow_expr(arrow_expr));
}

let mut folder = JsMacroFolder::new(&mut self.ctx);
let mut folder = JsMacroFolder::new(&mut self.ctx, &self.comments);

folder.fold_expr(expr).fold_children_with(self)
}
Expand All @@ -399,7 +439,7 @@ impl Fold for LinguiMacroFolder {
return expr;
}

let mut folder = JsMacroFolder::new(&mut self.ctx);
let mut folder = JsMacroFolder::new(&mut self.ctx, &self.comments);

folder.fold_call_expr(expr).fold_children_with(self)
}
Expand All @@ -413,7 +453,7 @@ impl Fold for LinguiMacroFolder {

// apply JS Macro transformations to jsx elements
// before they will be extracted as message components
el = el.fold_with(&mut JsMacroFolder::new(&mut self.ctx));
el = el.fold_with(&mut JsMacroFolder::new(&mut self.ctx, &self.comments));

if let JSXElementName::Ident(ident) = &el.opening.name {
if self.ctx.is_lingui_ident("Trans", ident) {
Expand Down Expand Up @@ -446,5 +486,5 @@ pub fn process_transform(program: Program, metadata: TransformPluginProgramMetad
.unwrap_or_default(),
);

program.fold_with(&mut LinguiMacroFolder::new(config))
program.fold_with(&mut LinguiMacroFolder::new(config, metadata.comments))
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { i18n as $_i18n } from "@lingui/core";
import { t } from "./custom-t";
t`Don't touch me!`;
$_i18n._({
$_i18n._(/*i18n*/ {
__lingui__: true,
id: "kwTAtG",
message: "{value, plural, one {...} other {...}}",
values: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { i18n as $_i18n } from "@lingui/core";
import { plural } from "./custom-plural";
$_i18n._({
$_i18n._(/*i18n*/ {
__lingui__: true,
id: "0IkKj6",
message: "Hello World!"
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { i18n as $_i18n } from "@lingui/core";
$_i18n._({
$_i18n._(/*i18n*/ {
__lingui__: true,
id: "0IkKj6",
message: "Hello World!"
});
$_i18n._({
$_i18n._(/*i18n*/ {
__lingui__: true,
id: "kwTAtG",
message: "{value, plural, one {...} other {...}}",
values: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Trans as Trans_ } from "@lingui/react";
import { Select } from "./my-select-cmp";
;
<Trans_ message={"{count, plural, one {Message} other {Messages}}"} id={"V4EO9s"} values={{
count: count
<Trans_ {.../*i18n*/ {
__lingui__: true,
id: "V4EO9s",
values: {
count: count
},
message: "{count, plural, one {Message} other {Messages}}"
}}/>;
<Select prop="propValue">Should be untouched</Select>;
Loading