Skip to content
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
55 changes: 44 additions & 11 deletions contrib/graphql-codegen-client-preset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,12 @@ fn to_pascal_case(s: &str) -> String {

fn apply_naming_convention(s: &str, naming_convention: &str) -> String {
match naming_convention {
"change-case-all#pascalCase" => to_pascal_case(s),
"change-case-all#upperCaseFirst" => upper_case_first(s),
_ => to_pascal_case(s),
// GraphQL Codegen supports `keep`; preserve casing in that mode.
"keep" => s.to_string(),
// Keep unknown/custom conventions unchanged instead of forcing PascalCase.
_ => s.to_string(),
}
}

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

// using PathBuf to add the relative path to the artifact directory
let mut file_full_path = PathBuf::from(&cwd);
file_full_path.push(&filename);
// Resolve filename first. In WASM runtimes, `PathBuf::push` would treat
// `C:/...` as relative, so we must detect Windows absolute paths
// ourselves.
let file_full_path = if is_absolute_path(&filename) {
PathBuf::from(&filename)
} else {
let mut path = PathBuf::from(&cwd);
path.push(&filename);
path
};
let file_s_dirname = file_full_path.parent().unwrap();

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

let import_name = apply_naming_convention(
&operation_name,
&self.options.naming_convention,
);
let import_name =
apply_naming_convention(&operation_name, &self.options.naming_convention);

self.graphql_operations_or_fragments_to_import
.push(import_name.clone());
Expand Down Expand Up @@ -340,6 +349,30 @@ fn naming_convention_default() -> String {
"change-case-all#pascalCase".to_string()
}

#[derive(Deserialize)]
#[serde(untagged)]
enum NamingConventionOption {
String(String),
Object(serde_json::Value),
}

impl NamingConventionOption {
fn as_type_name_convention(&self) -> String {
match self {
NamingConventionOption::String(v) => v.to_string(),
NamingConventionOption::Object(v) => v
.get("typeNames")
.and_then(|t| t.as_str())
.map(str::to_string)
.unwrap_or_else(naming_convention_default),
}
}
}

fn naming_convention_option_default() -> NamingConventionOption {
NamingConventionOption::String(naming_convention_default())
}

#[allow(non_snake_case)]
#[derive(Deserialize)]
struct PluginOptions {
Expand All @@ -348,8 +381,8 @@ struct PluginOptions {
#[serde(default = "gql_default")]
gqlTagName: String,

#[serde(default = "naming_convention_default")]
namingConvention: String,
#[serde(default = "naming_convention_option_default")]
namingConvention: NamingConventionOption,
}

#[plugin_transform]
Expand Down Expand Up @@ -380,7 +413,7 @@ pub fn process_transform(program: Program, metadata: TransformPluginProgramMetad
cwd,
artifact_directory,
gql_tag_name: plugin_config.gqlTagName,
naming_convention: plugin_config.namingConvention,
naming_convention: plugin_config.namingConvention.as_type_name_convention(),
});

program.apply(&mut visit_mut_pass(visitor))
Expand Down
66 changes: 63 additions & 3 deletions contrib/graphql-codegen-client-preset/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,11 @@ fn import_files_from_other_directory(input_path: PathBuf) {
}

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

#[test]
fn windows_absolute_filename_path_gets_correct_relative_import_path() {
let visitor = get_windows_path_visitor();
let import_path = visitor.get_relative_import_path("graphql");

assert_eq!(import_path, "./gql/graphql");
}

test!(
Default::default(),
|_| visit_mut_pass(get_test_code_visitor()),
Expand Down Expand Up @@ -210,3 +219,54 @@ const SomeEGRockets = gql(`
}
`);"#
);

#[test]
fn naming_convention_keep_preserves_original_name() {
assert_eq!(
apply_naming_convention("SomeEGRocketsDocument", "keep"),
"SomeEGRocketsDocument"
);
}

#[test]
fn naming_convention_unknown_preserves_original_name() {
assert_eq!(
apply_naming_convention("SomeEGRocketsDocument", "lodash#camelCase"),
"SomeEGRocketsDocument"
);
}

#[test]
fn plugin_options_accept_object_naming_convention() {
let options: PluginOptions = serde_json::from_str(
r#"{
"artifactDirectory":"./src/gql",
"namingConvention":{
"typeNames":"keep",
"enumValues":"change-case-all#upperCaseFirst",
"transformUnderscore":true
}
}"#,
)
.unwrap();

assert_eq!(options.namingConvention.as_type_name_convention(), "keep");
}

#[test]
fn plugin_options_object_without_type_names_uses_default_naming_convention() {
let options: PluginOptions = serde_json::from_str(
r#"{
"artifactDirectory":"./src/gql",
"namingConvention":{
"enumValues":"keep"
}
}"#,
)
.unwrap();

assert_eq!(
options.namingConvention.as_type_name_convention(),
"change-case-all#pascalCase"
);
}
89 changes: 67 additions & 22 deletions packages/emotion/transform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ use regex::{Regex, RegexBuilder};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use swc_atoms::{atom, Atom, Wtf8Atom};
use swc_common::{comments::Comments, util::take::Take, BytePos, SourceMapperDyn, DUMMY_SP};
use swc_common::{
comments::Comments, util::take::Take, BytePos, SourceMapperDyn, Span, Spanned, DUMMY_SP,
};
use swc_ecma_ast::{
fn_pass, ArrayLit, CallExpr, Callee, ClassDecl, ClassMethod, ClassProp, Expr, ExprOrSpread,
FnDecl, Id, Ident, IdentName, ImportDecl, ImportSpecifier, JSXAttr, JSXAttrName,
JSXAttrOrSpread, JSXAttrValue, JSXElement, JSXElementName, JSXExpr, JSXExprContainer,
JSXObject, KeyValueProp, Lit, MemberProp, MethodProp, ModuleExportName, ObjectLit, Pass, Pat,
Prop, PropName, PropOrSpread, SourceMapperExt, SpreadElement, Tpl, VarDeclarator,
JSXObject, KeyValueProp, Lit, MemberExpr, MemberProp, MethodProp, ModuleExportName, ObjectLit,
Pass, Pat, Prop, PropName, PropOrSpread, SourceMapperExt, SpreadElement, Tpl, VarDeclarator,
};
use swc_ecma_utils::ExprFactory;
use swc_ecma_visit::{Fold, FoldWith, Visit, VisitWith};
Expand Down Expand Up @@ -242,6 +244,14 @@ impl<'a, C: Comments> EmotionTransformer<'a, C> {
label
}

fn create_tagged_tpl_label_arg(&self) -> ExprOrSpread {
let mut label = self.create_label(true);
if !label.is_empty() && self.options.sourcemap.unwrap_or(false) && !label.ends_with(';') {
label.push(';');
}
label.as_arg()
}

fn create_sourcemap(&mut self, pos: BytePos) -> Option<String> {
if self.options.sourcemap.unwrap_or(false) {
let loc = self.cm.get_code_map().lookup_char_pos(pos);
Expand Down Expand Up @@ -433,30 +443,42 @@ impl<'a, C: Comments> EmotionTransformer<'a, C> {
attrs: &mut [JSXAttrOrSpread],
label_context: Option<String>,
) {
// Find a css identifier (ExprKind::Css) in import_packages
let css_id = self
#[derive(Clone)]
enum CssPropCallee {
Named(Id),
Namespace { id: Id, export_name: String },
}

// Find css from either named imports (`import { css }`) or namespace
// imports (`import * as emotionReact`).
let css_callee = self
.import_packages
.iter()
.find_map(|(id, meta)| {
if matches!(meta, PackageMeta::Named(ExprKind::Css)) {
Some(id.clone())
} else {
None
}
.find_map(|(id, meta)| match meta {
PackageMeta::Named(ExprKind::Css) => Some(CssPropCallee::Named(id.clone())),
PackageMeta::Namespace(module) => module
.exported_names
.iter()
.find(|item| matches!(item.kind, ExprKind::Css))
.map(|item| CssPropCallee::Namespace {
Comment on lines +459 to +463
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 Match css-prop namespace calls only on a css export

In the new namespace-import css-prop path, this picks the first export whose kind is ExprKind::Css, but that enum is also used for keyframes aliases (packages/emotion/transform/src/import_map.rs:43-51). If a project uses an import-map package with import * as emo from "pkg" and the only mapped emotion-style export is keyframes, <div css={{...}} /> will now be rewritten to emo.keyframes(...), which returns an animation name instead of serialized styles. The namespace match here needs to require an actual css export, not any ExprKind::Css entry.

Useful? React with 👍 / 👎.

id: id.clone(),
export_name: item.name.clone(),
}),
_ => None,
});

let Some((css_sym, css_ctxt)) = css_id else {
let Some(css_callee) = css_callee else {
return;
};

for attr in attrs.iter_mut() {
if let JSXAttrOrSpread::JSXAttr(JSXAttr {
name: JSXAttrName::Ident(name_ident),
value: Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
expr: JSXExpr::Expr(expr),
span: container_span,
..
})),
value:
Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
expr: JSXExpr::Expr(expr),
..
})),
..
}) = attr
{
Expand All @@ -471,7 +493,7 @@ impl<'a, C: Comments> EmotionTransformer<'a, C> {
_ => continue,
}

let expr_pos = container_span.lo;
let expr_pos = expr.span().lo();
let raw_expr = expr.take();
let mut args = vec![raw_expr.as_arg()];

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

self.comments.add_pure_comment(expr_pos);
let call_span = Span::new(expr_pos, expr_pos);

let callee = match &css_callee {
CssPropCallee::Named((css_sym, css_ctxt)) => {
Ident::new(css_sym.clone(), call_span, *css_ctxt).as_callee()
}
CssPropCallee::Namespace {
id: (namespace_sym, namespace_ctxt),
export_name,
} => Expr::Member(MemberExpr {
span: call_span,
obj: Box::new(Expr::Ident(Ident::new(
namespace_sym.clone(),
call_span,
*namespace_ctxt,
))),
prop: MemberProp::Ident(IdentName::new(
export_name.clone().into(),
call_span,
)),
})
.as_callee(),
};

*expr = Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Ident::new(css_sym, DUMMY_SP, css_ctxt).as_callee(),
span: call_span,
callee,
args,
..Default::default()
}));
Expand Down Expand Up @@ -804,7 +849,7 @@ impl<C: Comments> Fold for EmotionTransformer<'_, C> {
if !self.in_jsx_element {
self.comments.add_pure_comment(i.span.lo());
if self.options.auto_label.unwrap_or(false) {
args.push(self.create_label(true).as_arg());
args.push(self.create_tagged_tpl_label_arg());
}
if let Some(cm) = self.create_sourcemap(tagged_tpl.span.lo()) {
args.push(cm.as_arg());
Expand Down Expand Up @@ -880,7 +925,7 @@ impl<C: Comments> Fold for EmotionTransformer<'_, C> {
&mut tagged_tpl.tpl,
);
if self.options.auto_label.unwrap_or(false) {
args.push(self.create_label(true).as_arg());
args.push(self.create_tagged_tpl_label_arg());
}
if let Some(cm) =
self.create_sourcemap(tagged_tpl.span.lo())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const Animated = /*#__PURE__*/ styled("div", {
target: "e1xgaop21",
label: "Animated"
})("& code{background-color:linen;}animation:", ({ animation })=>animation, " 0.2s infinite ease-in-out alternate;", "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5wdXQudHMiLCJzb3VyY2VzIjpbImlucHV0LnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNzcyB9IGZyb20gXCJAZW1vdGlvbi9yZWFjdFwiO1xuaW1wb3J0IHN0eWxlZCBmcm9tIFwiQGVtb3Rpb24vc3R5bGVkXCI7XG5cbmNvbnN0IHVuaXROb3JtYWwgPSBcIjFyZW1cIjtcbmNvbnN0IHVuaXRMYXJnZSA9IFwiMnJlbVwiO1xuXG5jb25zdCBFeGFtcGxlID0gc3R5bGVkLmRpdmBcbiAgbWFyZ2luOiAke3VuaXROb3JtYWx9ICR7dW5pdExhcmdlfTtcbmA7XG5cbmV4cG9ydCBjb25zdCBBbmltYXRlZCA9IHN0eWxlZC5kaXZgXG4gICYgY29kZSB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogbGluZW47XG4gIH1cbiAgYW5pbWF0aW9uOiAkeyh7IGFuaW1hdGlvbiB9KSA9PiBhbmltYXRpb259IDAuMnMgaW5maW5pdGUgZWFzZS1pbi1vdXQgYWx0ZXJuYXRlO1xuYDtcblxuY29uc3Qgc2hhZG93Qm9yZGVyID0gKHsgd2lkdGggPSBcIjFweFwiLCBjb2xvciB9KSA9PiBjc3NgXG4gIGJveC1zaGFkb3c6IGluc2V0IDBweCAwcHggMHB4ICR7d2lkdGh9ICR7Y29sb3J9O1xuYDtcblxuY29uc3QgU3R5bGVkSW5wdXQgPSBzdHlsZWQuaW5wdXRgXG4gICR7c2hhZG93Qm9yZGVyKHsgY29sb3I6IFwicmVkXCIsIHdpZHRoOiBcIjRweFwiIH0pfVxuYDtcbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFVd0IifQ== */");
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= */");
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= */");
const StyledInput = /*#__PURE__*/ styled("input", {
target: "e1xgaop22",
label: "StyledInput"
Expand Down
Loading
Loading