Skip to content

Commit 802f4dc

Browse files
hanneslundHannes Lund
andauthored
next-swc: styled-jsx error checking and reporting updated (invalid-styled-jsx-children.md) (vercel#31940)
Co-authored-by: Hannes Lund <[email protected]>
1 parent b3b92b6 commit 802f4dc

File tree

16 files changed

+213
-27
lines changed

16 files changed

+213
-27
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Invalid `styled-jsx` children
2+
3+
#### Why This Error Occurred
4+
5+
You have passed invalid children to a `<style jsx>` tag.
6+
7+
#### Possible Ways to Fix It
8+
9+
Make sure to only pass one child to the `<style jsx>` tag, typically a template literal.
10+
11+
```jsx
12+
const Component = () => (
13+
<div>
14+
<p>Red paragraph</p>
15+
<style jsx>{`
16+
p {
17+
color: red;
18+
}
19+
`}</style>
20+
</div>
21+
)
22+
```
23+
24+
Please see the links for more examples.
25+
26+
### Useful Links
27+
28+
- [Built-In CSS-in-JS](https://nextjs.org/docs/basic-features/built-in-css-support#css-in-js)
29+
- [styled-jsx documentation](https://github.com/vercel/styled-jsx)

errors/manifest.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,10 @@
522522
{
523523
"title": "invalid-dynamic-options-type",
524524
"path": "/errors/invalid-dynamic-options-type.md"
525+
},
526+
{
527+
"title": "invalid-styled-jsx-children",
528+
"path": "/errors/invalid-styled-jsx-children.md"
525529
}
526530
]
527531
}

packages/next-swc/crates/core/src/styled_jsx/mod.rs

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,20 @@ pub enum JSXStyle {
8686
External(ExternalStyle),
8787
}
8888

89+
enum StyleExpr<'a> {
90+
Str(&'a Str),
91+
Tpl(&'a Tpl, &'a Expr),
92+
Ident(&'a Ident),
93+
}
94+
8995
impl Fold for StyledJSXTransformer {
9096
fn fold_jsx_element(&mut self, el: JSXElement) -> JSXElement {
9197
if is_styled_jsx(&el) {
9298
let parent_has_styled_jsx = self.has_styled_jsx;
9399
if !parent_has_styled_jsx {
94-
self.check_for_jsx_styles(Some(&el), &el.children);
100+
if self.check_for_jsx_styles(Some(&el), &el.children).is_err() {
101+
return el;
102+
}
95103
}
96104
let el = match self.replace_jsx_style(&el) {
97105
Ok(el) => el,
@@ -107,7 +115,9 @@ impl Fold for StyledJSXTransformer {
107115
return el.fold_children_with(self);
108116
}
109117

110-
self.check_for_jsx_styles(None, &el.children);
118+
if self.check_for_jsx_styles(None, &el.children).is_err() {
119+
return el;
120+
}
111121
let el = el.fold_children_with(self);
112122
self.reset_styles_state();
113123

@@ -119,7 +129,9 @@ impl Fold for StyledJSXTransformer {
119129
return fragment.fold_children_with(self);
120130
}
121131

122-
self.check_for_jsx_styles(None, &fragment.children);
132+
if self.check_for_jsx_styles(None, &fragment.children).is_err() {
133+
return fragment;
134+
};
123135
let fragment = fragment.fold_children_with(self);
124136
self.reset_styles_state();
125137

@@ -383,23 +395,29 @@ impl Fold for StyledJSXTransformer {
383395
}
384396

385397
impl StyledJSXTransformer {
386-
fn check_for_jsx_styles(&mut self, el: Option<&JSXElement>, children: &Vec<JSXElementChild>) {
398+
fn check_for_jsx_styles(
399+
&mut self,
400+
el: Option<&JSXElement>,
401+
children: &[JSXElementChild],
402+
) -> Result<(), Error> {
387403
let mut styles = vec![];
388404
let mut process_style = |el: &JSXElement| {
389405
self.file_has_styled_jsx = true;
390406
self.has_styled_jsx = true;
391-
let expr = get_style_expr(el);
407+
let expr = get_style_expr(el)?;
392408
let style_info = self.get_jsx_style(expr, is_global(el));
393409
styles.insert(0, style_info);
410+
411+
Ok(())
394412
};
395413

396414
if el.is_some() && is_styled_jsx(el.unwrap()) {
397-
process_style(el.unwrap());
415+
process_style(el.unwrap())?;
398416
} else {
399417
for i in 0..children.len() {
400418
if let JSXElementChild::JSXElement(child_el) = &children[i] {
401419
if is_styled_jsx(&child_el) {
402-
process_style(&child_el);
420+
process_style(&child_el)?;
403421
}
404422
}
405423
}
@@ -412,26 +430,31 @@ impl StyledJSXTransformer {
412430
self.static_class_name = static_class_name;
413431
self.class_name = class_name;
414432
}
433+
434+
Ok(())
415435
}
416436

417-
fn get_jsx_style(&mut self, expr: &Expr, is_global_jsx_element: bool) -> JSXStyle {
437+
fn get_jsx_style(&mut self, style_expr: StyleExpr, is_global_jsx_element: bool) -> JSXStyle {
418438
let mut hasher = DefaultHasher::new();
419439
let css: String;
420440
let css_span: Span;
421441
let is_dynamic;
422442
let mut expressions = vec![];
423-
match expr {
424-
Expr::Lit(Lit::Str(Str { value, span, .. })) => {
443+
match style_expr {
444+
StyleExpr::Str(Str { value, span, .. }) => {
425445
hasher.write(value.as_ref().as_bytes());
426446
css = value.to_string().clone();
427447
css_span = span.clone();
428448
is_dynamic = false;
429449
}
430-
Expr::Tpl(Tpl {
431-
exprs,
432-
quasis,
433-
span,
434-
}) => {
450+
StyleExpr::Tpl(
451+
Tpl {
452+
exprs,
453+
quasis,
454+
span,
455+
},
456+
expr,
457+
) => {
435458
if exprs.is_empty() {
436459
hasher.write(quasis[0].raw.value.as_bytes());
437460
css = quasis[0].raw.value.to_string();
@@ -463,7 +486,7 @@ impl StyledJSXTransformer {
463486
expressions = exprs.clone();
464487
}
465488
}
466-
Expr::Ident(ident) => {
489+
StyleExpr::Ident(ident) => {
467490
return JSXStyle::External(ExternalStyle {
468491
expr: Expr::Member(MemberExpr {
469492
obj: ExprOrSuper::Expr(Box::new(Expr::Ident(ident.clone()))),
@@ -479,7 +502,6 @@ impl StyledJSXTransformer {
479502
is_global: is_global_jsx_element,
480503
});
481504
}
482-
_ => panic!("Not implemented"), // TODO: handle bad style input
483505
}
484506

485507
return JSXStyle::Local(LocalStyle {
@@ -539,7 +561,11 @@ impl StyledJSXTransformer {
539561
// references to this.something (e.g. props or state).
540562
// We allow dynamic styles only when resolving styles.
541563
}
542-
let style = self.get_jsx_style(&Expr::Tpl(tagged_tpl.tpl.clone()), false);
564+
565+
let style = self.get_jsx_style(
566+
StyleExpr::Tpl(&tagged_tpl.tpl, &Expr::Tpl(tagged_tpl.tpl.clone())),
567+
false,
568+
);
543569
let styles = vec![style];
544570
let (static_class_name, class_name) =
545571
compute_class_names(&styles, &self.style_import_name.as_ref().unwrap());
@@ -657,7 +683,7 @@ fn is_global(el: &JSXElement) -> bool {
657683
})
658684
}
659685

660-
fn get_style_expr(el: &JSXElement) -> &Expr {
686+
fn get_style_expr(el: &JSXElement) -> Result<StyleExpr, Error> {
661687
let non_whitespace_children: &Vec<&JSXElementChild> = &el
662688
.children
663689
.iter()
@@ -677,34 +703,47 @@ fn get_style_expr(el: &JSXElement) -> &Expr {
677703
.struct_span_err(
678704
el.span,
679705
&format!(
680-
"Expected one child under JSX style tag, but got {} (eg: <style \
681-
jsx>{{`hi`}}</style>)",
706+
"Expected one child under JSX style tag, but got {}.\nRead more: https://nextjs.org/docs/messages/invalid-styled-jsx-children",
682707
non_whitespace_children.len()
683708
),
684709
)
685710
.emit()
686711
});
687-
panic!("styled-jsx style error");
712+
bail!("styled-jsx style error");
688713
}
689714

690715
if let JSXElementChild::JSXExprContainer(JSXExprContainer {
691716
expr: JSXExpr::Expr(expr),
692717
..
693718
}) = non_whitespace_children[0]
694719
{
695-
return &**expr;
720+
return Ok(match &**expr {
721+
Expr::Lit(Lit::Str(str)) => StyleExpr::Str(str),
722+
Expr::Tpl(tpl) => StyleExpr::Tpl(tpl, &**expr),
723+
Expr::Ident(ident) => StyleExpr::Ident(ident),
724+
_ => {
725+
HANDLER.with(|handler| {
726+
handler
727+
.struct_span_err(
728+
el.span,
729+
"Expected a template literal, string or identifier inside the JSXExpressionContainer.\nRead more: https://nextjs.org/docs/messages/invalid-styled-jsx-children",
730+
)
731+
.emit()
732+
});
733+
bail!("wrong jsx expression container type");
734+
}
735+
});
696736
}
697737

698738
HANDLER.with(|handler| {
699739
handler
700740
.struct_span_err(
701741
el.span,
702-
"Expected a single child of type JSXExpressionContainer under JSX Style tag (eg: \
703-
<style jsx>{{`hi`}}</style>)",
742+
"Expected a single child of type JSXExpressionContainer under JSX Style tag.\nRead more: https://nextjs.org/docs/messages/invalid-styled-jsx-children",
704743
)
705744
.emit()
706745
});
707-
panic!("next-swc compilation error");
746+
bail!("next-swc compilation error");
708747
}
709748

710749
fn get_existing_class_name(el: &JSXOpeningElement) -> (Option<Expr>, Option<usize>, Option<usize>) {

packages/next-swc/crates/core/tests/errors.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use next_swc::{
22
disallow_re_export_all_in_page::disallow_re_export_all_in_page, next_dynamic::next_dynamic,
3-
next_ssg::next_ssg,
3+
next_ssg::next_ssg, styled_jsx::styled_jsx,
44
};
55
use std::path::PathBuf;
66
use swc_common::FileName;
@@ -44,6 +44,12 @@ fn next_dynamic_errors(input: PathBuf) {
4444
);
4545
}
4646

47+
#[fixture("tests/errors/styled-jsx/**/input.js")]
48+
fn styled_jsx_errors(input: PathBuf) {
49+
let output = input.parent().unwrap().join("output.js");
50+
test_fixture_allowing_error(syntax(), &|t| styled_jsx(t.cm.clone()), &input, &output);
51+
}
52+
4753
#[fixture("tests/errors/next-ssg/**/input.js")]
4854
fn next_ssg_errors(input: PathBuf) {
4955
let output = input.parent().unwrap().join("output.js");
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default () => (
2+
<div>
3+
<style jsx>
4+
</style>
5+
</div>
6+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import _JSXStyle from "styled-jsx/style";
2+
export default (()=><div >
3+
4+
<style jsx>
5+
6+
</style>
7+
8+
</div>
9+
);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: Expected one child under JSX style tag, but got 0.
2+
Read more: https://nextjs.org/docs/messages/invalid-styled-jsx-children
3+
--> input.js:3:5
4+
|
5+
3 | / <style jsx>
6+
4 | | </style>
7+
| |____________^
8+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default () => (
2+
<div>
3+
<style jsx>
4+
{`.p {}`}
5+
{`.p {}`}
6+
</style>
7+
</div>
8+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import _JSXStyle from "styled-jsx/style";
2+
export default (()=><div >
3+
4+
<style jsx>
5+
6+
{`.p {}`}
7+
8+
{`.p {}`}
9+
10+
</style>
11+
12+
</div>
13+
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: Expected one child under JSX style tag, but got 2.
2+
Read more: https://nextjs.org/docs/messages/invalid-styled-jsx-children
3+
--> input.js:3:5
4+
|
5+
3 | / <style jsx>
6+
4 | | {`.p {}`}
7+
5 | | {`.p {}`}
8+
6 | | </style>
9+
| |____________^
10+

0 commit comments

Comments
 (0)