Skip to content
Open
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
44 changes: 44 additions & 0 deletions packages/formatjs/__tests__/wasm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,50 @@ describe("formatjs swc plugin", () => {
expect(code).toMatchSnapshot();
});

it("should not break on JSX outside formatjs calls", async () => {
const input = `
import React from 'react';

const Loading = () => <div>Loading...</div>;

function App() {
return (
<React.Suspense fallback={<Loading />}>
<div>Content</div>
</React.Suspense>
);
}
`;

const output = await transformCode(input);

// Build should succeed; no formatjs ids should be generated
expect(output).toBeTruthy();
expect(output).not.toMatch(/id:/);
// The original JSX structure should be preserved
expect(output).toMatch(/React\.Suspense/);
expect(output).toMatch(/Loading/);
});

it("should not break on conditional JSX rendering outside formatjs calls", async () => {
const input = `
import React from 'react';

function App({ show }) {
return show ? <div>Hello</div> : <span>World</span>;
}
`;

const output = await transformCode(input);

// Build should succeed; no formatjs ids should be generated
expect(output).toBeTruthy();
expect(output).not.toMatch(/id:/);
// The original JSX structure should be preserved
expect(output).toMatch(/Hello/);
expect(output).toMatch(/World/);
});

it("should generate same id even if description is an template literal string", async () => {
const input1 = `
import { FormattedMessage } from 'react-intl';
Expand Down
27 changes: 24 additions & 3 deletions packages/formatjs/transform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ impl MessageDescriptorExtractor for JSXAttrOrSpread {
Some(name.sym.to_string())
}
};
// Only evaluate expressions for known formatjs attribute names to avoid
// spurious "must be statically evaluate-able" errors on unrelated attributes.
if !matches!(
key.as_deref(),
Some("id") | Some("defaultMessage") | Some("description")
) {
return None;
}
let value = match value {
JSXAttrValue::Str(s) => Some(MessageDescriptionValue::Str(
s.value.as_str().expect("non-utf8 string").to_string(),
Expand Down Expand Up @@ -161,6 +169,14 @@ impl MessageDescriptorExtractor for PropOrSpread {
None
}
};
// Only evaluate expressions for known formatjs prop names to avoid
// spurious "must be statically evaluate-able" errors on unrelated props.
if !matches!(
key.as_deref(),
Some("id") | Some("defaultMessage") | Some("description")
) {
return None;
}
let value = match &*key_value.value {
Expr::Object(obj) => Some(MessageDescriptionValue::Obj(obj.clone())),
expr => {
Expand Down Expand Up @@ -978,10 +994,15 @@ impl<'a, C: Clone + Comments, S: SourceMapper> VisitMut for FormatJSVisitor<'a,

let name = &jsx_opening_elem.name;

if let JSXElementName::Ident(ident) = name {
if !self.component_names.contains(&*ident.sym) {
return;
match name {
JSXElementName::Ident(ident) => {
if !self.component_names.contains(&*ident.sym) {
return;
}
}
// Member expressions (e.g. React.Suspense) and namespaced names are never
// formatjs components, so skip processing their attributes entirely.
_ => return,
}

let mut descriptor = self.create_message_descriptor_from_extractor(&jsx_opening_elem.attrs);
Expand Down
Loading