Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: robust js/jsx whitespace handling #83

Merged
merged 3 commits into from
Mar 29, 2024
Merged
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
723 changes: 333 additions & 390 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ serde = "1"
serde_json = "1.0.95"
regex = "1.7.3"
once_cell = "1.17.1"
swc_common = "=0.33.15"
swc_core = { version = "0.87.28", features = [
swc_core = { version = "0.90.3", features = [
"ecma_plugin_transform",
"ecma_utils",
"ecma_visit",
Expand Down
1 change: 1 addition & 0 deletions src/ast_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ pub fn create_key_value_prop(key: &str, value: Box<Expr>) -> PropOrSpread {
pub fn create_import(source: JsWord, specifier: Ident) -> ModuleItem {
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
phase: ImportPhase::default(),
specifiers: vec![
ImportSpecifier::Named(ImportNamedSpecifier {
span: DUMMY_SP,
Expand Down
16 changes: 4 additions & 12 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ use swc_core::{
},
};

use crate::{
normalize_witespaces_js::normalize_whitespaces_js,
normalize_witespaces_jsx::normalize_whitespaces_jsx,
};
use crate::tokens::{IcuChoice, CaseOrOffset, MsgToken};

fn dedup_values(mut v: Vec<ValueWithPlaceholder>) -> Vec<ValueWithPlaceholder> {
Expand Down Expand Up @@ -55,7 +51,7 @@ pub struct MessageBuilder {
}

impl MessageBuilder {
pub fn parse(tokens: Vec<MsgToken>, jsx: bool) -> MessageBuilderResult {
pub fn parse(tokens: Vec<MsgToken>) -> MessageBuilderResult {
let mut builder = MessageBuilder {
message: String::new(),
components_stack: Vec::new(),
Expand All @@ -65,15 +61,11 @@ impl MessageBuilder {
};

builder.from_tokens(tokens);
builder.to_args(jsx)
builder.to_args()
}

pub fn to_args(mut self, jsx: bool) -> MessageBuilderResult {
let message_str = if jsx {
normalize_whitespaces_jsx(&self.message)
} else {
normalize_whitespaces_js(&self.message)
};
pub fn to_args(mut self) -> MessageBuilderResult {
let message_str = self.message;

let message = Box::new(Expr::Lit(Lit::Str(Str {
span: DUMMY_SP,
Expand Down
4 changes: 2 additions & 2 deletions src/js_macro_folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl<'a> JsMacroFolder<'a> {
}

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

let mut props: Vec<PropOrSpread> = vec![
create_key_value_prop("id", generate_message_id(&parsed.message_str, "").into()),
Expand Down Expand Up @@ -98,7 +98,7 @@ impl<'a> JsMacroFolder<'a> {
if let Some(prop) = message_prop {
let tokens = self.ctx.try_tokenize_expr(&prop.value).unwrap_or_else(|| Vec::new());

let parsed = MessageBuilder::parse(tokens, false);
let parsed = MessageBuilder::parse(tokens);

if !id_prop.is_some() {
new_props.push(
Expand Down
46 changes: 45 additions & 1 deletion src/jsx_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,49 @@ static WORD_OPTION: Lazy<Regex> = Lazy::new(|| Regex::new(r"_(\w+)").unwrap());
// const pluralRuleRe = /(_[\d\w]+|zero|one|two|few|many|other)/
// const jsx2icuExactChoice = (value: string) => value.replace(/_(\d+)/, "=$1").replace(/_(\w+)/, "$1")

// taken from babel repo -> packages/babel-types/src/utils/react/cleanJSXElementLiteralChild.ts
fn clean_jsx_element_literal_child(value: &str) -> String {
let lines: Vec<&str> = value.split('\n').collect();
let mut last_non_empty_line = 0;

for (i, line) in lines.iter().enumerate() {
if line.trim().len() > 0 {
last_non_empty_line = i;
}
}

let mut result = String::new();

for (i, line) in lines.iter().enumerate() {
let is_first_line = i == 0;
let is_last_line = i == lines.len() - 1;
let is_last_non_empty_line = i == last_non_empty_line;

// replace rendered whitespace tabs with spaces
let mut trimmed_line = line.replace("\t", " ");

// trim whitespace touching a newline
if !is_first_line {
trimmed_line = trimmed_line.trim_start().to_string();
}

// trim whitespace touching an endline
if !is_last_line {
trimmed_line = trimmed_line.trim_end().to_string();
}

if !trimmed_line.is_empty() {
if !is_last_non_empty_line {
trimmed_line.push(' ');
}

result.push_str(&trimmed_line);
}
}

result
}

fn is_allowed_plural_option(key: &str) -> Option<JsWord> {
if PLURAL_OPTIONS_WHITELIST.is_match(key) {
let key = NUM_OPTION.replace(key, "=$1");
Expand Down Expand Up @@ -174,8 +217,9 @@ impl<'a> Visit for TransJSXVisitor<'a> {
}

fn visit_jsx_text(&mut self, el: &JSXText) {

self.tokens.push(
MsgToken::String(el.value.to_string())
MsgToken::String(clean_jsx_element_literal_child(&el.raw.to_string()))
);
}

Expand Down
4 changes: 1 addition & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ use swc_core::{
};

mod tests;
mod normalize_witespaces_jsx;
mod normalize_witespaces_js;
mod builder;
mod tokens;
mod ast_utils;
Expand Down Expand Up @@ -58,7 +56,7 @@ impl LinguiMacroFolder {
el.visit_children_with(&mut trans_visitor);
}

let parsed = MessageBuilder::parse(trans_visitor.tokens, true);
let parsed = MessageBuilder::parse(trans_visitor.tokens);
let id_attr = get_jsx_attr(&el.opening, "id");

let context_attr_val = get_jsx_attr(&el.opening, "context")
Expand Down
2 changes: 1 addition & 1 deletion src/macro_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl MacroCtx {
let mut tokens: Vec<MsgToken> = Vec::with_capacity(tpl.quasis.len());

for (i, tpl_element) in tpl.quasis.iter().enumerate() {
tokens.push(MsgToken::String(tpl_element.raw.to_string()));
tokens.push(MsgToken::String(tpl_element.cooked.as_ref().unwrap_or(&tpl_element.raw).to_string()));

if let Some(exp) = tpl.exprs.get(i) {
if let Expr::Call(call) = exp.as_ref() {
Expand Down
39 changes: 0 additions & 39 deletions src/normalize_witespaces_js.rs

This file was deleted.

117 changes: 0 additions & 117 deletions src/normalize_witespaces_jsx.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,3 @@ macro_rules! to {
);
}
}

38 changes: 36 additions & 2 deletions src/tests/js_t.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,46 @@ to!(
r#"
import { i18n } from "@lingui/core";
i18n._({
id: "EfogM+",
message: "Multiline\nstring"
id: "amQF7O",
message: "Multiline\n string"
});
"#
);

to!(
js_continuation_character,
r#"
import { t } from '@lingui/macro';
t`Multiline\
string`;
"#,
r#"
import { i18n } from "@lingui/core";
i18n._({
id: "d1nA7b",
message: "Multiline string"
});
"#
);
to!(
unicode_characters_interpreted,
r#"
import { t } from '@lingui/macro';
t`Message \u0020`;
t`Bienvenue\xA0!`
"#,
r#"
import { i18n } from "@lingui/core";
i18n._({
id: "dZXeyN",
message: "Message "
});
i18n._({
id: "9K3RGd",
message: "Bienvenue !"
});
"#
);
to!(
js_support_message_descriptor_in_t_fn,
r#"
Expand Down
Loading