diff --git a/.changeset/sour-feet-carry.md b/.changeset/sour-feet-carry.md new file mode 100644 index 0000000000..6e09bac7f2 --- /dev/null +++ b/.changeset/sour-feet-carry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: wrap `:id`, `:where``:not` and `:has` with `:global` during migration diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index 5630b8b34c..ee5abc8853 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -34,6 +34,61 @@ class MigrationError extends Error { } } +/** + * + * @param {State} state + */ +function migrate_css(state) { + if (!state.analysis.css.ast?.start) return; + let code = state.str + .snip(state.analysis.css.ast.start, /** @type {number} */ (state.analysis.css.ast?.end)) + .toString(); + let starting = 0; + + // since we already blank css we can't work directly on `state.str` so we will create a copy that we can update + const str = new MagicString(code); + while (code) { + if ( + code.startsWith(':has') || + code.startsWith(':not') || + code.startsWith(':is') || + code.startsWith(':where') + ) { + let start = code.indexOf('(') + 1; + let is_global = false; + const global_str = ':global'; + const next_global = code.indexOf(global_str); + const str_between = code.substring(start, next_global); + if (!str_between.trim()) { + is_global = true; + start += global_str.length; + } + let parenthesis = 1; + let end = start; + let char = code[end]; + // find the closing parenthesis + while (parenthesis !== 0 && char) { + if (char === '(') parenthesis++; + if (char === ')') parenthesis--; + end++; + char = code[end]; + } + if (start && end) { + if (!is_global) { + str.prependLeft(starting + start, ':global('); + str.appendRight(starting + end - 1, ')'); + } + starting += end - 1; + code = code.substring(end - 1); + continue; + } + } + starting++; + code = code.substring(1); + } + state.str.update(state.analysis.css.ast?.start, state.analysis.css.ast?.end, str.toString()); +} + /** * Does a best-effort migration of Svelte code towards using runes, event attributes and render tags. * May throw an error if the code is too complex to migrate automatically. @@ -320,7 +375,10 @@ export function migrate(source, { filename, use_ts } = {}) { if (!parsed.instance && need_script) { str.appendRight(insertion_point, '\n\n\n'); } - return { code: str.toString() }; + migrate_css(state); + return { + code: str.toString() + }; } catch (e) { if (!(e instanceof MigrationError)) { // eslint-disable-next-line no-console diff --git a/packages/svelte/tests/migrate/samples/is-not-where-has/input.svelte b/packages/svelte/tests/migrate/samples/is-not-where-has/input.svelte new file mode 100644 index 0000000000..fbfc1c119f --- /dev/null +++ b/packages/svelte/tests/migrate/samples/is-not-where-has/input.svelte @@ -0,0 +1,58 @@ + + +what if i'm talking about `:has()` in my blog? + +```css + :has(.is_cool) +``` + + \ No newline at end of file diff --git a/packages/svelte/tests/migrate/samples/is-not-where-has/output.svelte b/packages/svelte/tests/migrate/samples/is-not-where-has/output.svelte new file mode 100644 index 0000000000..1e02e9bdfd --- /dev/null +++ b/packages/svelte/tests/migrate/samples/is-not-where-has/output.svelte @@ -0,0 +1,58 @@ + + +what if i'm talking about `:has()` in my blog? + +```css + :has(.is_cool) +``` + + \ No newline at end of file