Skip to content

Commit ef60b07

Browse files
committed
feature(swc-plugin): switch to Fold
1 parent 1f3a24b commit ef60b07

File tree

2 files changed

+94
-113
lines changed

2 files changed

+94
-113
lines changed

packages/presets/swc-plugin/src/lib.rs

Lines changed: 88 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use serde::Deserialize;
44
use std::path::{Path, PathBuf};
55
use swc_core::{
66
atoms::Atom,
7-
common::{util::take::Take, Span},
7+
common::Span,
88
ecma::{
99
ast::*,
1010
utils::{prepend_stmts, quote_ident},
11-
visit::{visit_mut_pass, VisitMut, VisitMutWith},
11+
visit::{fold_pass, Fold, FoldWith},
1212
},
1313
plugin::{
1414
errors::HANDLER, metadata::TransformPluginMetadataContextKind, plugin_transform,
@@ -30,16 +30,16 @@ pub struct GraphQLCodegenOptions {
3030
pub gql_tag_name: String,
3131
}
3232

33-
pub struct GraphQLVisitor {
33+
pub struct GraphQLCodegen {
3434
options: GraphQLCodegenOptions,
35-
graphql_operations_or_fragments_to_import: Vec<GraphQLModuleItem>,
35+
imports: Vec<GraphQLModuleItem>,
3636
}
3737

38-
impl GraphQLVisitor {
38+
impl GraphQLCodegen {
3939
pub fn new(options: GraphQLCodegenOptions) -> Self {
40-
GraphQLVisitor {
40+
GraphQLCodegen {
4141
options,
42-
graphql_operations_or_fragments_to_import: Vec::new(),
42+
imports: Vec::new(),
4343
}
4444
}
4545

@@ -77,120 +77,103 @@ impl GraphQLVisitor {
7777
let platform_specific_path = start_of_path.to_string() + relative.to_str().unwrap();
7878
platform_specific_path.replace('\\', "/")
7979
}
80-
}
8180

82-
pub fn create_graphql_codegen_visitor(options: GraphQLCodegenOptions) -> impl VisitMut {
83-
GraphQLVisitor::new(options)
84-
}
81+
fn build_call_expr_from_call(&mut self, call: &CallExpr) -> Option<Expr> {
82+
let ident = call.callee.as_expr()?.as_ident()?;
83+
if &*ident.sym != self.options.gql_tag_name.as_str() {
84+
return None;
85+
}
8586

86-
impl VisitMut for GraphQLVisitor {
87-
fn visit_mut_var_decl(&mut self, e: &mut VarDecl) {
88-
e.visit_mut_children_with(self);
87+
if call.args.is_empty() {
88+
self.handle_error("missing GraphQL query", call.span);
89+
return None;
90+
}
8991

90-
for decl in e.decls.iter_mut() {
91-
if let Some(init) = &mut decl.init {
92-
if let Expr::Call(CallExpr { callee, args, .. }) = &mut **init {
93-
if args.is_empty() {
94-
return;
95-
}
92+
let tpl = &dbg!(&call.args[0].expr).as_tpl()?;
93+
let graphql_ast = tpl.quasis.iter().find_map(|quasis| {
94+
match parse_query::<&str>(dbg!(quasis.cooked.as_ref()?)) {
95+
Ok(ast) => Some(ast),
96+
Err(e) => {
97+
// Currently the parser outputs a string like: "query parse error", so we add "GraphQL" to the beginning
98+
let error = format!("GraphQL {}", e);
99+
self.handle_error(error.as_str(), quasis.span);
100+
return None;
101+
}
102+
}
103+
})?;
104+
105+
let first_definition = match graphql_ast.definitions.get(0) {
106+
Some(definition) => definition,
107+
None => return None,
108+
};
96109

97-
match callee.as_expr() {
98-
Some(expr_box) => match &**expr_box {
99-
Expr::Ident(ident) => {
100-
if &ident.sym != self.options.gql_tag_name.as_str() {
101-
return;
102-
}
103-
}
104-
_ => return,
105-
},
106-
_ => return,
110+
let operation_name = match first_definition {
111+
graphql_parser::query::Definition::Fragment(fragment) => {
112+
fragment.name.to_string() + "FragmentDoc"
113+
}
114+
graphql_parser::query::Definition::Operation(op) => match op {
115+
graphql_parser::query::OperationDefinition::Query(query) => match query.name {
116+
Some(name) => name.to_string() + "Document",
117+
None => return None,
118+
},
119+
graphql_parser::query::OperationDefinition::Mutation(mutation) => {
120+
match mutation.name {
121+
Some(name) => name.to_string() + "Document",
122+
None => return None,
123+
}
124+
}
125+
graphql_parser::query::OperationDefinition::Subscription(subscription) => {
126+
match subscription.name {
127+
Some(name) => name.to_string() + "Document",
128+
None => return None,
107129
}
130+
}
131+
_ => return None,
132+
},
133+
};
134+
135+
let capitalized_operation_name: Atom = capitalize(&operation_name).into();
136+
self.imports.push(GraphQLModuleItem {
137+
operation_or_fragment_name: capitalized_operation_name.clone(),
138+
});
139+
140+
// now change the call expression to a Identifier
141+
Some(Expr::Ident(quote_ident!(capitalized_operation_name).into()))
142+
}
143+
}
108144

109-
let quasis = match &*args[0].expr {
110-
Expr::Tpl(tpl) => &tpl.quasis,
111-
_ => return,
112-
};
113-
114-
let raw = match &quasis[0].cooked {
115-
Some(cooked) => cooked,
116-
None => return,
117-
};
118-
119-
let graphql_ast = match parse_query::<&str>(raw) {
120-
Ok(ast) => ast,
121-
Err(e) => {
122-
// Currently the parser outputs a string like: "query parse error", so we add "GraphQL" to the beginning
123-
let error = format!("GraphQL {}", e);
124-
self.handle_error(error.as_str(), quasis[0].span);
125-
return;
126-
}
127-
};
128-
129-
let first_definition = match graphql_ast.definitions.get(0) {
130-
Some(definition) => definition,
131-
None => return,
132-
};
133-
134-
let operation_name = match first_definition {
135-
graphql_parser::query::Definition::Fragment(fragment) => {
136-
fragment.name.to_string() + "FragmentDoc"
137-
}
138-
graphql_parser::query::Definition::Operation(op) => match op {
139-
graphql_parser::query::OperationDefinition::Query(query) => {
140-
match query.name {
141-
Some(name) => name.to_string() + "Document",
142-
None => return,
143-
}
144-
}
145-
graphql_parser::query::OperationDefinition::Mutation(mutation) => {
146-
match mutation.name {
147-
Some(name) => name.to_string() + "Document",
148-
None => return,
149-
}
150-
}
151-
graphql_parser::query::OperationDefinition::Subscription(
152-
subscription,
153-
) => match subscription.name {
154-
Some(name) => name.to_string() + "Document",
155-
None => return,
156-
},
157-
_ => return,
158-
},
159-
};
160-
161-
let capitalized_operation_name: Atom = capitalize(&operation_name).into();
162-
self.graphql_operations_or_fragments_to_import
163-
.push(GraphQLModuleItem {
164-
operation_or_fragment_name: capitalized_operation_name.clone(),
165-
});
166-
167-
// now change the call expression to a Identifier
168-
let new_expr = Expr::Ident(quote_ident!(capitalized_operation_name).into());
169-
170-
*init = Box::new(new_expr);
145+
impl Fold for GraphQLCodegen {
146+
fn fold_expr(&mut self, expr: Expr) -> Expr {
147+
let expr = expr.fold_children_with(self);
148+
match &expr {
149+
Expr::Call(call) => {
150+
if let Some(built_expr) = dbg!(self.build_call_expr_from_call(call)) {
151+
built_expr
152+
} else {
153+
expr
171154
}
172155
}
156+
_ => expr,
173157
}
174158
}
175159

176-
fn visit_mut_module(&mut self, module: &mut Module) {
177-
// First visit all its children, collect the GraphQL document names, and then add the necessary imports
178-
module.visit_mut_children_with(self);
179-
180-
if self.graphql_operations_or_fragments_to_import.is_empty() {
181-
return;
182-
}
160+
fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
161+
// First fold all its children, collect the GraphQL document names, and then add the necessary imports
162+
let mut items = items
163+
.into_iter()
164+
.map(|item| item.fold_children_with(self))
165+
.collect::<Vec<_>>();
183166

184167
let platform_specific_path: Atom = self.get_relative_import_path("graphql").into();
185168

186-
let mut items = module.body.take();
187169
prepend_stmts(
188170
&mut items,
189-
self.graphql_operations_or_fragments_to_import
171+
self.imports
190172
.iter()
191173
.map(|module_item| module_item.as_module_item(platform_specific_path.clone())),
192174
);
193-
module.body = items;
175+
176+
items
194177
}
195178
}
196179

@@ -249,12 +232,11 @@ pub fn process_transform(program: Program, metadata: TransformPluginProgramMetad
249232
panic!("artifactDirectory is not present in the config for @graphql-codegen/client-preset-swc-plugin");
250233
}
251234

252-
let visitor = create_graphql_codegen_visitor(GraphQLCodegenOptions {
235+
let pass = fold_pass(GraphQLCodegen::new(GraphQLCodegenOptions {
253236
filename,
254237
cwd,
255238
artifact_directory,
256239
gql_tag_name: plugin_config.gqlTagName,
257-
});
258-
259-
program.apply(&mut visit_mut_pass(visitor))
240+
}));
241+
program.apply(pass)
260242
}

packages/presets/swc-plugin/src/tests.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ use swc_core::{
33
ecma::{
44
parser::{Syntax, TsSyntax},
55
transforms::testing::{test, test_fixture},
6-
visit::visit_mut_pass,
76
},
87
testing,
98
};
109

1110
use super::*;
1211

13-
fn get_test_code_visitor() -> GraphQLVisitor {
14-
GraphQLVisitor::new(GraphQLCodegenOptions {
12+
fn get_test_code_visitor() -> GraphQLCodegen {
13+
GraphQLCodegen::new(GraphQLCodegenOptions {
1514
filename: "test.ts".to_string(),
1615
cwd: "/home/faketestproject".to_string(),
1716
artifact_directory: "./src/gql".to_string(),
@@ -33,7 +32,7 @@ fn import_files_from_same_directory(input_path: PathBuf) {
3332
..Default::default()
3433
}),
3534
&|_metadata| {
36-
visit_mut_pass(GraphQLVisitor::new(GraphQLCodegenOptions {
35+
fold_pass(GraphQLCodegen::new(GraphQLCodegenOptions {
3736
filename: relative_file_path.to_string_lossy().to_string(),
3837
cwd: cwd.to_string_lossy().to_string(),
3938
artifact_directory: "./tests/fixtures".to_string(),
@@ -62,7 +61,7 @@ fn import_files_from_other_directory(input_path: PathBuf) {
6261
..Default::default()
6362
}),
6463
&|_metadata| {
65-
visit_mut_pass(GraphQLVisitor::new(GraphQLCodegenOptions {
64+
fold_pass(GraphQLCodegen::new(GraphQLCodegenOptions {
6665
filename: relative_file_path.to_string_lossy().to_string(),
6766
cwd: cwd.to_string_lossy().to_string(),
6867
artifact_directory: cwd.to_string_lossy().to_string(),
@@ -77,7 +76,7 @@ fn import_files_from_other_directory(input_path: PathBuf) {
7776

7877
test!(
7978
Default::default(),
80-
|_| visit_mut_pass(get_test_code_visitor()),
79+
|_| fold_pass(get_test_code_visitor()),
8180
expect_normal_declarations_to_not_panic_and_to_be_ignored,
8281
// Example from Next.js' server.js
8382
r#"const emitter = (0, _mitt).default();
@@ -87,7 +86,7 @@ test!(
8786

8887
test!(
8988
Default::default(),
90-
|_| visit_mut_pass(get_test_code_visitor()),
89+
|_| fold_pass(get_test_code_visitor()),
9190
top_level_directives_are_preserved,
9291
r#""use client";
9392
//@ts-ignore

0 commit comments

Comments
 (0)