Skip to content

Commit e47ee22

Browse files
fix: wrap :id, :where:not and :has with :global during migration (#13850)
Closes #13765
1 parent 217ef20 commit e47ee22

File tree

4 files changed

+180
-1
lines changed

4 files changed

+180
-1
lines changed

.changeset/sour-feet-carry.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: wrap `:id`, `:where``:not` and `:has` with `:global` during migration

packages/svelte/src/compiler/migrate/index.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,61 @@ class MigrationError extends Error {
3434
}
3535
}
3636

37+
/**
38+
*
39+
* @param {State} state
40+
*/
41+
function migrate_css(state) {
42+
if (!state.analysis.css.ast?.start) return;
43+
let code = state.str
44+
.snip(state.analysis.css.ast.start, /** @type {number} */ (state.analysis.css.ast?.end))
45+
.toString();
46+
let starting = 0;
47+
48+
// since we already blank css we can't work directly on `state.str` so we will create a copy that we can update
49+
const str = new MagicString(code);
50+
while (code) {
51+
if (
52+
code.startsWith(':has') ||
53+
code.startsWith(':not') ||
54+
code.startsWith(':is') ||
55+
code.startsWith(':where')
56+
) {
57+
let start = code.indexOf('(') + 1;
58+
let is_global = false;
59+
const global_str = ':global';
60+
const next_global = code.indexOf(global_str);
61+
const str_between = code.substring(start, next_global);
62+
if (!str_between.trim()) {
63+
is_global = true;
64+
start += global_str.length;
65+
}
66+
let parenthesis = 1;
67+
let end = start;
68+
let char = code[end];
69+
// find the closing parenthesis
70+
while (parenthesis !== 0 && char) {
71+
if (char === '(') parenthesis++;
72+
if (char === ')') parenthesis--;
73+
end++;
74+
char = code[end];
75+
}
76+
if (start && end) {
77+
if (!is_global) {
78+
str.prependLeft(starting + start, ':global(');
79+
str.appendRight(starting + end - 1, ')');
80+
}
81+
starting += end - 1;
82+
code = code.substring(end - 1);
83+
continue;
84+
}
85+
}
86+
starting++;
87+
code = code.substring(1);
88+
}
89+
state.str.update(state.analysis.css.ast?.start, state.analysis.css.ast?.end, str.toString());
90+
}
91+
3792
/**
3893
* Does a best-effort migration of Svelte code towards using runes, event attributes and render tags.
3994
* May throw an error if the code is too complex to migrate automatically.
@@ -320,7 +375,10 @@ export function migrate(source, { filename, use_ts } = {}) {
320375
if (!parsed.instance && need_script) {
321376
str.appendRight(insertion_point, '\n</script>\n\n');
322377
}
323-
return { code: str.toString() };
378+
migrate_css(state);
379+
return {
380+
code: str.toString()
381+
};
324382
} catch (e) {
325383
if (!(e instanceof MigrationError)) {
326384
// eslint-disable-next-line no-console
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script>
2+
function is(){}
3+
function where(){}
4+
function not(){}
5+
function has(){}
6+
7+
// looks like css but it's not in style tag
8+
const x = {
9+
div:is(42),
10+
span:where(42),
11+
form:not(42),
12+
input:has(42),
13+
}
14+
</script>
15+
16+
what if i'm talking about `:has()` in my blog?
17+
18+
```css
19+
:has(.is_cool)
20+
```
21+
22+
<style lang="postcss">
23+
div:has(span){}
24+
div > :not(span){}
25+
div > :is(span){}
26+
div > :where(span){}
27+
28+
div:has(:is(span)){}
29+
div > :not(:is(span)){}
30+
div > :is(:is(span)){}
31+
div > :where(:is(span)){}
32+
33+
div:has(.class:is(span)){}
34+
div > :not(.class:is(span)){}
35+
div > :is(.class:is(span)){}
36+
div > :where(.class:is(span)){}
37+
38+
div :has(.class:is(span:where(:focus))){}
39+
div :not(.class:is(span:where(:focus-within))){}
40+
div :is(.class:is(span:is(:hover))){}
41+
div :where(.class:is(span:has(* > *))){}
42+
div :is(.class:is(span:is(:hover)), .x){}
43+
44+
div :has( :global(.class:is(span:where(:focus)))){}
45+
div :not(:global(.class:is(span:where(:focus-within)))){}
46+
div :is(:global(.class:is(span:is(:hover)))){}
47+
div :where(:global(.class:is(span:has(* > *)))){}
48+
div :is(:global(.class:is(span:is(:hover)), .x)){}
49+
50+
div{
51+
p:has(&){
52+
53+
}
54+
:not(span > *){
55+
:where(form){}
56+
}
57+
}
58+
</style>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script>
2+
function is(){}
3+
function where(){}
4+
function not(){}
5+
function has(){}
6+
7+
// looks like css but it's not in style tag
8+
const x = {
9+
div:is(42),
10+
span:where(42),
11+
form:not(42),
12+
input:has(42),
13+
}
14+
</script>
15+
16+
what if i'm talking about `:has()` in my blog?
17+
18+
```css
19+
:has(.is_cool)
20+
```
21+
22+
<style lang="postcss">
23+
div:has(:global(span)){}
24+
div > :not(:global(span)){}
25+
div > :is(:global(span)){}
26+
div > :where(:global(span)){}
27+
28+
div:has(:global(:is(span))){}
29+
div > :not(:global(:is(span))){}
30+
div > :is(:global(:is(span))){}
31+
div > :where(:global(:is(span))){}
32+
33+
div:has(:global(.class:is(span))){}
34+
div > :not(:global(.class:is(span))){}
35+
div > :is(:global(.class:is(span))){}
36+
div > :where(:global(.class:is(span))){}
37+
38+
div :has(:global(.class:is(span:where(:focus)))){}
39+
div :not(:global(.class:is(span:where(:focus-within)))){}
40+
div :is(:global(.class:is(span:is(:hover)))){}
41+
div :where(:global(.class:is(span:has(* > *)))){}
42+
div :is(:global(.class:is(span:is(:hover)), .x)){}
43+
44+
div :has( :global(.class:is(span:where(:focus)))){}
45+
div :not(:global(.class:is(span:where(:focus-within)))){}
46+
div :is(:global(.class:is(span:is(:hover)))){}
47+
div :where(:global(.class:is(span:has(* > *)))){}
48+
div :is(:global(.class:is(span:is(:hover)), .x)){}
49+
50+
div{
51+
p:has(:global(&)){
52+
53+
}
54+
:not(:global(span > *)){
55+
:where(:global(form)){}
56+
}
57+
}
58+
</style>

0 commit comments

Comments
 (0)