diff --git a/.changeset/hot-ladybugs-clap.md b/.changeset/hot-ladybugs-clap.md new file mode 100644 index 000000000000..20acdd075010 --- /dev/null +++ b/.changeset/hot-ladybugs-clap.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't wrap pseudo classes inside `:global(...)` with another `:global(...)` during migration diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index 080babdfb7c7..4942dd7db279 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -40,9 +40,10 @@ class MigrationError extends Error { */ function migrate_css(state) { if (!state.analysis.css.ast?.start) return; - let code = state.str + const css_contents = state.str .snip(state.analysis.css.ast.start, /** @type {number} */ (state.analysis.css.ast?.end)) .toString(); + let code = css_contents; 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 @@ -56,23 +57,28 @@ function migrate_css(state) { ) { 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; + } else { + const prev_global = css_contents.lastIndexOf(global_str, starting); + if (prev_global > -1) { + const end = + find_closing_parenthesis(css_contents.indexOf('(', prev_global) + 1, css_contents) - + starting; + if (end > start) { + starting += end; + code = code.substring(end); + continue; + } + } } - 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]; - } + + const end = find_closing_parenthesis(start, code); if (start && end) { if (!is_global && !code.startsWith(':not')) { str.prependLeft(starting + start, ':global('); @@ -89,6 +95,24 @@ function migrate_css(state) { state.str.update(state.analysis.css.ast?.start, state.analysis.css.ast?.end, str.toString()); } +/** + * @param {number} start + * @param {string} code + */ +function find_closing_parenthesis(start, code) { + 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]; + } + return end; +} + /** * 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. 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 index fbfc1c119fb8..88c54ad2db2e 100644 --- a/packages/svelte/tests/migrate/samples/is-not-where-has/input.svelte +++ b/packages/svelte/tests/migrate/samples/is-not-where-has/input.svelte @@ -47,6 +47,8 @@ what if i'm talking about `:has()` in my blog? div :where(:global(.class:is(span:has(* > *)))){} div :is(:global(.class:is(span:is(:hover)), .x)){} + :global(button:has(.is-active)){} + div{ p:has(&){ 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 index d8bb703ac53a..9f23cb10245e 100644 --- a/packages/svelte/tests/migrate/samples/is-not-where-has/output.svelte +++ b/packages/svelte/tests/migrate/samples/is-not-where-has/output.svelte @@ -47,6 +47,8 @@ what if i'm talking about `:has()` in my blog? div :where(:global(.class:is(span:has(* > *)))){} div :is(:global(.class:is(span:is(:hover)), .x)){} + :global(button:has(.is-active)){} + div{ p:has(:global(&)){