diff --git a/packages/formatjs/__tests__/wasm.test.ts b/packages/formatjs/__tests__/wasm.test.ts
index 29780795a..985eb9fc1 100644
--- a/packages/formatjs/__tests__/wasm.test.ts
+++ b/packages/formatjs/__tests__/wasm.test.ts
@@ -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 = () =>
Loading...
;
+
+ function App() {
+ return (
+ }>
+ Content
+
+ );
+ }
+ `;
+
+ 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 ? Hello
: World;
+ }
+ `;
+
+ 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';
diff --git a/packages/formatjs/transform/src/lib.rs b/packages/formatjs/transform/src/lib.rs
index 42424290c..66678792a 100644
--- a/packages/formatjs/transform/src/lib.rs
+++ b/packages/formatjs/transform/src/lib.rs
@@ -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(),
@@ -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 => {
@@ -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);