@soda-gql/swc-transformer 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,285 @@
1
+ //! Import management module.
2
+ //!
3
+ //! This module handles:
4
+ //! - Adding the `@soda-gql/runtime` import/require
5
+ //! - Removing the `graphql-system` imports
6
+
7
+ use swc_core::common::{SyntaxContext, DUMMY_SP};
8
+ use swc_core::ecma::ast::*;
9
+ use swc_core::ecma::visit::{VisitMut, VisitMutWith};
10
+
11
+ const RUNTIME_MODULE: &str = "@soda-gql/runtime";
12
+ const RUNTIME_IMPORT_NAME: &str = "gqlRuntime";
13
+ const CJS_RUNTIME_NAME: &str = "__soda_gql_runtime";
14
+
15
+ /// Manages imports for the transformation.
16
+ pub struct ImportManager {
17
+ needs_runtime_import: bool,
18
+ is_cjs: bool,
19
+ graphql_system_aliases: Vec<String>,
20
+ has_added_import: bool,
21
+ }
22
+
23
+ impl ImportManager {
24
+ pub fn new(needs_runtime_import: bool, is_cjs: bool, graphql_system_aliases: &[String]) -> Self {
25
+ Self {
26
+ needs_runtime_import,
27
+ is_cjs,
28
+ graphql_system_aliases: graphql_system_aliases.to_vec(),
29
+ has_added_import: false,
30
+ }
31
+ }
32
+
33
+ /// Check if a specifier is a graphql-system import.
34
+ fn is_graphql_system_import(&self, specifier: &str) -> bool {
35
+ self.graphql_system_aliases.iter().any(|alias| {
36
+ specifier == alias || specifier.starts_with(&format!("{}/", alias))
37
+ })
38
+ }
39
+
40
+ /// Create the ESM runtime import.
41
+ fn create_esm_import(&self) -> ModuleItem {
42
+ // import { gqlRuntime } from "@soda-gql/runtime";
43
+ ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
44
+ span: DUMMY_SP,
45
+ specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
46
+ span: DUMMY_SP,
47
+ local: Ident::new(RUNTIME_IMPORT_NAME.into(), DUMMY_SP, Default::default()),
48
+ imported: None,
49
+ is_type_only: false,
50
+ })],
51
+ src: Box::new(Str {
52
+ span: DUMMY_SP,
53
+ value: RUNTIME_MODULE.into(),
54
+ raw: None,
55
+ }),
56
+ type_only: false,
57
+ with: None,
58
+ phase: ImportPhase::Evaluation,
59
+ }))
60
+ }
61
+
62
+ /// Create the CJS runtime require.
63
+ fn create_cjs_require(&self) -> ModuleItem {
64
+ // const __soda_gql_runtime = require("@soda-gql/runtime");
65
+ ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
66
+ span: DUMMY_SP,
67
+ ctxt: SyntaxContext::empty(),
68
+ kind: VarDeclKind::Const,
69
+ declare: false,
70
+ decls: vec![VarDeclarator {
71
+ span: DUMMY_SP,
72
+ name: Pat::Ident(BindingIdent {
73
+ id: Ident::new(CJS_RUNTIME_NAME.into(), DUMMY_SP, Default::default()),
74
+ type_ann: None,
75
+ }),
76
+ init: Some(Box::new(Expr::Call(CallExpr {
77
+ span: DUMMY_SP,
78
+ ctxt: SyntaxContext::empty(),
79
+ callee: Callee::Expr(Box::new(Expr::Ident(Ident::new(
80
+ "require".into(),
81
+ DUMMY_SP,
82
+ Default::default(),
83
+ )))),
84
+ args: vec![ExprOrSpread {
85
+ spread: None,
86
+ expr: Box::new(Expr::Lit(Lit::Str(Str {
87
+ span: DUMMY_SP,
88
+ value: RUNTIME_MODULE.into(),
89
+ raw: None,
90
+ }))),
91
+ }],
92
+ type_args: None,
93
+ }))),
94
+ definite: false,
95
+ }],
96
+ }))))
97
+ }
98
+
99
+ /// Check if a variable declaration is a require for graphql-system.
100
+ fn is_graphql_system_require(&self, decl: &VarDeclarator) -> bool {
101
+ if let Some(init) = &decl.init {
102
+ if let Some(specifier) = extract_require_specifier(init) {
103
+ return self.is_graphql_system_import(&specifier);
104
+ }
105
+ }
106
+ false
107
+ }
108
+
109
+ /// Check if an import already has the runtime import.
110
+ fn has_runtime_import(&self, import: &ImportDecl) -> bool {
111
+ if !wtf8_eq(&import.src.value, RUNTIME_MODULE) {
112
+ return false;
113
+ }
114
+
115
+ import.specifiers.iter().any(|spec| {
116
+ if let ImportSpecifier::Named(named) = spec {
117
+ atom_eq(&named.local.sym, RUNTIME_IMPORT_NAME)
118
+ } else {
119
+ false
120
+ }
121
+ })
122
+ }
123
+ }
124
+
125
+ impl VisitMut for ImportManager {
126
+ fn visit_mut_module(&mut self, module: &mut Module) {
127
+ // First, visit children to handle nested transformations
128
+ module.visit_mut_children_with(self);
129
+
130
+ // Collect new body items
131
+ let mut new_body: Vec<ModuleItem> = Vec::new();
132
+ let mut import_insert_pos = 0;
133
+ let mut found_non_import = false;
134
+ let mut existing_runtime_import_idx: Option<usize> = None;
135
+
136
+ for (_idx, item) in module.body.iter().enumerate() {
137
+ match item {
138
+ // Handle ESM imports
139
+ ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
140
+ let specifier = wtf8_to_string(&import.src.value);
141
+
142
+ // Skip graphql-system imports
143
+ if self.is_graphql_system_import(&specifier) {
144
+ continue;
145
+ }
146
+
147
+ // Check if this is already the runtime import
148
+ if specifier == RUNTIME_MODULE {
149
+ existing_runtime_import_idx = Some(new_body.len());
150
+ }
151
+
152
+ import_insert_pos = new_body.len() + 1;
153
+ new_body.push(item.clone());
154
+ }
155
+
156
+ // Handle CJS require statements
157
+ ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => {
158
+ // Filter out graphql-system requires
159
+ let filtered_decls: Vec<VarDeclarator> = var_decl
160
+ .decls
161
+ .iter()
162
+ .filter(|decl| !self.is_graphql_system_require(decl))
163
+ .cloned()
164
+ .collect();
165
+
166
+ if filtered_decls.is_empty() {
167
+ // All declarations were graphql-system requires, skip
168
+ continue;
169
+ }
170
+
171
+ if filtered_decls.len() < var_decl.decls.len() {
172
+ // Some declarations were filtered
173
+ new_body.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(
174
+ VarDecl {
175
+ span: var_decl.span,
176
+ ctxt: var_decl.ctxt,
177
+ kind: var_decl.kind,
178
+ declare: var_decl.declare,
179
+ decls: filtered_decls,
180
+ },
181
+ )))));
182
+ } else {
183
+ new_body.push(item.clone());
184
+ }
185
+
186
+ if !found_non_import {
187
+ import_insert_pos = new_body.len();
188
+ }
189
+ found_non_import = true;
190
+ }
191
+
192
+ _ => {
193
+ if !found_non_import {
194
+ import_insert_pos = new_body.len();
195
+ }
196
+ found_non_import = true;
197
+ new_body.push(item.clone());
198
+ }
199
+ }
200
+ }
201
+
202
+ // Add runtime import if needed
203
+ if self.needs_runtime_import && !self.has_added_import {
204
+ // Check if we already have the runtime import
205
+ let already_has_import = existing_runtime_import_idx.map_or(false, |idx| {
206
+ if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = &new_body[idx] {
207
+ self.has_runtime_import(import)
208
+ } else {
209
+ false
210
+ }
211
+ });
212
+
213
+ if !already_has_import {
214
+ if let Some(idx) = existing_runtime_import_idx {
215
+ // Merge with existing import
216
+ if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = &mut new_body[idx] {
217
+ let mut specifiers = import.specifiers.clone();
218
+ specifiers.push(ImportSpecifier::Named(ImportNamedSpecifier {
219
+ span: DUMMY_SP,
220
+ local: Ident::new(RUNTIME_IMPORT_NAME.into(), DUMMY_SP, Default::default()),
221
+ imported: None,
222
+ is_type_only: false,
223
+ }));
224
+ import.specifiers = specifiers;
225
+ }
226
+ } else {
227
+ // Add new import
228
+ let runtime_import = if self.is_cjs {
229
+ self.create_cjs_require()
230
+ } else {
231
+ self.create_esm_import()
232
+ };
233
+ new_body.insert(import_insert_pos, runtime_import);
234
+ }
235
+ self.has_added_import = true;
236
+ }
237
+ }
238
+
239
+ module.body = new_body;
240
+ }
241
+ }
242
+
243
+ /// Extract the module specifier from a require() call.
244
+ fn extract_require_specifier(expr: &Expr) -> Option<String> {
245
+ match expr {
246
+ Expr::Call(call) => {
247
+ // Direct require("...")
248
+ if let Callee::Expr(callee) = &call.callee {
249
+ if let Expr::Ident(ident) = &**callee {
250
+ if atom_eq(&ident.sym, "require") {
251
+ if let Some(arg) = call.args.first() {
252
+ if let Expr::Lit(Lit::Str(s)) = &*arg.expr {
253
+ return Some(wtf8_to_string(&s.value));
254
+ }
255
+ }
256
+ }
257
+
258
+ // __importDefault(require("...")) or __importStar(require("..."))
259
+ if atom_eq(&ident.sym, "__importDefault") || atom_eq(&ident.sym, "__importStar") {
260
+ if let Some(arg) = call.args.first() {
261
+ return extract_require_specifier(&arg.expr);
262
+ }
263
+ }
264
+ }
265
+ }
266
+ None
267
+ }
268
+ _ => None,
269
+ }
270
+ }
271
+
272
+ /// Helper to compare an Atom with a string.
273
+ fn atom_eq<T: AsRef<str>>(atom: &T, s: &str) -> bool {
274
+ atom.as_ref() == s
275
+ }
276
+
277
+ /// Helper to compare a Wtf8Atom (string literal value) with a string.
278
+ fn wtf8_eq(atom: &swc_core::atoms::Wtf8Atom, s: &str) -> bool {
279
+ atom.to_string_lossy() == s
280
+ }
281
+
282
+ /// Helper to convert a Wtf8Atom to String.
283
+ fn wtf8_to_string(atom: &swc_core::atoms::Wtf8Atom) -> String {
284
+ atom.to_string_lossy().into_owned()
285
+ }
@@ -0,0 +1,371 @@
1
+ //! Metadata collection module.
2
+ //!
3
+ //! This module collects metadata about GQL definitions in the source code:
4
+ //! - AST path (canonical path)
5
+ //! - Export bindings
6
+ //! - Scope tracking
7
+
8
+ use std::collections::HashMap;
9
+ use swc_core::common::Span;
10
+ use swc_core::ecma::ast::*;
11
+ use swc_core::ecma::visit::{Visit, VisitWith};
12
+
13
+ /// Metadata about a GQL definition.
14
+ #[derive(Debug, Clone)]
15
+ pub struct GqlDefinitionMetadata {
16
+ /// The AST path for canonical ID resolution.
17
+ pub ast_path: String,
18
+ /// Whether this is a top-level definition.
19
+ #[allow(dead_code)]
20
+ pub is_top_level: bool,
21
+ /// Whether this definition is exported.
22
+ #[allow(dead_code)]
23
+ pub is_exported: bool,
24
+ /// The export binding name, if exported.
25
+ #[allow(dead_code)]
26
+ pub export_binding: Option<String>,
27
+ }
28
+
29
+ /// Map from call expression span to metadata.
30
+ pub type MetadataMap = HashMap<Span, GqlDefinitionMetadata>;
31
+
32
+ /// Map from local name to export name.
33
+ type ExportBindingMap = HashMap<String, String>;
34
+
35
+ /// Collects metadata about GQL definitions in a module.
36
+ pub struct MetadataCollector {
37
+ #[allow(dead_code)]
38
+ source_path: String,
39
+ export_bindings: ExportBindingMap,
40
+ scope_stack: Vec<ScopeFrame>,
41
+ metadata: MetadataMap,
42
+ anonymous_counters: HashMap<String, usize>,
43
+ #[allow(dead_code)]
44
+ definition_counter: usize,
45
+ }
46
+
47
+ struct ScopeFrame {
48
+ segment: String,
49
+ #[allow(dead_code)]
50
+ kind: String,
51
+ }
52
+
53
+ impl MetadataCollector {
54
+ /// Collect metadata from a module.
55
+ pub fn collect(module: &Module, source_path: &str) -> MetadataMap {
56
+ let export_bindings = Self::collect_export_bindings(module);
57
+
58
+ let mut collector = Self {
59
+ source_path: source_path.to_string(),
60
+ export_bindings,
61
+ scope_stack: Vec::new(),
62
+ metadata: HashMap::new(),
63
+ anonymous_counters: HashMap::new(),
64
+ definition_counter: 0,
65
+ };
66
+
67
+ module.visit_with(&mut collector);
68
+ collector.metadata
69
+ }
70
+
71
+ /// Collect export bindings from the module.
72
+ fn collect_export_bindings(module: &Module) -> ExportBindingMap {
73
+ let mut bindings = HashMap::new();
74
+
75
+ for item in &module.body {
76
+ match item {
77
+ // ESM: export { foo }
78
+ ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
79
+ if export.src.is_none() {
80
+ for spec in &export.specifiers {
81
+ if let ExportSpecifier::Named(named) = spec {
82
+ let local = match &named.orig {
83
+ ModuleExportName::Ident(id) => atom_to_string(&id.sym),
84
+ ModuleExportName::Str(s) => wtf8_to_string(&s.value),
85
+ };
86
+ let exported = match &named.exported {
87
+ Some(ModuleExportName::Ident(id)) => atom_to_string(&id.sym),
88
+ Some(ModuleExportName::Str(s)) => wtf8_to_string(&s.value),
89
+ None => local.clone(),
90
+ };
91
+ bindings.insert(local, exported);
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ // ESM: export const foo = ...
98
+ ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => {
99
+ if let Decl::Var(var_decl) = &export.decl {
100
+ for decl in &var_decl.decls {
101
+ if let Pat::Ident(ident) = &decl.name {
102
+ let name = atom_to_string(&ident.id.sym);
103
+ bindings.insert(name.clone(), name);
104
+ }
105
+ }
106
+ } else if let Decl::Fn(fn_decl) = &export.decl {
107
+ let name = atom_to_string(&fn_decl.ident.sym);
108
+ bindings.insert(name.clone(), name);
109
+ } else if let Decl::Class(class_decl) = &export.decl {
110
+ let name = atom_to_string(&class_decl.ident.sym);
111
+ bindings.insert(name.clone(), name);
112
+ }
113
+ }
114
+
115
+ // CommonJS: exports.foo = ... or module.exports.foo = ...
116
+ ModuleItem::Stmt(Stmt::Expr(expr_stmt)) => {
117
+ if let Expr::Assign(assign) = &*expr_stmt.expr {
118
+ if let Some(name) = get_commonjs_export_name(&assign.left) {
119
+ bindings.insert(name.clone(), name);
120
+ }
121
+ }
122
+ }
123
+
124
+ _ => {}
125
+ }
126
+ }
127
+
128
+ bindings
129
+ }
130
+
131
+ /// Get the current AST path.
132
+ fn get_ast_path(&self) -> String {
133
+ self.scope_stack
134
+ .iter()
135
+ .map(|f| f.segment.clone())
136
+ .collect::<Vec<_>>()
137
+ .join(".")
138
+ }
139
+
140
+ /// Get an anonymous name for a scope kind (not currently used but kept for future).
141
+ #[allow(dead_code)]
142
+ fn get_anonymous_name(&mut self, kind: &str) -> String {
143
+ let count = self.anonymous_counters.entry(kind.to_string()).or_insert(0);
144
+ let name = format!("{}#{}", kind, count);
145
+ *count += 1;
146
+ name
147
+ }
148
+
149
+ /// Register a definition and get its AST path.
150
+ /// The AST path is the scope segments joined by `.`, with `$N` suffix for duplicates.
151
+ fn register_definition(&mut self) -> String {
152
+ let base_path = self.get_ast_path();
153
+
154
+ // Track occurrences for uniqueness
155
+ let count = self.anonymous_counters.entry(base_path.clone()).or_insert(0);
156
+ let path = if *count == 0 {
157
+ base_path.clone()
158
+ } else {
159
+ format!("{}${}", base_path, count)
160
+ };
161
+ *count += 1;
162
+ path
163
+ }
164
+
165
+ /// Enter a scope.
166
+ fn enter_scope(&mut self, segment: String, kind: &str) {
167
+ self.scope_stack.push(ScopeFrame {
168
+ segment,
169
+ kind: kind.to_string(),
170
+ });
171
+ }
172
+
173
+ /// Exit a scope.
174
+ fn exit_scope(&mut self) {
175
+ self.scope_stack.pop();
176
+ }
177
+
178
+ /// Check if a call expression is a GQL definition call.
179
+ fn is_gql_definition_call(&self, call: &CallExpr) -> bool {
180
+ // Check if callee is gql.* pattern
181
+ if let Callee::Expr(expr) = &call.callee {
182
+ if let Expr::Member(member) = &**expr {
183
+ if is_gql_reference(&member.obj) {
184
+ // Check if first argument is an arrow function or function expression
185
+ if let Some(first_arg) = call.args.first() {
186
+ return matches!(&*first_arg.expr, Expr::Arrow(_) | Expr::Fn(_));
187
+ }
188
+ }
189
+ }
190
+ }
191
+ false
192
+ }
193
+
194
+ /// Resolve top-level export info for a call.
195
+ fn resolve_export_info(&self, _call: &CallExpr) -> Option<String> {
196
+ // This is a simplified version - in practice, you'd need to track
197
+ // parent nodes to find the variable declaration or assignment
198
+ // For now, we'll look at the scope stack
199
+ if self.scope_stack.len() == 1 {
200
+ let binding_name = &self.scope_stack[0].segment;
201
+ self.export_bindings.get(binding_name).cloned()
202
+ } else {
203
+ None
204
+ }
205
+ }
206
+ }
207
+
208
+ impl Visit for MetadataCollector {
209
+ fn visit_var_declarator(&mut self, decl: &VarDeclarator) {
210
+ if let Pat::Ident(ident) = &decl.name {
211
+ let name = atom_to_string(&ident.id.sym);
212
+ self.enter_scope(name, "variable");
213
+ decl.visit_children_with(self);
214
+ self.exit_scope();
215
+ } else {
216
+ decl.visit_children_with(self);
217
+ }
218
+ }
219
+
220
+ fn visit_fn_decl(&mut self, decl: &FnDecl) {
221
+ let name = atom_to_string(&decl.ident.sym);
222
+ self.enter_scope(name, "function");
223
+ decl.visit_children_with(self);
224
+ self.exit_scope();
225
+ }
226
+
227
+ fn visit_fn_expr(&mut self, expr: &FnExpr) {
228
+ let name = expr
229
+ .ident
230
+ .as_ref()
231
+ .map(|i| atom_to_string(&i.sym))
232
+ .unwrap_or_else(|| self.get_anonymous_name("function"));
233
+ self.enter_scope(name, "function");
234
+ expr.visit_children_with(self);
235
+ self.exit_scope();
236
+ }
237
+
238
+ fn visit_arrow_expr(&mut self, expr: &ArrowExpr) {
239
+ let name = self.get_anonymous_name("arrow");
240
+ self.enter_scope(name, "function");
241
+ expr.visit_children_with(self);
242
+ self.exit_scope();
243
+ }
244
+
245
+ fn visit_class_decl(&mut self, decl: &ClassDecl) {
246
+ let name = atom_to_string(&decl.ident.sym);
247
+ self.enter_scope(name, "class");
248
+ decl.visit_children_with(self);
249
+ self.exit_scope();
250
+ }
251
+
252
+ fn visit_class_method(&mut self, method: &ClassMethod) {
253
+ if let PropName::Ident(ident) = &method.key {
254
+ let name = atom_to_string(&ident.sym);
255
+ self.enter_scope(name, "method");
256
+ method.visit_children_with(self);
257
+ self.exit_scope();
258
+ } else {
259
+ method.visit_children_with(self);
260
+ }
261
+ }
262
+
263
+ fn visit_key_value_prop(&mut self, prop: &KeyValueProp) {
264
+ let name = match &prop.key {
265
+ PropName::Ident(ident) => Some(atom_to_string(&ident.sym)),
266
+ PropName::Str(s) => Some(wtf8_to_string(&s.value)),
267
+ _ => None,
268
+ };
269
+
270
+ if let Some(name) = name {
271
+ self.enter_scope(name, "property");
272
+ prop.visit_children_with(self);
273
+ self.exit_scope();
274
+ } else {
275
+ prop.visit_children_with(self);
276
+ }
277
+ }
278
+
279
+ fn visit_assign_expr(&mut self, expr: &AssignExpr) {
280
+ // Handle CommonJS exports: exports.foo = ...
281
+ if let Some(name) = get_commonjs_export_name(&expr.left) {
282
+ self.enter_scope(name, "variable");
283
+ expr.visit_children_with(self);
284
+ self.exit_scope();
285
+ } else {
286
+ expr.visit_children_with(self);
287
+ }
288
+ }
289
+
290
+ fn visit_call_expr(&mut self, call: &CallExpr) {
291
+ if self.is_gql_definition_call(call) {
292
+ let ast_path = self.register_definition();
293
+ let is_top_level = self.scope_stack.len() <= 1;
294
+ let export_binding = self.resolve_export_info(call);
295
+
296
+ self.metadata.insert(
297
+ call.span,
298
+ GqlDefinitionMetadata {
299
+ ast_path,
300
+ is_top_level,
301
+ is_exported: export_binding.is_some(),
302
+ export_binding,
303
+ },
304
+ );
305
+
306
+ // Don't visit children of GQL calls
307
+ return;
308
+ }
309
+
310
+ call.visit_children_with(self);
311
+ }
312
+ }
313
+
314
+ /// Check if an expression is a reference to `gql`.
315
+ fn is_gql_reference(expr: &Expr) -> bool {
316
+ match expr {
317
+ Expr::Ident(ident) => atom_eq(&ident.sym, "gql"),
318
+ Expr::Member(member) => {
319
+ if let MemberProp::Ident(ident) = &member.prop {
320
+ if atom_eq(&ident.sym, "gql") {
321
+ return true;
322
+ }
323
+ }
324
+ is_gql_reference(&member.obj)
325
+ }
326
+ _ => false,
327
+ }
328
+ }
329
+
330
+ /// Get the export name from a CommonJS export pattern.
331
+ fn get_commonjs_export_name(target: &AssignTarget) -> Option<String> {
332
+ match target {
333
+ AssignTarget::Simple(SimpleAssignTarget::Member(member)) => {
334
+ // Check for exports.foo or module.exports.foo
335
+ let is_exports = matches!(&*member.obj, Expr::Ident(ident) if atom_eq(&ident.sym, "exports"));
336
+ let is_module_exports = if let Expr::Member(inner) = &*member.obj {
337
+ matches!(&*inner.obj, Expr::Ident(ident) if atom_eq(&ident.sym, "module"))
338
+ && matches!(&inner.prop, MemberProp::Ident(ident) if atom_eq(&ident.sym, "exports"))
339
+ } else {
340
+ false
341
+ };
342
+
343
+ if !is_exports && !is_module_exports {
344
+ return None;
345
+ }
346
+
347
+ // Extract property name
348
+ if let MemberProp::Ident(ident) = &member.prop {
349
+ Some(atom_to_string(&ident.sym))
350
+ } else {
351
+ None
352
+ }
353
+ }
354
+ _ => None,
355
+ }
356
+ }
357
+
358
+ /// Helper to compare an Atom with a string.
359
+ fn atom_eq<T: AsRef<str>>(atom: &T, s: &str) -> bool {
360
+ atom.as_ref() == s
361
+ }
362
+
363
+ /// Helper to convert an Atom to String.
364
+ fn atom_to_string<T: AsRef<str>>(atom: &T) -> String {
365
+ atom.as_ref().to_string()
366
+ }
367
+
368
+ /// Helper to convert a Wtf8Atom (string literal value) to String.
369
+ fn wtf8_to_string(atom: &swc_core::atoms::Wtf8Atom) -> String {
370
+ atom.to_string_lossy().into_owned()
371
+ }
@@ -0,0 +1,7 @@
1
+ //! Transformation modules for the SWC transformer.
2
+
3
+ pub mod analysis;
4
+ pub mod imports;
5
+ pub mod metadata;
6
+ pub mod runtime;
7
+ pub mod transformer;