Skip to content

Commit 3d49731

Browse files
authored
fix: refine css :global() selector checks in a compound selector (#11142)
1 parent e7301af commit 3d49731

File tree

8 files changed

+104
-28
lines changed

8 files changed

+104
-28
lines changed

.changeset/dry-fans-march.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: refine css `:global()` selector checks in a compound selector

packages/svelte/src/compiler/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ const css = {
108108
'invalid-css-global-selector': () => `:global(...) must contain exactly one selector`,
109109
'invalid-css-global-selector-list': () =>
110110
`:global(...) must not contain type or universal selectors when used in a compound selector`,
111+
'invalid-css-type-selector-placement': () =>
112+
`:global(...) must not be followed with a type selector`,
111113
'invalid-css-selector': () => `Invalid selector`,
112114
'invalid-css-identifier': () => 'Expected a valid CSS identifier',
113115
'invalid-nesting-selector': () => `Nesting selectors can only be used inside a rule`,

packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -99,41 +99,31 @@ const validation_visitors = {
9999
}
100100
}
101101

102-
// ensure `:global(...)`contains a single selector
103-
// (standalone :global() with multiple selectors is OK)
104-
if (node.children.length > 1 || node.children[0].selectors.length > 1) {
105-
for (const relative_selector of node.children) {
106-
for (const selector of relative_selector.selectors) {
107-
if (
108-
selector.type === 'PseudoClassSelector' &&
109-
selector.name === 'global' &&
110-
selector.args !== null &&
111-
selector.args.children.length > 1
112-
) {
113-
error(selector, 'invalid-css-global-selector');
114-
}
115-
}
116-
}
117-
}
118-
119-
// ensure `:global(...)` is not part of a larger compound selector
102+
// ensure `:global(...)` do not lead to invalid css after `:global()` is removed
120103
for (const relative_selector of node.children) {
121104
for (let i = 0; i < relative_selector.selectors.length; i++) {
122105
const selector = relative_selector.selectors[i];
123106

124107
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
125108
const child = selector.args?.children[0].children[0];
109+
// ensure `:global(element)` to be at the first position in a compound selector
110+
if (child?.selectors[0].type === 'TypeSelector' && i !== 0) {
111+
error(selector, 'invalid-css-global-selector-list');
112+
}
113+
114+
// ensure `:global(.class)` is not followed by a type selector, eg: `:global(.class)element`
115+
if (relative_selector.selectors[i + 1]?.type === 'TypeSelector') {
116+
error(relative_selector.selectors[i + 1], 'invalid-css-type-selector-placement');
117+
}
118+
119+
// ensure `:global(...)`contains a single selector
120+
// (standalone :global() with multiple selectors is OK)
126121
if (
127-
child?.selectors[0].type === 'TypeSelector' &&
128-
!/[.:#]/.test(child.selectors[0].name[0]) &&
129-
(i !== 0 ||
130-
relative_selector.selectors
131-
.slice(1)
132-
.some(
133-
(s) => s.type !== 'PseudoElementSelector' && s.type !== 'PseudoClassSelector'
134-
))
122+
selector.args !== null &&
123+
selector.args.children.length > 1 &&
124+
(node.children.length > 1 || relative_selector.selectors.length > 1)
135125
) {
136-
error(selector, 'invalid-css-global-selector-list');
126+
error(selector, 'invalid-css-global-selector');
137127
}
138128
}
139129
}

packages/svelte/src/compiler/phases/3-transform/css/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ const visitors = {
230230

231231
context.state.specificity.bumped = true;
232232

233-
// TODO err... can this happen?
233+
// for any :global() at the middle of compound selector
234234
for (const selector of relative_selector.selectors) {
235235
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
236236
remove_global_pseudo_class(selector);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "invalid-css-global-selector-list",
4+
"message": ":global(...) must not contain type or universal selectors when used in a compound selector",
5+
"start": {
6+
"line": 20,
7+
"column": 6
8+
},
9+
"end": {
10+
"line": 20,
11+
"column": 17
12+
}
13+
}
14+
]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<style>
2+
::foo:global([data-state='checked']) {
3+
color: red;
4+
}
5+
::foo:global(.foo) {
6+
color: red;
7+
}
8+
::foo:global(#foo) {
9+
color: red;
10+
}
11+
::foo:global(::foo) {
12+
color: red;
13+
}
14+
::foo:global(:foo) {
15+
color: red;
16+
}
17+
:global(h1) {
18+
color: red;
19+
}
20+
::foo:global(h1) {
21+
color: red;
22+
}
23+
</style>
24+
25+
<div>
26+
<h1>hello world</h1>
27+
</div>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"code": "invalid-css-type-selector-placement",
4+
"message": ":global(...) must not be followed with a type selector",
5+
"start": {
6+
"line": 17,
7+
"column": 14
8+
},
9+
"end": {
10+
"line": 17,
11+
"column": 16
12+
}
13+
}
14+
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<style>
2+
:global(.foo):foo {
3+
color: red;
4+
}
5+
:global(.foo)::foo {
6+
color: red;
7+
}
8+
:global(.foo).bar {
9+
color: red;
10+
}
11+
:global(.foo)#baz {
12+
color: red;
13+
}
14+
:global(.foo)[id] {
15+
color: red;
16+
}
17+
:global(.foo)h1 {
18+
color: red;
19+
}
20+
</style>
21+
22+
<div>
23+
<h1 class="bar" id="baz">hello world</h1>
24+
</div>

0 commit comments

Comments
 (0)