@swc/plugin-styled-jsx 1.3.0 → 1.5.2

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.
Files changed (139) hide show
  1. package/Cargo.toml +23 -8
  2. package/package.json +1 -1
  3. package/src/lib.rs +9 -1
  4. package/src/style.rs +21 -0
  5. package/src/transform_css.rs +530 -0
  6. package/src/utils.rs +370 -0
  7. package/src/visitor.rs +921 -0
  8. package/swc_plugin_styled_jsx.wasm +0 -0
  9. package/tests/errors/nested-style-tags/input.js +31 -0
  10. package/tests/errors/nested-style-tags/output.js +31 -0
  11. package/tests/errors/nested-style-tags/output.stderr +21 -0
  12. package/tests/errors/no-child/input.js +6 -0
  13. package/tests/errors/no-child/output.js +8 -0
  14. package/tests/errors/no-child/output.stderr +7 -0
  15. package/tests/errors/ts-with-css-resolve/input.js +10 -0
  16. package/tests/errors/ts-with-css-resolve/output.js +5 -0
  17. package/tests/errors/ts-with-css-resolve/output.stderr +2 -0
  18. package/tests/errors/two-children/input.js +8 -0
  19. package/tests/errors/two-children/output.js +12 -0
  20. package/tests/errors/two-children/output.stderr +9 -0
  21. package/tests/errors/wrong-child-type/input.js +7 -0
  22. package/tests/errors/wrong-child-type/output.js +10 -0
  23. package/tests/errors/wrong-child-type/output.stderr +8 -0
  24. package/tests/errors/wrong-jsx-expression-type/input.js +7 -0
  25. package/tests/errors/wrong-jsx-expression-type/output.js +10 -0
  26. package/tests/errors/wrong-jsx-expression-type/output.stderr +8 -0
  27. package/tests/fixture/absent/input.js +6 -0
  28. package/tests/fixture/absent/output.js +7 -0
  29. package/tests/fixture/attribute-generation-classname-rewriting/input.js +55 -0
  30. package/tests/fixture/attribute-generation-classname-rewriting/output.js +112 -0
  31. package/tests/fixture/attribute-generation-modes/input.js +166 -0
  32. package/tests/fixture/attribute-generation-modes/output.js +228 -0
  33. package/tests/fixture/class/input.js +14 -0
  34. package/tests/fixture/class/output.js +12 -0
  35. package/tests/fixture/comments/input.js +18 -0
  36. package/tests/fixture/comments/output.js +12 -0
  37. package/tests/fixture/component-attribute/input.js +11 -0
  38. package/tests/fixture/component-attribute/output.js +10 -0
  39. package/tests/fixture/conflicts/input.js +16 -0
  40. package/tests/fixture/conflicts/output.js +14 -0
  41. package/tests/fixture/css-selector-after-pseudo/input.js +18 -0
  42. package/tests/fixture/css-selector-after-pseudo/output.js +11 -0
  43. package/tests/fixture/css-tag-same-file/input.js +25 -0
  44. package/tests/fixture/css-tag-same-file/output.js +21 -0
  45. package/tests/fixture/different-jsx-ids/input.js +23 -0
  46. package/tests/fixture/different-jsx-ids/output.js +24 -0
  47. package/tests/fixture/dynamic-element/input.js +56 -0
  48. package/tests/fixture/dynamic-element/output.js +40 -0
  49. package/tests/fixture/dynamic-element-class/input.js +32 -0
  50. package/tests/fixture/dynamic-element-class/output.js +25 -0
  51. package/tests/fixture/dynamic-element-external/input.js +12 -0
  52. package/tests/fixture/dynamic-element-external/output.js +12 -0
  53. package/tests/fixture/dynamic-this-in-arrow/input.js +41 -0
  54. package/tests/fixture/dynamic-this-in-arrow/output.js +39 -0
  55. package/tests/fixture/exported-jsx-style/input.js +1 -0
  56. package/tests/fixture/exported-jsx-style/output.js +1 -0
  57. package/tests/fixture/exported-non-jsx-style/input.js +1 -0
  58. package/tests/fixture/exported-non-jsx-style/output.js +1 -0
  59. package/tests/fixture/expressions/input.js +90 -0
  60. package/tests/fixture/expressions/output.js +91 -0
  61. package/tests/fixture/external-nested-scope/input.js +5 -0
  62. package/tests/fixture/external-nested-scope/output.js +7 -0
  63. package/tests/fixture/external-stylesheet/input.js +41 -0
  64. package/tests/fixture/external-stylesheet/output.js +34 -0
  65. package/tests/fixture/external-stylesheet-global/input.js +19 -0
  66. package/tests/fixture/external-stylesheet-global/output.js +18 -0
  67. package/tests/fixture/external-stylesheet-multi-line/input.js +9 -0
  68. package/tests/fixture/external-stylesheet-multi-line/output.js +9 -0
  69. package/tests/fixture/fragment/input.js +51 -0
  70. package/tests/fixture/fragment/output.js +47 -0
  71. package/tests/fixture/global/input.js +22 -0
  72. package/tests/fixture/global/output.js +7 -0
  73. package/tests/fixture/global-child-selector/input.js +10 -0
  74. package/tests/fixture/global-child-selector/output.js +8 -0
  75. package/tests/fixture/global-redundant/input.js +12 -0
  76. package/tests/fixture/global-redundant/output.js +10 -0
  77. package/tests/fixture/issue-30480/input.js +7 -0
  78. package/tests/fixture/issue-30480/output.js +15 -0
  79. package/tests/fixture/issue-30570/input.js +15 -0
  80. package/tests/fixture/issue-30570/output.js +12 -0
  81. package/tests/fixture/issue-31562-interpolation-in-mdea/input.js +30 -0
  82. package/tests/fixture/issue-31562-interpolation-in-mdea/output.js +43 -0
  83. package/tests/fixture/mixed-global-scoped/input.js +9 -0
  84. package/tests/fixture/mixed-global-scoped/output.js +11 -0
  85. package/tests/fixture/multiple-jsx/input.js +43 -0
  86. package/tests/fixture/multiple-jsx/output.js +32 -0
  87. package/tests/fixture/nested-style-tags/index.js +0 -0
  88. package/tests/fixture/nested-style-tags/output.js +0 -0
  89. package/tests/fixture/non-styled-jsx-style/input.js +7 -0
  90. package/tests/fixture/non-styled-jsx-style/output.js +12 -0
  91. package/tests/fixture/not-styled-jsx-tagged-templates/input.js +30 -0
  92. package/tests/fixture/not-styled-jsx-tagged-templates/output.js +24 -0
  93. package/tests/fixture/number-after-placeholder/input.js +17 -0
  94. package/tests/fixture/number-after-placeholder/output.js +17 -0
  95. package/tests/fixture/one-off-global-selectors/input.js +10 -0
  96. package/tests/fixture/one-off-global-selectors/output.js +8 -0
  97. package/tests/fixture/source-maps/input.js +8 -0
  98. package/tests/fixture/source-maps/output.js +8 -0
  99. package/tests/fixture/stateless/input.js +8 -0
  100. package/tests/fixture/stateless/output.js +12 -0
  101. package/tests/fixture/styles/input.js +57 -0
  102. package/tests/fixture/styles/output.js +39 -0
  103. package/tests/fixture/styles-external-invalid/input.js +13 -0
  104. package/tests/fixture/styles-external-invalid/output.js +9 -0
  105. package/tests/fixture/styles-external-invalid2/input.js +11 -0
  106. package/tests/fixture/styles-external-invalid2/output.js +8 -0
  107. package/tests/fixture/styles2/input.js +6 -0
  108. package/tests/fixture/styles2/output.js +5 -0
  109. package/tests/fixture/too-many/input.js +35 -0
  110. package/tests/fixture/too-many/output.js +49 -0
  111. package/tests/fixture/tpl-escape-1/input.js +14 -0
  112. package/tests/fixture/tpl-escape-1/output.js +12 -0
  113. package/tests/fixture/tpl-escape-2/input.js +17 -0
  114. package/tests/fixture/tpl-escape-2/output.js +17 -0
  115. package/tests/fixture/tpl-placeholder-1-as-property/input.js +17 -0
  116. package/tests/fixture/tpl-placeholder-1-as-property/output.js +29 -0
  117. package/tests/fixture/tpl-placeholder-2-as-part-of-value/input.js +21 -0
  118. package/tests/fixture/tpl-placeholder-2-as-part-of-value/output.js +34 -0
  119. package/tests/fixture/tpl-placeholder-3-as-value/input.js +17 -0
  120. package/tests/fixture/tpl-placeholder-3-as-value/output.js +28 -0
  121. package/tests/fixture/tpl-placeholder-4-as-part-of-value-in-multiple/input.js +17 -0
  122. package/tests/fixture/tpl-placeholder-4-as-part-of-value-in-multiple/output.js +31 -0
  123. package/tests/fixture/tpl-placeholder-5-values-of-multiple-properties/input.js +16 -0
  124. package/tests/fixture/tpl-placeholder-5-values-of-multiple-properties/output.js +31 -0
  125. package/tests/fixture/transform-css/input.js +176 -0
  126. package/tests/fixture/transform-css/output.js +8 -0
  127. package/tests/fixture/transform-css-complex-selector/input.js +116 -0
  128. package/tests/fixture/transform-css-complex-selector/output.js +8 -0
  129. package/tests/fixture/transform-css-global/input.js +32 -0
  130. package/tests/fixture/transform-css-global/output.js +8 -0
  131. package/tests/fixture/transform-css-media-query/input.js +24 -0
  132. package/tests/fixture/transform-css-media-query/output.js +8 -0
  133. package/tests/fixture/transform-css-normal/input.js +22 -0
  134. package/tests/fixture/transform-css-normal/output.js +8 -0
  135. package/tests/fixture/transform-css-nth-1/input.js +10 -0
  136. package/tests/fixture/transform-css-nth-1/output.js +8 -0
  137. package/tests/fixture/whitespace/input.js +12 -0
  138. package/tests/fixture/whitespace/output.js +12 -0
  139. package/tests/fixture.rs +87 -0
package/src/visitor.rs ADDED
@@ -0,0 +1,921 @@
1
+ use std::{
2
+ collections::hash_map::DefaultHasher,
3
+ hash::{Hash, Hasher},
4
+ mem::take,
5
+ sync::Arc,
6
+ };
7
+
8
+ use easy_error::{bail, Error};
9
+ use swc_core::{
10
+ common::{collections::AHashSet, errors::HANDLER, FileName, SourceMap, Span, DUMMY_SP},
11
+ ecma::{
12
+ ast::*,
13
+ minifier::{
14
+ eval::{EvalResult, Evaluator},
15
+ marks::Marks,
16
+ },
17
+ utils::{collect_decls, drop_span, prepend_stmt, private_ident},
18
+ visit::{Fold, FoldWith},
19
+ },
20
+ };
21
+
22
+ use crate::{
23
+ style::{ExternalStyle, JSXStyle, LocalStyle},
24
+ transform_css::transform_css,
25
+ utils::{
26
+ add, and, compute_class_names, get_usable_import_specifier, hash_string, ident,
27
+ is_capitalized, make_external_styled_jsx_el, make_local_styled_jsx_el, not_eq, or,
28
+ string_literal_expr, styled_jsx_import_decl,
29
+ },
30
+ };
31
+ //use external::external_styles;
32
+
33
+ pub fn styled_jsx(cm: Arc<SourceMap>, file_name: FileName) -> impl Fold {
34
+ let file_name = match file_name {
35
+ FileName::Real(real_file_name) => real_file_name
36
+ .to_str()
37
+ .map(|real_file_name| real_file_name.to_string()),
38
+ _ => None,
39
+ };
40
+
41
+ StyledJSXTransformer {
42
+ cm,
43
+ file_name,
44
+ styles: Default::default(),
45
+ static_class_name: Default::default(),
46
+ class_name: Default::default(),
47
+ file_has_styled_jsx: Default::default(),
48
+ has_styled_jsx: Default::default(),
49
+ bindings: Default::default(),
50
+ nearest_scope_bindings: Default::default(),
51
+ func_scope_level: Default::default(),
52
+ style_import_name: Default::default(),
53
+ external_bindings: Default::default(),
54
+ file_has_css_resolve: Default::default(),
55
+ external_hash: Default::default(),
56
+ add_hash: Default::default(),
57
+ add_default_decl: Default::default(),
58
+ in_function_params: Default::default(),
59
+ evaluator: Default::default(),
60
+ visiting_styled_jsx_descendants: Default::default(),
61
+ }
62
+ }
63
+
64
+ struct StyledJSXTransformer {
65
+ cm: Arc<SourceMap>,
66
+ file_name: Option<String>,
67
+ styles: Vec<JSXStyle>,
68
+ static_class_name: Option<String>,
69
+ class_name: Option<Expr>,
70
+ file_has_styled_jsx: bool,
71
+ has_styled_jsx: bool,
72
+ bindings: AHashSet<Id>,
73
+ nearest_scope_bindings: AHashSet<Id>,
74
+ func_scope_level: u8,
75
+ style_import_name: Option<String>,
76
+ external_bindings: Vec<Id>,
77
+ file_has_css_resolve: bool,
78
+ external_hash: Option<String>,
79
+ add_hash: Option<(Id, String)>,
80
+ add_default_decl: Option<(Id, Expr)>,
81
+ in_function_params: bool,
82
+ evaluator: Option<Evaluator>,
83
+ visiting_styled_jsx_descendants: bool,
84
+ }
85
+
86
+ enum StyleExpr<'a> {
87
+ Str(&'a Str),
88
+ Tpl(&'a Tpl, &'a Expr),
89
+ Ident(&'a Ident),
90
+ }
91
+
92
+ impl Fold for StyledJSXTransformer {
93
+ fn fold_jsx_element(&mut self, el: JSXElement) -> JSXElement {
94
+ if is_styled_jsx(&el) {
95
+ if self.visiting_styled_jsx_descendants {
96
+ HANDLER.with(|handler| {
97
+ handler
98
+ .struct_span_err(
99
+ el.span,
100
+ "Detected nested styled-jsx tag.\nRead more: https://nextjs.org/docs/messages/nested-styled-jsx-tags",
101
+ )
102
+ .emit()
103
+ });
104
+ return el;
105
+ }
106
+
107
+ let parent_has_styled_jsx = self.has_styled_jsx;
108
+ if !parent_has_styled_jsx && self.check_for_jsx_styles(Some(&el), &el.children).is_err()
109
+ {
110
+ return el;
111
+ }
112
+ let el = match self.replace_jsx_style(&el) {
113
+ Ok(el) => el,
114
+ Err(_) => el,
115
+ };
116
+ if !parent_has_styled_jsx {
117
+ self.reset_styles_state();
118
+ }
119
+ return el;
120
+ }
121
+
122
+ if self.has_styled_jsx {
123
+ self.visiting_styled_jsx_descendants = true;
124
+ let el = el.fold_children_with(self);
125
+ self.visiting_styled_jsx_descendants = false;
126
+ return el;
127
+ }
128
+
129
+ if self.check_for_jsx_styles(None, &el.children).is_err() {
130
+ return el;
131
+ }
132
+ let el = el.fold_children_with(self);
133
+ self.reset_styles_state();
134
+
135
+ el
136
+ }
137
+
138
+ fn fold_jsx_fragment(&mut self, fragment: JSXFragment) -> JSXFragment {
139
+ if self.has_styled_jsx {
140
+ self.visiting_styled_jsx_descendants = true;
141
+ let fragment = fragment.fold_children_with(self);
142
+ self.visiting_styled_jsx_descendants = false;
143
+ return fragment;
144
+ }
145
+
146
+ if self.check_for_jsx_styles(None, &fragment.children).is_err() {
147
+ return fragment;
148
+ };
149
+ let fragment = fragment.fold_children_with(self);
150
+ self.reset_styles_state();
151
+
152
+ fragment
153
+ }
154
+
155
+ fn fold_jsx_opening_element(&mut self, mut el: JSXOpeningElement) -> JSXOpeningElement {
156
+ if !self.has_styled_jsx {
157
+ return el;
158
+ }
159
+
160
+ el.attrs = el.attrs.fold_with(self);
161
+
162
+ if let JSXElementName::Ident(Ident { sym, span, .. }) = &el.name {
163
+ if sym != "style"
164
+ && sym != self.style_import_name.as_ref().unwrap()
165
+ && (!is_capitalized(sym)
166
+ || self
167
+ .nearest_scope_bindings
168
+ .contains(&(sym.clone(), span.ctxt)))
169
+ {
170
+ let (existing_class_name, existing_index, existing_spread_index) =
171
+ get_existing_class_name(&el);
172
+
173
+ let new_class_name = match (existing_class_name, &self.class_name) {
174
+ (Some(existing_class_name), Some(class_name)) => Some(add(
175
+ add(class_name.clone(), string_literal_expr(" ")),
176
+ existing_class_name,
177
+ )),
178
+ (Some(existing_class_name), None) => Some(existing_class_name),
179
+ (None, Some(class_name)) => Some(class_name.clone()),
180
+ _ => None,
181
+ };
182
+
183
+ if let Some(new_class_name) = new_class_name {
184
+ let class_name_attr = JSXAttrOrSpread::JSXAttr(JSXAttr {
185
+ span: DUMMY_SP,
186
+ name: JSXAttrName::Ident(ident("className")),
187
+ value: Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
188
+ expr: JSXExpr::Expr(Box::new(new_class_name)),
189
+ span: DUMMY_SP,
190
+ })),
191
+ });
192
+ el.attrs.push(class_name_attr);
193
+ }
194
+ if let Some(existing_spread_index) = existing_spread_index {
195
+ el.attrs.remove(existing_spread_index);
196
+ }
197
+ if let Some(existing_index) = existing_index {
198
+ el.attrs.remove(existing_index);
199
+ }
200
+ }
201
+ }
202
+
203
+ el
204
+ }
205
+
206
+ fn fold_import_decl(&mut self, decl: ImportDecl) -> ImportDecl {
207
+ let ImportDecl {
208
+ ref src,
209
+ ref specifiers,
210
+ ..
211
+ } = decl;
212
+ if &src.value == "styled-jsx/css" {
213
+ for specifier in specifiers {
214
+ match specifier {
215
+ ImportSpecifier::Default(default_specifier) => {
216
+ self.external_bindings.push(default_specifier.local.to_id())
217
+ }
218
+ ImportSpecifier::Named(named_specifier) => {
219
+ self.external_bindings.push(named_specifier.local.to_id())
220
+ }
221
+ _ => {}
222
+ }
223
+ }
224
+ }
225
+
226
+ decl
227
+ }
228
+
229
+ fn fold_expr(&mut self, expr: Expr) -> Expr {
230
+ let expr = expr.fold_children_with(self);
231
+ match expr {
232
+ Expr::TaggedTpl(tagged_tpl) => match &*tagged_tpl.tag {
233
+ Expr::Ident(identifier) => {
234
+ if self.external_bindings.contains(&identifier.to_id()) {
235
+ match self.process_tagged_template_expr(&tagged_tpl, &identifier.sym) {
236
+ Ok(expr) => expr,
237
+ Err(_) => Expr::TaggedTpl(tagged_tpl),
238
+ }
239
+ } else {
240
+ Expr::TaggedTpl(tagged_tpl)
241
+ }
242
+ }
243
+ Expr::Member(MemberExpr {
244
+ obj: boxed_ident, ..
245
+ }) => {
246
+ if let Expr::Ident(identifier) = &**boxed_ident {
247
+ if self.external_bindings.contains(&identifier.to_id()) {
248
+ match self.process_tagged_template_expr(&tagged_tpl, &identifier.sym) {
249
+ Ok(expr) => expr,
250
+ Err(_) => Expr::TaggedTpl(tagged_tpl),
251
+ }
252
+ } else {
253
+ Expr::TaggedTpl(tagged_tpl)
254
+ }
255
+ } else {
256
+ Expr::TaggedTpl(tagged_tpl)
257
+ }
258
+ }
259
+ _ => Expr::TaggedTpl(tagged_tpl),
260
+ },
261
+ expr => expr,
262
+ }
263
+ }
264
+
265
+ fn fold_var_declarator(&mut self, declarator: VarDeclarator) -> VarDeclarator {
266
+ let declarator = declarator.fold_children_with(self);
267
+ if let Some(external_hash) = &self.external_hash.take() {
268
+ if let Pat::Ident(BindingIdent {
269
+ id: Ident { span, sym, .. },
270
+ ..
271
+ }) = &declarator.name
272
+ {
273
+ self.add_hash = Some(((sym.clone(), span.ctxt), external_hash.clone()));
274
+ }
275
+ }
276
+ declarator
277
+ }
278
+
279
+ fn fold_export_default_expr(&mut self, default_expr: ExportDefaultExpr) -> ExportDefaultExpr {
280
+ let default_expr = default_expr.fold_children_with(self);
281
+ if let Some(external_hash) = &self.external_hash.take() {
282
+ let default_ident = private_ident!("_defaultExport");
283
+ self.add_hash = Some((default_ident.to_id(), external_hash.clone()));
284
+ self.add_default_decl = Some((default_ident.to_id(), *default_expr.expr));
285
+ return ExportDefaultExpr {
286
+ expr: Box::new(Expr::Ident(default_ident)),
287
+ span: DUMMY_SP,
288
+ };
289
+ }
290
+ default_expr
291
+ }
292
+
293
+ fn fold_block_stmt(&mut self, mut block: BlockStmt) -> BlockStmt {
294
+ let mut new_stmts = vec![];
295
+ for stmt in block.stmts {
296
+ new_stmts.push(stmt.fold_children_with(self));
297
+ if let Some(add_hash) = self.add_hash.take() {
298
+ new_stmts.push(add_hash_statement(add_hash));
299
+ }
300
+ }
301
+
302
+ block.stmts = new_stmts;
303
+ block
304
+ }
305
+
306
+ fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
307
+ let mut new_items = vec![];
308
+ for item in items {
309
+ let new_item = item.fold_children_with(self);
310
+ if let Some((default_ident, default_expr)) = &self.add_default_decl {
311
+ new_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
312
+ kind: VarDeclKind::Const,
313
+ declare: false,
314
+ decls: vec![VarDeclarator {
315
+ name: Pat::Ident(BindingIdent {
316
+ id: Ident {
317
+ sym: default_ident.0.clone(),
318
+ span: DUMMY_SP.with_ctxt(default_ident.1),
319
+ optional: false,
320
+ },
321
+ type_ann: None,
322
+ }),
323
+ init: Some(Box::new(default_expr.clone())),
324
+ definite: false,
325
+ span: DUMMY_SP,
326
+ }],
327
+ span: DUMMY_SP,
328
+ })))));
329
+ self.add_default_decl = None;
330
+ if let Some(add_hash) = self.add_hash.take() {
331
+ new_items.push(ModuleItem::Stmt(add_hash_statement(add_hash)));
332
+ }
333
+ }
334
+ if !is_styled_css_import(&new_item) {
335
+ new_items.push(new_item);
336
+ }
337
+ if let Some(add_hash) = self.add_hash.take() {
338
+ new_items.push(ModuleItem::Stmt(add_hash_statement(add_hash)));
339
+ }
340
+ }
341
+
342
+ if self.file_has_styled_jsx || self.file_has_css_resolve {
343
+ prepend_stmt(
344
+ &mut new_items,
345
+ styled_jsx_import_decl(self.style_import_name.as_ref().unwrap()),
346
+ );
347
+ }
348
+
349
+ new_items
350
+ }
351
+
352
+ fn fold_binding_ident(&mut self, node: BindingIdent) -> BindingIdent {
353
+ if self.in_function_params {
354
+ self.nearest_scope_bindings.insert(node.id.to_id());
355
+ }
356
+ node
357
+ }
358
+
359
+ fn fold_assign_pat_prop(&mut self, node: AssignPatProp) -> AssignPatProp {
360
+ if self.in_function_params {
361
+ self.nearest_scope_bindings.insert(node.key.to_id());
362
+ }
363
+ node
364
+ }
365
+
366
+ fn fold_function(&mut self, mut func: Function) -> Function {
367
+ self.func_scope_level += 1;
368
+ let surrounding_scope_bindings = take(&mut self.nearest_scope_bindings);
369
+ self.in_function_params = true;
370
+ let mut new_params = vec![];
371
+ for param in func.params {
372
+ new_params.push(param.fold_with(self));
373
+ }
374
+ func.params = new_params;
375
+ self.in_function_params = false;
376
+ self.nearest_scope_bindings.extend(collect_decls(&func));
377
+ func.body = func.body.fold_with(self);
378
+ self.nearest_scope_bindings = surrounding_scope_bindings;
379
+ self.func_scope_level -= 1;
380
+ func
381
+ }
382
+
383
+ fn fold_arrow_expr(&mut self, mut func: ArrowExpr) -> ArrowExpr {
384
+ self.func_scope_level += 1;
385
+ let surrounding_scope_bindings = take(&mut self.nearest_scope_bindings);
386
+ self.in_function_params = true;
387
+ let mut new_params = vec![];
388
+ for param in func.params {
389
+ new_params.push(param.fold_with(self));
390
+ }
391
+ func.params = new_params;
392
+ self.in_function_params = false;
393
+ self.nearest_scope_bindings.extend(collect_decls(&func));
394
+ func.body = func.body.fold_with(self);
395
+ self.nearest_scope_bindings = surrounding_scope_bindings;
396
+ self.func_scope_level -= 1;
397
+ func
398
+ }
399
+
400
+ fn fold_module(&mut self, module: Module) -> Module {
401
+ self.bindings = collect_decls(&module);
402
+ self.evaluator = Some(Evaluator::new(module.clone(), Marks::new()));
403
+ self.style_import_name = Some(get_usable_import_specifier(&module.body));
404
+ let module = module.fold_children_with(self);
405
+ if self.file_has_css_resolve
406
+ && self.file_name.is_some()
407
+ && self.file_name.as_ref().unwrap().ends_with(".ts")
408
+ {
409
+ let file_name: &str = self.file_name.as_ref().unwrap();
410
+ HANDLER.with(|handler| {
411
+ handler.err(&format!(
412
+ "{} uses `css.resolve`, but ends with `.ts`. The file extension needs to be \
413
+ `.tsx` so that the jsx injected by `css.resolve` will be transformed.",
414
+ file_name
415
+ ));
416
+ });
417
+ }
418
+ module
419
+ }
420
+ }
421
+
422
+ impl StyledJSXTransformer {
423
+ fn check_for_jsx_styles(
424
+ &mut self,
425
+ el: Option<&JSXElement>,
426
+ children: &[JSXElementChild],
427
+ ) -> Result<(), Error> {
428
+ let mut styles = vec![];
429
+ let mut process_style = |el: &JSXElement| {
430
+ self.file_has_styled_jsx = true;
431
+ self.has_styled_jsx = true;
432
+ let expr = get_style_expr(el)?;
433
+ let style_info = self.get_jsx_style(expr, is_global(el));
434
+ styles.insert(0, style_info);
435
+
436
+ Ok(())
437
+ };
438
+
439
+ if el.is_some() && is_styled_jsx(el.unwrap()) {
440
+ process_style(el.unwrap())?;
441
+ } else {
442
+ for i in children {
443
+ if let JSXElementChild::JSXElement(child_el) = &i {
444
+ if is_styled_jsx(child_el) {
445
+ process_style(child_el)?;
446
+ }
447
+ }
448
+ }
449
+ };
450
+
451
+ if self.has_styled_jsx {
452
+ let (static_class_name, class_name) =
453
+ compute_class_names(&styles, self.style_import_name.as_ref().unwrap());
454
+ self.styles = styles;
455
+ self.static_class_name = static_class_name;
456
+ self.class_name = class_name;
457
+ }
458
+
459
+ Ok(())
460
+ }
461
+
462
+ fn get_jsx_style(&mut self, style_expr: StyleExpr, is_global_jsx_element: bool) -> JSXStyle {
463
+ let mut hasher = DefaultHasher::new();
464
+ let css: String;
465
+ let css_span: Span;
466
+ let is_dynamic;
467
+ let mut expressions = vec![];
468
+ match style_expr {
469
+ StyleExpr::Str(Str { value, span, .. }) => {
470
+ hasher.write(value.as_ref().as_bytes());
471
+ css = value.to_string();
472
+ css_span = *span;
473
+ is_dynamic = false;
474
+ }
475
+ StyleExpr::Tpl(
476
+ Tpl {
477
+ exprs,
478
+ quasis,
479
+ span,
480
+ },
481
+ expr,
482
+ ) => {
483
+ if exprs.is_empty() {
484
+ hasher.write(quasis[0].raw.as_bytes());
485
+ css = quasis[0].raw.to_string();
486
+ css_span = *span;
487
+ is_dynamic = false;
488
+ } else {
489
+ drop_span(expr.clone()).hash(&mut hasher);
490
+ let mut s = String::new();
491
+ for i in 0..quasis.len() {
492
+ let placeholder = if i == quasis.len() - 1 {
493
+ String::new()
494
+ } else {
495
+ format!("__styled-jsx-placeholder-{}__", i)
496
+ };
497
+ s = format!("{}{}{}", s, quasis[i].raw, placeholder)
498
+ }
499
+ css = s;
500
+ css_span = *span;
501
+ is_dynamic = if self.func_scope_level > 0 {
502
+ let res = self.evaluator.as_mut().unwrap().eval(expr);
503
+ !matches!(res, Some(EvalResult::Lit(_)))
504
+ } else {
505
+ false
506
+ };
507
+ expressions = exprs.clone();
508
+ }
509
+ }
510
+ StyleExpr::Ident(ident) => {
511
+ return JSXStyle::External(ExternalStyle {
512
+ expr: Expr::Member(MemberExpr {
513
+ obj: Box::new(Expr::Ident(ident.clone())),
514
+ prop: MemberProp::Ident(Ident {
515
+ sym: "__hash".into(),
516
+ span: DUMMY_SP,
517
+ optional: false,
518
+ }),
519
+ span: DUMMY_SP,
520
+ }),
521
+ identifier: ident.clone(),
522
+ is_global: is_global_jsx_element,
523
+ });
524
+ }
525
+ }
526
+
527
+ JSXStyle::Local(LocalStyle {
528
+ hash: format!("{:x}", hasher.finish()),
529
+ css,
530
+ css_span,
531
+ is_dynamic,
532
+ expressions,
533
+ })
534
+ }
535
+
536
+ fn replace_jsx_style(&mut self, el: &JSXElement) -> Result<JSXElement, Error> {
537
+ let style_info = self.styles.pop().unwrap();
538
+
539
+ let is_global = el.opening.attrs.iter().any(|attr| {
540
+ if let JSXAttrOrSpread::JSXAttr(JSXAttr {
541
+ name: JSXAttrName::Ident(Ident { sym, .. }),
542
+ ..
543
+ }) = &attr
544
+ {
545
+ if sym == "global" {
546
+ return true;
547
+ }
548
+ }
549
+ false
550
+ });
551
+
552
+ match &style_info {
553
+ JSXStyle::Local(style_info) => {
554
+ let css = transform_css(
555
+ self.cm.clone(),
556
+ style_info,
557
+ is_global,
558
+ &self.static_class_name,
559
+ )?;
560
+ Ok(make_local_styled_jsx_el(
561
+ style_info,
562
+ css,
563
+ self.style_import_name.as_ref().unwrap(),
564
+ self.static_class_name.as_ref(),
565
+ ))
566
+ }
567
+ JSXStyle::External(style) => Ok(make_external_styled_jsx_el(
568
+ style,
569
+ self.style_import_name.as_ref().unwrap(),
570
+ )),
571
+ }
572
+ }
573
+
574
+ fn process_tagged_template_expr(
575
+ &mut self,
576
+ tagged_tpl: &TaggedTpl,
577
+ tag: &str,
578
+ ) -> Result<Expr, Error> {
579
+ if tag != "resolve" {
580
+ // Check whether there are undefined references or
581
+ // references to this.something (e.g. props or state).
582
+ // We allow dynamic styles only when resolving styles.
583
+ }
584
+
585
+ let style = self.get_jsx_style(
586
+ StyleExpr::Tpl(&tagged_tpl.tpl, &Expr::Tpl(tagged_tpl.tpl.clone())),
587
+ false,
588
+ );
589
+ let styles = vec![style];
590
+ let (static_class_name, class_name) =
591
+ compute_class_names(&styles, self.style_import_name.as_ref().unwrap());
592
+ let tag = match &*tagged_tpl.tag {
593
+ Expr::Ident(Ident { sym, .. }) => sym.to_string(),
594
+ Expr::Member(MemberExpr {
595
+ prop: MemberProp::Ident(Ident { sym, .. }),
596
+ ..
597
+ }) => sym.to_string(),
598
+ _ => String::from("not_styled_jsx_tag"),
599
+ };
600
+ let style = if let JSXStyle::Local(style) = &styles[0] {
601
+ if tag != "resolve" {
602
+ self.external_hash = Some(hash_string(&style.hash.clone()));
603
+ }
604
+ style
605
+ } else {
606
+ bail!("This shouldn't happen, we already know that this is a template literal");
607
+ };
608
+ let css = transform_css(self.cm.clone(), style, tag == "global", &static_class_name)?;
609
+ if tag == "resolve" {
610
+ self.file_has_css_resolve = true;
611
+ return Ok(Expr::Object(ObjectLit {
612
+ props: vec![
613
+ PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
614
+ key: PropName::Ident(Ident {
615
+ sym: "styles".into(),
616
+ span: DUMMY_SP,
617
+ optional: false,
618
+ }),
619
+ value: Box::new(Expr::JSXElement(Box::new(make_local_styled_jsx_el(
620
+ style,
621
+ css,
622
+ self.style_import_name.as_ref().unwrap(),
623
+ self.static_class_name.as_ref(),
624
+ )))),
625
+ }))),
626
+ PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
627
+ key: PropName::Ident(Ident {
628
+ sym: "className".into(),
629
+ span: DUMMY_SP,
630
+ optional: false,
631
+ }),
632
+ value: Box::new(class_name.unwrap()),
633
+ }))),
634
+ ],
635
+ span: DUMMY_SP,
636
+ }));
637
+ }
638
+ Ok(Expr::New(NewExpr {
639
+ callee: Box::new(Expr::Ident(Ident {
640
+ sym: "String".into(),
641
+ span: DUMMY_SP,
642
+ optional: false,
643
+ })),
644
+ args: Some(vec![ExprOrSpread {
645
+ expr: Box::new(css),
646
+ spread: None,
647
+ }]),
648
+ span: DUMMY_SP,
649
+ type_args: None,
650
+ }))
651
+ }
652
+
653
+ fn reset_styles_state(&mut self) {
654
+ self.has_styled_jsx = false;
655
+ self.static_class_name = None;
656
+ self.class_name = None;
657
+ self.styles = vec![];
658
+ }
659
+ }
660
+
661
+ fn is_styled_jsx(el: &JSXElement) -> bool {
662
+ if let JSXElementName::Ident(Ident { sym, .. }) = &el.opening.name {
663
+ if sym != "style" {
664
+ return false;
665
+ }
666
+ }
667
+
668
+ el.opening.attrs.iter().any(|attr| {
669
+ if let JSXAttrOrSpread::JSXAttr(JSXAttr {
670
+ name: JSXAttrName::Ident(Ident { sym, .. }),
671
+ ..
672
+ }) = &attr
673
+ {
674
+ if sym == "jsx" {
675
+ return true;
676
+ }
677
+ }
678
+ false
679
+ })
680
+ }
681
+
682
+ fn is_global(el: &JSXElement) -> bool {
683
+ if let JSXElementName::Ident(Ident { sym, .. }) = &el.opening.name {
684
+ if sym != "style" {
685
+ return false;
686
+ }
687
+ }
688
+
689
+ el.opening.attrs.iter().any(|attr| {
690
+ if let JSXAttrOrSpread::JSXAttr(JSXAttr {
691
+ name: JSXAttrName::Ident(Ident { sym, .. }),
692
+ ..
693
+ }) = &attr
694
+ {
695
+ if sym == "global" {
696
+ return true;
697
+ }
698
+ }
699
+ false
700
+ })
701
+ }
702
+
703
+ fn get_style_expr(el: &JSXElement) -> Result<StyleExpr, Error> {
704
+ let non_whitespace_children: &Vec<&JSXElementChild> = &el
705
+ .children
706
+ .iter()
707
+ .filter(|child| {
708
+ if let JSXElementChild::JSXText(txt) = child {
709
+ if txt.value.chars().all(char::is_whitespace) {
710
+ return false;
711
+ }
712
+ }
713
+ true
714
+ })
715
+ .collect();
716
+
717
+ if non_whitespace_children.len() != 1 {
718
+ HANDLER.with(|handler| {
719
+ handler
720
+ .struct_span_err(
721
+ el.span,
722
+ &format!(
723
+ "Expected one child under JSX style tag, but got {}.\nRead more: https://nextjs.org/docs/messages/invalid-styled-jsx-children",
724
+ non_whitespace_children.len()
725
+ ),
726
+ )
727
+ .emit()
728
+ });
729
+ bail!("styled-jsx style error");
730
+ }
731
+
732
+ if let JSXElementChild::JSXExprContainer(JSXExprContainer {
733
+ expr: JSXExpr::Expr(expr),
734
+ ..
735
+ }) = non_whitespace_children[0]
736
+ {
737
+ return Ok(match &**expr {
738
+ Expr::Lit(Lit::Str(str)) => StyleExpr::Str(str),
739
+ Expr::Tpl(tpl) => StyleExpr::Tpl(tpl, expr),
740
+ Expr::Ident(ident) => StyleExpr::Ident(ident),
741
+ _ => {
742
+ HANDLER.with(|handler| {
743
+ handler
744
+ .struct_span_err(
745
+ el.span,
746
+ "Expected a template literal, string or identifier inside the JSXExpressionContainer.\nRead more: https://nextjs.org/docs/messages/invalid-styled-jsx-children",
747
+ )
748
+ .emit()
749
+ });
750
+ bail!("wrong jsx expression container type");
751
+ }
752
+ });
753
+ }
754
+
755
+ HANDLER.with(|handler| {
756
+ handler
757
+ .struct_span_err(
758
+ el.span,
759
+ "Expected a single child of type JSXExpressionContainer under JSX Style tag.\nRead more: https://nextjs.org/docs/messages/invalid-styled-jsx-children",
760
+ )
761
+ .emit()
762
+ });
763
+ bail!("next-swc compilation error");
764
+ }
765
+
766
+ fn get_existing_class_name(el: &JSXOpeningElement) -> (Option<Expr>, Option<usize>, Option<usize>) {
767
+ let mut spreads = vec![];
768
+ let mut class_name_expr = None;
769
+ let mut existing_index = None;
770
+ let mut existing_spread_index = None;
771
+ for i in (0..el.attrs.len()).rev() {
772
+ match &el.attrs[i] {
773
+ JSXAttrOrSpread::JSXAttr(JSXAttr {
774
+ name: JSXAttrName::Ident(Ident { sym, .. }),
775
+ value,
776
+ ..
777
+ }) => {
778
+ if sym == "className" {
779
+ existing_index = Some(i);
780
+ class_name_expr = match value {
781
+ Some(JSXAttrValue::Lit(str_lit)) => Some(Expr::Lit(str_lit.clone())),
782
+ Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
783
+ expr: JSXExpr::Expr(expr),
784
+ ..
785
+ })) => Some(*expr.clone()),
786
+ None => None,
787
+ _ => None,
788
+ };
789
+ break;
790
+ }
791
+ }
792
+ JSXAttrOrSpread::SpreadElement(SpreadElement { expr, .. }) => {
793
+ if let Expr::Object(ObjectLit { props, .. }) = &**expr {
794
+ let mut has_spread = false;
795
+ let mut has_class_name = false;
796
+ for j in 0..props.len() {
797
+ if let PropOrSpread::Prop(prop) = &props[j] {
798
+ if let Prop::KeyValue(KeyValueProp {
799
+ key: PropName::Ident(Ident { sym, .. }),
800
+ value,
801
+ }) = &**prop
802
+ {
803
+ if sym == "className" {
804
+ has_class_name = true;
805
+ class_name_expr = Some(*value.clone());
806
+ if props.len() == 1 {
807
+ existing_spread_index = Some(i);
808
+ }
809
+ }
810
+ }
811
+ } else {
812
+ has_spread = true;
813
+ }
814
+ }
815
+ if has_class_name {
816
+ break;
817
+ }
818
+ if !has_spread {
819
+ continue;
820
+ }
821
+ }
822
+
823
+ let valid_spread = matches!(&**expr, Expr::Member(_) | Expr::Ident(_));
824
+
825
+ if valid_spread {
826
+ let member_dot_name = Expr::Member(MemberExpr {
827
+ obj: Box::new(*expr.clone()),
828
+ prop: MemberProp::Ident(ident("className")),
829
+ span: DUMMY_SP,
830
+ });
831
+ // `${name} && ${name}.className != null && ${name}.className`
832
+ spreads.push(and(
833
+ and(
834
+ *expr.clone(),
835
+ not_eq(
836
+ member_dot_name.clone(),
837
+ Expr::Lit(Lit::Null(Null { span: DUMMY_SP })),
838
+ ),
839
+ ),
840
+ member_dot_name.clone(),
841
+ ));
842
+ }
843
+ }
844
+ _ => {}
845
+ };
846
+ }
847
+
848
+ let spread_expr = match spreads.len() {
849
+ 0 => None,
850
+ _ => Some(join_spreads(spreads)),
851
+ };
852
+
853
+ let class_name_expr = match class_name_expr {
854
+ Some(e @ Expr::Tpl(_) | e @ Expr::Lit(Lit::Str(_))) => Some(e),
855
+ None => None,
856
+ _ => Some(or(class_name_expr.unwrap(), string_literal_expr(""))),
857
+ };
858
+
859
+ let existing_class_name_expr = match (spread_expr, class_name_expr) {
860
+ (Some(spread_expr), Some(class_name_expr)) => Some(or(spread_expr, class_name_expr)),
861
+ (Some(spread_expr), None) => Some(or(spread_expr, string_literal_expr(""))),
862
+ (None, Some(class_name_expr)) => Some(class_name_expr),
863
+ _ => None,
864
+ };
865
+
866
+ (
867
+ existing_class_name_expr,
868
+ existing_index,
869
+ existing_spread_index,
870
+ )
871
+ }
872
+
873
+ fn join_spreads(spreads: Vec<Expr>) -> Expr {
874
+ let mut new_expr = spreads[0].clone();
875
+ for i in spreads.iter().skip(1) {
876
+ new_expr = Expr::Bin(BinExpr {
877
+ op: op!("||"),
878
+ left: Box::new(new_expr.clone()),
879
+ right: Box::new(i.clone()),
880
+ span: DUMMY_SP,
881
+ })
882
+ }
883
+ new_expr
884
+ }
885
+
886
+ fn add_hash_statement((id, hash): (Id, String)) -> Stmt {
887
+ Stmt::Expr(ExprStmt {
888
+ expr: Box::new(Expr::Assign(AssignExpr {
889
+ left: PatOrExpr::Expr(Box::new(Expr::Member(MemberExpr {
890
+ obj: Box::new(Expr::Ident(Ident {
891
+ sym: id.0,
892
+ span: DUMMY_SP.with_ctxt(id.1),
893
+ optional: false,
894
+ })),
895
+ prop: MemberProp::Ident(Ident {
896
+ sym: "__hash".into(),
897
+ span: DUMMY_SP,
898
+ optional: false,
899
+ }),
900
+ span: DUMMY_SP,
901
+ }))),
902
+ right: Box::new(string_literal_expr(&hash)),
903
+ op: op!("="),
904
+ span: DUMMY_SP,
905
+ })),
906
+ span: DUMMY_SP,
907
+ })
908
+ }
909
+
910
+ fn is_styled_css_import(item: &ModuleItem) -> bool {
911
+ if let ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
912
+ src: box Str { value, .. },
913
+ ..
914
+ })) = item
915
+ {
916
+ if value == "styled-jsx/css" {
917
+ return true;
918
+ }
919
+ }
920
+ false
921
+ }