Skip to content

Commit 9ab2a34

Browse files
authored
fix: address codex-bot follow-ups for 2026-03-21 merged PRs (#600)
## 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).
1 parent e2775d6 commit 9ab2a34

35 files changed

Lines changed: 362 additions & 111 deletions

File tree

contrib/graphql-codegen-client-preset/src/lib.rs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,12 @@ fn to_pascal_case(s: &str) -> String {
8383

8484
fn apply_naming_convention(s: &str, naming_convention: &str) -> String {
8585
match naming_convention {
86+
"change-case-all#pascalCase" => to_pascal_case(s),
8687
"change-case-all#upperCaseFirst" => upper_case_first(s),
87-
_ => to_pascal_case(s),
88+
// GraphQL Codegen supports `keep`; preserve casing in that mode.
89+
"keep" => s.to_string(),
90+
// Keep unknown/custom conventions unchanged instead of forcing PascalCase.
91+
_ => s.to_string(),
8892
}
8993
}
9094

@@ -146,9 +150,16 @@ impl GraphQLVisitor {
146150
let filename = self.options.filename.replace('\\', "/");
147151
let artifact_directory = self.options.artifact_directory.replace('\\', "/");
148152

149-
// using PathBuf to add the relative path to the artifact directory
150-
let mut file_full_path = PathBuf::from(&cwd);
151-
file_full_path.push(&filename);
153+
// Resolve filename first. In WASM runtimes, `PathBuf::push` would treat
154+
// `C:/...` as relative, so we must detect Windows absolute paths
155+
// ourselves.
156+
let file_full_path = if is_absolute_path(&filename) {
157+
PathBuf::from(&filename)
158+
} else {
159+
let mut path = PathBuf::from(&cwd);
160+
path.push(&filename);
161+
path
162+
};
152163
let file_s_dirname = file_full_path.parent().unwrap();
153164

154165
// The resolved artifact directory as seen from the current running SWC plugin
@@ -260,10 +271,8 @@ impl VisitMut for GraphQLVisitor {
260271
},
261272
};
262273

263-
let import_name = apply_naming_convention(
264-
&operation_name,
265-
&self.options.naming_convention,
266-
);
274+
let import_name =
275+
apply_naming_convention(&operation_name, &self.options.naming_convention);
267276

268277
self.graphql_operations_or_fragments_to_import
269278
.push(import_name.clone());
@@ -340,6 +349,30 @@ fn naming_convention_default() -> String {
340349
"change-case-all#pascalCase".to_string()
341350
}
342351

352+
#[derive(Deserialize)]
353+
#[serde(untagged)]
354+
enum NamingConventionOption {
355+
String(String),
356+
Object(serde_json::Value),
357+
}
358+
359+
impl NamingConventionOption {
360+
fn as_type_name_convention(&self) -> String {
361+
match self {
362+
NamingConventionOption::String(v) => v.to_string(),
363+
NamingConventionOption::Object(v) => v
364+
.get("typeNames")
365+
.and_then(|t| t.as_str())
366+
.map(str::to_string)
367+
.unwrap_or_else(naming_convention_default),
368+
}
369+
}
370+
}
371+
372+
fn naming_convention_option_default() -> NamingConventionOption {
373+
NamingConventionOption::String(naming_convention_default())
374+
}
375+
343376
#[allow(non_snake_case)]
344377
#[derive(Deserialize)]
345378
struct PluginOptions {
@@ -348,8 +381,8 @@ struct PluginOptions {
348381
#[serde(default = "gql_default")]
349382
gqlTagName: String,
350383

351-
#[serde(default = "naming_convention_default")]
352-
namingConvention: String,
384+
#[serde(default = "naming_convention_option_default")]
385+
namingConvention: NamingConventionOption,
353386
}
354387

355388
#[plugin_transform]
@@ -380,7 +413,7 @@ pub fn process_transform(program: Program, metadata: TransformPluginProgramMetad
380413
cwd,
381414
artifact_directory,
382415
gql_tag_name: plugin_config.gqlTagName,
383-
naming_convention: plugin_config.namingConvention,
416+
naming_convention: plugin_config.namingConvention.as_type_name_convention(),
384417
});
385418

386419
program.apply(&mut visit_mut_pass(visitor))

contrib/graphql-codegen-client-preset/src/tests.rs

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,11 @@ fn import_files_from_other_directory(input_path: PathBuf) {
9393
}
9494

9595
fn get_windows_path_visitor() -> GraphQLVisitor {
96-
// Simulate a Windows environment where cwd and artifact_directory use backslashes.
97-
// The WASM plugin runs with Unix path semantics, so it must normalize these.
96+
// Simulate a Windows environment where cwd and artifact_directory use
97+
// backslashes. The WASM plugin runs with Unix path semantics, so it must
98+
// normalize these.
9899
GraphQLVisitor::new(GraphQLCodegenOptions {
99-
filename: "src\\App.tsx".to_string(),
100+
filename: "C:\\Users\\user\\project\\src\\App.tsx".to_string(),
100101
cwd: "C:\\Users\\user\\project".to_string(),
101102
artifact_directory: "C:\\Users\\user\\project\\src\\gql".to_string(),
102103
gql_tag_name: "gql".to_string(),
@@ -119,6 +120,14 @@ const GetUser = gql(`
119120
`);"#
120121
);
121122

123+
#[test]
124+
fn windows_absolute_filename_path_gets_correct_relative_import_path() {
125+
let visitor = get_windows_path_visitor();
126+
let import_path = visitor.get_relative_import_path("graphql");
127+
128+
assert_eq!(import_path, "./gql/graphql");
129+
}
130+
122131
test!(
123132
Default::default(),
124133
|_| visit_mut_pass(get_test_code_visitor()),
@@ -210,3 +219,54 @@ const SomeEGRockets = gql(`
210219
}
211220
`);"#
212221
);
222+
223+
#[test]
224+
fn naming_convention_keep_preserves_original_name() {
225+
assert_eq!(
226+
apply_naming_convention("SomeEGRocketsDocument", "keep"),
227+
"SomeEGRocketsDocument"
228+
);
229+
}
230+
231+
#[test]
232+
fn naming_convention_unknown_preserves_original_name() {
233+
assert_eq!(
234+
apply_naming_convention("SomeEGRocketsDocument", "lodash#camelCase"),
235+
"SomeEGRocketsDocument"
236+
);
237+
}
238+
239+
#[test]
240+
fn plugin_options_accept_object_naming_convention() {
241+
let options: PluginOptions = serde_json::from_str(
242+
r#"{
243+
"artifactDirectory":"./src/gql",
244+
"namingConvention":{
245+
"typeNames":"keep",
246+
"enumValues":"change-case-all#upperCaseFirst",
247+
"transformUnderscore":true
248+
}
249+
}"#,
250+
)
251+
.unwrap();
252+
253+
assert_eq!(options.namingConvention.as_type_name_convention(), "keep");
254+
}
255+
256+
#[test]
257+
fn plugin_options_object_without_type_names_uses_default_naming_convention() {
258+
let options: PluginOptions = serde_json::from_str(
259+
r#"{
260+
"artifactDirectory":"./src/gql",
261+
"namingConvention":{
262+
"enumValues":"keep"
263+
}
264+
}"#,
265+
)
266+
.unwrap();
267+
268+
assert_eq!(
269+
options.namingConvention.as_type_name_convention(),
270+
"change-case-all#pascalCase"
271+
);
272+
}

packages/emotion/transform/src/lib.rs

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ use regex::{Regex, RegexBuilder};
77
use rustc_hash::FxHashMap;
88
use serde::{Deserialize, Serialize};
99
use swc_atoms::{atom, Atom, Wtf8Atom};
10-
use swc_common::{comments::Comments, util::take::Take, BytePos, SourceMapperDyn, DUMMY_SP};
10+
use swc_common::{
11+
comments::Comments, util::take::Take, BytePos, SourceMapperDyn, Span, Spanned, DUMMY_SP,
12+
};
1113
use swc_ecma_ast::{
1214
fn_pass, ArrayLit, CallExpr, Callee, ClassDecl, ClassMethod, ClassProp, Expr, ExprOrSpread,
1315
FnDecl, Id, Ident, IdentName, ImportDecl, ImportSpecifier, JSXAttr, JSXAttrName,
1416
JSXAttrOrSpread, JSXAttrValue, JSXElement, JSXElementName, JSXExpr, JSXExprContainer,
15-
JSXObject, KeyValueProp, Lit, MemberProp, MethodProp, ModuleExportName, ObjectLit, Pass, Pat,
16-
Prop, PropName, PropOrSpread, SourceMapperExt, SpreadElement, Tpl, VarDeclarator,
17+
JSXObject, KeyValueProp, Lit, MemberExpr, MemberProp, MethodProp, ModuleExportName, ObjectLit,
18+
Pass, Pat, Prop, PropName, PropOrSpread, SourceMapperExt, SpreadElement, Tpl, VarDeclarator,
1719
};
1820
use swc_ecma_utils::ExprFactory;
1921
use swc_ecma_visit::{Fold, FoldWith, Visit, VisitWith};
@@ -242,6 +244,14 @@ impl<'a, C: Comments> EmotionTransformer<'a, C> {
242244
label
243245
}
244246

247+
fn create_tagged_tpl_label_arg(&self) -> ExprOrSpread {
248+
let mut label = self.create_label(true);
249+
if !label.is_empty() && self.options.sourcemap.unwrap_or(false) && !label.ends_with(';') {
250+
label.push(';');
251+
}
252+
label.as_arg()
253+
}
254+
245255
fn create_sourcemap(&mut self, pos: BytePos) -> Option<String> {
246256
if self.options.sourcemap.unwrap_or(false) {
247257
let loc = self.cm.get_code_map().lookup_char_pos(pos);
@@ -433,30 +443,42 @@ impl<'a, C: Comments> EmotionTransformer<'a, C> {
433443
attrs: &mut [JSXAttrOrSpread],
434444
label_context: Option<String>,
435445
) {
436-
// Find a css identifier (ExprKind::Css) in import_packages
437-
let css_id = self
446+
#[derive(Clone)]
447+
enum CssPropCallee {
448+
Named(Id),
449+
Namespace { id: Id, export_name: String },
450+
}
451+
452+
// Find css from either named imports (`import { css }`) or namespace
453+
// imports (`import * as emotionReact`).
454+
let css_callee = self
438455
.import_packages
439456
.iter()
440-
.find_map(|(id, meta)| {
441-
if matches!(meta, PackageMeta::Named(ExprKind::Css)) {
442-
Some(id.clone())
443-
} else {
444-
None
445-
}
457+
.find_map(|(id, meta)| match meta {
458+
PackageMeta::Named(ExprKind::Css) => Some(CssPropCallee::Named(id.clone())),
459+
PackageMeta::Namespace(module) => module
460+
.exported_names
461+
.iter()
462+
.find(|item| matches!(item.kind, ExprKind::Css))
463+
.map(|item| CssPropCallee::Namespace {
464+
id: id.clone(),
465+
export_name: item.name.clone(),
466+
}),
467+
_ => None,
446468
});
447469

448-
let Some((css_sym, css_ctxt)) = css_id else {
470+
let Some(css_callee) = css_callee else {
449471
return;
450472
};
451473

452474
for attr in attrs.iter_mut() {
453475
if let JSXAttrOrSpread::JSXAttr(JSXAttr {
454476
name: JSXAttrName::Ident(name_ident),
455-
value: Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
456-
expr: JSXExpr::Expr(expr),
457-
span: container_span,
458-
..
459-
})),
477+
value:
478+
Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
479+
expr: JSXExpr::Expr(expr),
480+
..
481+
})),
460482
..
461483
}) = attr
462484
{
@@ -471,7 +493,7 @@ impl<'a, C: Comments> EmotionTransformer<'a, C> {
471493
_ => continue,
472494
}
473495

474-
let expr_pos = container_span.lo;
496+
let expr_pos = expr.span().lo();
475497
let raw_expr = expr.take();
476498
let mut args = vec![raw_expr.as_arg()];
477499

@@ -492,10 +514,33 @@ impl<'a, C: Comments> EmotionTransformer<'a, C> {
492514
}
493515

494516
self.comments.add_pure_comment(expr_pos);
517+
let call_span = Span::new(expr_pos, expr_pos);
518+
519+
let callee = match &css_callee {
520+
CssPropCallee::Named((css_sym, css_ctxt)) => {
521+
Ident::new(css_sym.clone(), call_span, *css_ctxt).as_callee()
522+
}
523+
CssPropCallee::Namespace {
524+
id: (namespace_sym, namespace_ctxt),
525+
export_name,
526+
} => Expr::Member(MemberExpr {
527+
span: call_span,
528+
obj: Box::new(Expr::Ident(Ident::new(
529+
namespace_sym.clone(),
530+
call_span,
531+
*namespace_ctxt,
532+
))),
533+
prop: MemberProp::Ident(IdentName::new(
534+
export_name.clone().into(),
535+
call_span,
536+
)),
537+
})
538+
.as_callee(),
539+
};
495540

496541
*expr = Box::new(Expr::Call(CallExpr {
497-
span: DUMMY_SP,
498-
callee: Ident::new(css_sym, DUMMY_SP, css_ctxt).as_callee(),
542+
span: call_span,
543+
callee,
499544
args,
500545
..Default::default()
501546
}));
@@ -804,7 +849,7 @@ impl<C: Comments> Fold for EmotionTransformer<'_, C> {
804849
if !self.in_jsx_element {
805850
self.comments.add_pure_comment(i.span.lo());
806851
if self.options.auto_label.unwrap_or(false) {
807-
args.push(self.create_label(true).as_arg());
852+
args.push(self.create_tagged_tpl_label_arg());
808853
}
809854
if let Some(cm) = self.create_sourcemap(tagged_tpl.span.lo()) {
810855
args.push(cm.as_arg());
@@ -880,7 +925,7 @@ impl<C: Comments> Fold for EmotionTransformer<'_, C> {
880925
&mut tagged_tpl.tpl,
881926
);
882927
if self.options.auto_label.unwrap_or(false) {
883-
args.push(self.create_label(true).as_arg());
928+
args.push(self.create_tagged_tpl_label_arg());
884929
}
885930
if let Some(cm) =
886931
self.create_sourcemap(tagged_tpl.span.lo())

packages/emotion/transform/tests/fixture/compress/output.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const Animated = /*#__PURE__*/ styled("div", {
1010
target: "e1xgaop21",
1111
label: "Animated"
1212
})("& code{background-color:linen;}animation:", ({ animation })=>animation, " 0.2s infinite ease-in-out alternate;", "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5wdXQudHMiLCJzb3VyY2VzIjpbImlucHV0LnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNzcyB9IGZyb20gXCJAZW1vdGlvbi9yZWFjdFwiO1xuaW1wb3J0IHN0eWxlZCBmcm9tIFwiQGVtb3Rpb24vc3R5bGVkXCI7XG5cbmNvbnN0IHVuaXROb3JtYWwgPSBcIjFyZW1cIjtcbmNvbnN0IHVuaXRMYXJnZSA9IFwiMnJlbVwiO1xuXG5jb25zdCBFeGFtcGxlID0gc3R5bGVkLmRpdmBcbiAgbWFyZ2luOiAke3VuaXROb3JtYWx9ICR7dW5pdExhcmdlfTtcbmA7XG5cbmV4cG9ydCBjb25zdCBBbmltYXRlZCA9IHN0eWxlZC5kaXZgXG4gICYgY29kZSB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogbGluZW47XG4gIH1cbiAgYW5pbWF0aW9uOiAkeyh7IGFuaW1hdGlvbiB9KSA9PiBhbmltYXRpb259IDAuMnMgaW5maW5pdGUgZWFzZS1pbi1vdXQgYWx0ZXJuYXRlO1xuYDtcblxuY29uc3Qgc2hhZG93Qm9yZGVyID0gKHsgd2lkdGggPSBcIjFweFwiLCBjb2xvciB9KSA9PiBjc3NgXG4gIGJveC1zaGFkb3c6IGluc2V0IDBweCAwcHggMHB4ICR7d2lkdGh9ICR7Y29sb3J9O1xuYDtcblxuY29uc3QgU3R5bGVkSW5wdXQgPSBzdHlsZWQuaW5wdXRgXG4gICR7c2hhZG93Qm9yZGVyKHsgY29sb3I6IFwicmVkXCIsIHdpZHRoOiBcIjRweFwiIH0pfVxuYDtcbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFVd0IifQ== */");
13-
const shadowBorder = ({ width = "1px", color })=>/*#__PURE__*/ css("box-shadow:inset 0px 0px 0px ", width, " ", color, ";", "label:shadowBorder", "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5wdXQudHMiLCJzb3VyY2VzIjpbImlucHV0LnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNzcyB9IGZyb20gXCJAZW1vdGlvbi9yZWFjdFwiO1xuaW1wb3J0IHN0eWxlZCBmcm9tIFwiQGVtb3Rpb24vc3R5bGVkXCI7XG5cbmNvbnN0IHVuaXROb3JtYWwgPSBcIjFyZW1cIjtcbmNvbnN0IHVuaXRMYXJnZSA9IFwiMnJlbVwiO1xuXG5jb25zdCBFeGFtcGxlID0gc3R5bGVkLmRpdmBcbiAgbWFyZ2luOiAke3VuaXROb3JtYWx9ICR7dW5pdExhcmdlfTtcbmA7XG5cbmV4cG9ydCBjb25zdCBBbmltYXRlZCA9IHN0eWxlZC5kaXZgXG4gICYgY29kZSB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogbGluZW47XG4gIH1cbiAgYW5pbWF0aW9uOiAkeyh7IGFuaW1hdGlvbiB9KSA9PiBhbmltYXRpb259IDAuMnMgaW5maW5pdGUgZWFzZS1pbi1vdXQgYWx0ZXJuYXRlO1xuYDtcblxuY29uc3Qgc2hhZG93Qm9yZGVyID0gKHsgd2lkdGggPSBcIjFweFwiLCBjb2xvciB9KSA9PiBjc3NgXG4gIGJveC1zaGFkb3c6IGluc2V0IDBweCAwcHggMHB4ICR7d2lkdGh9ICR7Y29sb3J9O1xuYDtcblxuY29uc3QgU3R5bGVkSW5wdXQgPSBzdHlsZWQuaW5wdXRgXG4gICR7c2hhZG93Qm9yZGVyKHsgY29sb3I6IFwicmVkXCIsIHdpZHRoOiBcIjRweFwiIH0pfVxuYDtcbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFpQm1EIn0= */");
13+
const shadowBorder = ({ width = "1px", color })=>/*#__PURE__*/ css("box-shadow:inset 0px 0px 0px ", width, " ", color, ";", "label:shadowBorder;", "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5wdXQudHMiLCJzb3VyY2VzIjpbImlucHV0LnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNzcyB9IGZyb20gXCJAZW1vdGlvbi9yZWFjdFwiO1xuaW1wb3J0IHN0eWxlZCBmcm9tIFwiQGVtb3Rpb24vc3R5bGVkXCI7XG5cbmNvbnN0IHVuaXROb3JtYWwgPSBcIjFyZW1cIjtcbmNvbnN0IHVuaXRMYXJnZSA9IFwiMnJlbVwiO1xuXG5jb25zdCBFeGFtcGxlID0gc3R5bGVkLmRpdmBcbiAgbWFyZ2luOiAke3VuaXROb3JtYWx9ICR7dW5pdExhcmdlfTtcbmA7XG5cbmV4cG9ydCBjb25zdCBBbmltYXRlZCA9IHN0eWxlZC5kaXZgXG4gICYgY29kZSB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogbGluZW47XG4gIH1cbiAgYW5pbWF0aW9uOiAkeyh7IGFuaW1hdGlvbiB9KSA9PiBhbmltYXRpb259IDAuMnMgaW5maW5pdGUgZWFzZS1pbi1vdXQgYWx0ZXJuYXRlO1xuYDtcblxuY29uc3Qgc2hhZG93Qm9yZGVyID0gKHsgd2lkdGggPSBcIjFweFwiLCBjb2xvciB9KSA9PiBjc3NgXG4gIGJveC1zaGFkb3c6IGluc2V0IDBweCAwcHggMHB4ICR7d2lkdGh9ICR7Y29sb3J9O1xuYDtcblxuY29uc3QgU3R5bGVkSW5wdXQgPSBzdHlsZWQuaW5wdXRgXG4gICR7c2hhZG93Qm9yZGVyKHsgY29sb3I6IFwicmVkXCIsIHdpZHRoOiBcIjRweFwiIH0pfVxuYDtcbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFpQm1EIn0= */");
1414
const StyledInput = /*#__PURE__*/ styled("input", {
1515
target: "e1xgaop22",
1616
label: "StyledInput"

0 commit comments

Comments
 (0)