Skip to content

fix(formatjs): scope static evaluation to formatjs descriptor keys only#591

Merged
kdy1 merged 1 commit into
mainfrom
claude/issue-588-20260320-2232
Mar 21, 2026
Merged

fix(formatjs): scope static evaluation to formatjs descriptor keys only#591
kdy1 merged 1 commit into
mainfrom
claude/issue-588-20260320-2232

Conversation

@kdy1

@kdy1 kdy1 commented Mar 21, 2026

Copy link
Copy Markdown
Member

Two bugs caused valid JSX outside formatjs calls to trigger the "must be statically evaluate-able" error:

  1. visit_mut_jsx_opening_element only guarded against JSXElementName::Ident names. Member expressions like React.Suspense fell through the guard and had their attributes (e.g. fallback={<Loading />}) evaluated, causing spurious errors.

  2. JSXAttrOrSpread::get_key_value_with_visitor called evaluate_expr for ALL JSXExprContainer attribute values regardless of the attribute key. Attributes like values or fallback that contain JSX expressions would trigger the error even inside legitimate FormattedMessage components.

Fixes #588

Generated with Claude Code

Two bugs caused valid JSX outside formatjs calls to trigger the
"must be statically evaluate-able" error:

1. `visit_mut_jsx_opening_element` only guarded against `JSXElementName::Ident`
   names. Member expressions like `React.Suspense` fell through the guard and
   had their attributes (e.g. `fallback={<Loading />}`) evaluated, causing
   spurious errors.

2. `JSXAttrOrSpread::get_key_value_with_visitor` called `evaluate_expr` for
   ALL JSXExprContainer attribute values regardless of the attribute key.
   Attributes like `values` or `fallback` that contain JSX expressions would
   trigger the error even inside legitimate `FormattedMessage` components.

Fix: return early for non-Ident JSX element names, and only call `evaluate_expr`
for known formatjs descriptor keys (`id`, `defaultMessage`, `description`).

Fixes #588

Co-authored-by: Donny/강동윤 <kdy1@users.noreply.github.com>
@changeset-bot

changeset-bot Bot commented Mar 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d1a0294

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@kdy1 kdy1 enabled auto-merge (squash) March 21, 2026 00:26
@claude

claude Bot commented Mar 21, 2026

Copy link
Copy Markdown

PR Review: fix(formatjs): scope static evaluation to formatjs descriptor keys only

This PR fixes two related bugs that caused spurious "must be statically evaluate-able" errors on valid JSX outside of formatjs calls. The fixes are well-targeted and minimal.

Bug Fix 1: visit_mut_jsx_opening_element (lib.rs:986)

Correct fix. The original if let JSXElementName::Ident(ident) = name pattern only returned early for simple identifiers not in component_names — member expressions like React.Suspense silently fell through and had their attributes processed. The new match with _ => return properly guards all non-Ident variants (JSXMemberExpr, JSXNamespacedName).

Bug Fix 2: get_key_value_with_visitor (lib.rs:91)

Correct fix. The refactor from Some(name.sym.to_string()) to a plain String is sound since the match arms are exhaustive — key was always Some(...). The new guard:

if !matches!(key.as_str(), "id" | "defaultMessage" | "description") {
    return None;
}

correctly restricts static evaluation to the three known formatjs message descriptor fields. Attributes like values or fallback that may contain JSX are now skipped, preventing false errors.

Nit: hardcoded descriptor keys

The list "id" | "defaultMessage" | "description" is correct per the formatjs spec. If the plugin already has a constant or enum for these elsewhere, it might be worth referencing that rather than a literal match — but this is a minor style point and the current approach is clear.

Tests

All three added test cases directly cover the reported scenarios from #588:

  1. React.Suspense with a JSX fallback prop
  2. Conditional JSX expressions in unrelated components
  3. FormattedMessage with a values prop containing JSX

Suggestion: Consider also adding a positive test that confirms the plugin still correctly extracts and transforms id/defaultMessage/description when they contain valid string literals inside a known formatjs component. This would guard against accidentally over-filtering in the future.

Changeset

The changeset-bot flagged that no changeset was added. Since this is a user-visible bug fix, a patch changeset for @swc/plugin-formatjs is likely needed (the bot already provides a pre-filled link in its comment).


Overall: the root cause analysis is accurate, the fixes are minimal and correct, and the tests are well-chosen. Good work.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1a0294c82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +993 to +999
match name {
JSXElementName::Ident(ident) => {
if !self.component_names.contains(&*ident.sym) {
return;
}
}
_ => return,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support member-expression message components

visit_mut_jsx_opening_element now returns for every non-Ident tag, which fixes React.Suspense but also skips valid message components written as member expressions, e.g. import * as ReactIntl from 'react-intl'; <ReactIntl.FormattedMessage defaultMessage="..." />. Before this change those nodes still reached create_message_descriptor_from_extractor; with the new early return they never get IDs generated, descriptions removed, or messages extracted. This affects projects that use namespace imports or object-qualified wrapper components.

Useful? React with 👍 / 👎.

@kdy1 kdy1 merged commit ac7669d into main Mar 21, 2026
12 of 13 checks passed
@kdy1 kdy1 deleted the claude/issue-588-20260320-2232 branch March 21, 2026 00:34
kdy1 added a commit that referenced this pull request Mar 21, 2026
## Summary
- Addressed `chatgpt-codex-connector[bot]` review follow-ups across
merged PRs: #585, #591, #592, #593, #594, #595, #597, #598, #599.
- Applied the fixes as 9 separate commits (one commit per original PR)
in a single follow-up branch.

## What Changed
- #585 / #592 (`loadable-components`)
- Fixed source-less default-import matching to honor the configured
local name.
- Updated `ssr: false` detection to respect final object-literal
override order.
  - Added/updated fixtures for both behaviors.
- #591 / #594 (`formatjs`)
- Added JSX member-expression message component support (e.g.
`ReactIntl.FormattedMessage`).
  - Updated #532 regression coverage to validate the `ast: true` path.
- #593 / #595 (`graphql-codegen-client-preset`)
  - Extended `namingConvention` parsing to accept string/object forms.
- Preserved names for `keep`/unknown conventions instead of forcing
PascalCase.
- Fixed Windows absolute `filename` path handling in WASM runtime path
resolution.
  - Added unit coverage for the new config/path behaviors.
- #597 / #599 (`emotion`)
- Ensured tagged-template labels are terminated before sourcemap
comments.
- Added css-prop rewrite support for namespace imports
(`emotionReact.css`).
  - Attached PURE comments to the generated call site span.
  - Updated emotion fixtures accordingly.
- #598 (docs)
- Corrected capability descriptions in `packages/jest/README.tmpl.md`
and `packages/swc-sdk/README.tmpl.md`.

## Validation
- `cargo test -p swc_plugin_loadable_components --test fixture --
--ignored`
- `cargo test -p swc_plugin_graphql_codegen_client_preset`
- `cargo test -p swc_emotion --test fixture -- --ignored`
- `pnpm -C /Users/kdy1/.codex/worktrees/17e6/plugins/packages/formatjs
test`

All passed (formatjs has an existing non-blocking Vitest warning about
an un-awaited rejects assertion).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

formatjs: Version 9.4.0+ breaks on valid JSX outside formatjs calls

2 participants