Merge branch 'main' into ensure-reactivity

pull/15792/head
Rich Harris 5 months ago
commit 4c9a776b4d

@ -274,6 +274,12 @@ A `:global` selector cannot modify an existing selector
A `:global` selector can only be modified if it is a descendant of other selectors A `:global` selector can only be modified if it is a descendant of other selectors
``` ```
### css_global_block_invalid_placement
```
A `:global` selector cannot be inside a pseudoclass
```
### css_global_invalid_placement ### css_global_invalid_placement
``` ```

@ -1,5 +1,15 @@
# svelte # svelte
## 5.28.1
### Patch Changes
- fix: ensure `<svelte:boundary>` properly removes error content in production mode ([#15793](https://github.com/sveltejs/svelte/pull/15793))
- fix: `update_version` after `delete` if `source` is `undefined` and `prop` in `target` ([#15796](https://github.com/sveltejs/svelte/pull/15796))
- fix: emit error on wrong placement of the `:global` block selector ([#15794](https://github.com/sveltejs/svelte/pull/15794))
## 5.28.0 ## 5.28.0
### Minor Changes ### Minor Changes

@ -50,6 +50,10 @@ x y {
> A `:global` selector can only be modified if it is a descendant of other selectors > A `:global` selector can only be modified if it is a descendant of other selectors
## css_global_block_invalid_placement
> A `:global` selector cannot be inside a pseudoclass
## css_global_invalid_placement ## css_global_invalid_placement
> `:global(...)` can be at the start or end of a selector sequence, but not in the middle > `:global(...)` can be at the start or end of a selector sequence, but not in the middle

@ -2,7 +2,7 @@
"name": "svelte", "name": "svelte",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"license": "MIT", "license": "MIT",
"version": "5.28.0", "version": "5.28.1",
"type": "module", "type": "module",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"engines": { "engines": {

@ -581,6 +581,15 @@ export function css_global_block_invalid_modifier_start(node) {
e(node, 'css_global_block_invalid_modifier_start', `A \`:global\` selector can only be modified if it is a descendant of other selectors\nhttps://svelte.dev/e/css_global_block_invalid_modifier_start`); e(node, 'css_global_block_invalid_modifier_start', `A \`:global\` selector can only be modified if it is a descendant of other selectors\nhttps://svelte.dev/e/css_global_block_invalid_modifier_start`);
} }
/**
* A `:global` selector cannot be inside a pseudoclass
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_global_block_invalid_placement(node) {
e(node, 'css_global_block_invalid_placement', `A \`:global\` selector cannot be inside a pseudoclass\nhttps://svelte.dev/e/css_global_block_invalid_placement`);
}
/** /**
* `:global(...)` can be at the start or end of a selector sequence, but not in the middle * `:global(...)` can be at the start or end of a selector sequence, but not in the middle
* @param {null | number | NodeLike} node * @param {null | number | NodeLike} node

@ -68,8 +68,12 @@ const css_visitors = {
const global = node.children.find(is_global); const global = node.children.find(is_global);
if (global) { if (global) {
const idx = node.children.indexOf(global); const is_nested = context.path.at(-2)?.type === 'PseudoClassSelector';
if (is_nested && !global.selectors[0].args) {
e.css_global_block_invalid_placement(global.selectors[0]);
}
const idx = node.children.indexOf(global);
if (global.selectors[0].args !== null && idx !== 0 && idx !== node.children.length - 1) { if (global.selectors[0].args !== null && idx !== 0 && idx !== node.children.length - 1) {
// ensure `:global(...)` is not used in the middle of a selector (but multiple `global(...)` in sequence are ok) // ensure `:global(...)` is not used in the middle of a selector (but multiple `global(...)` in sequence are ok)
for (let i = idx + 1; i < node.children.length; i++) { for (let i = idx + 1; i < node.children.length; i++) {

@ -100,6 +100,7 @@ export function proxy(value) {
prop, prop,
with_parent(() => source(UNINITIALIZED, stack)) with_parent(() => source(UNINITIALIZED, stack))
); );
update_version(version);
} }
} else { } else {
// When working with arrays, we need to also ensure we update the length when removing // When working with arrays, we need to also ensure we update the length when removing

@ -291,31 +291,21 @@ export function handle_error(error, effect, previous_effect, component_context)
is_throwing_error = true; is_throwing_error = true;
} }
if ( if (DEV && component_context !== null && error instanceof Error && !handled_errors.has(error)) {
!DEV || handled_errors.add(error);
component_context === null ||
!(error instanceof Error) ||
handled_errors.has(error)
) {
propagate_error(error, effect);
return;
}
handled_errors.add(error);
const component_stack = []; const component_stack = [];
const effect_name = effect.fn?.name; const effect_name = effect.fn?.name;
if (effect_name) { if (effect_name) {
component_stack.push(effect_name); component_stack.push(effect_name);
} }
/** @type {ComponentContext | null} */ /** @type {ComponentContext | null} */
let current_context = component_context; let current_context = component_context;
while (current_context !== null) { while (current_context !== null) {
if (DEV) {
/** @type {string} */ /** @type {string} */
var filename = current_context.function?.[FILENAME]; var filename = current_context.function?.[FILENAME];
@ -323,35 +313,36 @@ export function handle_error(error, effect, previous_effect, component_context)
const file = filename.split('/').pop(); const file = filename.split('/').pop();
component_stack.push(file); component_stack.push(file);
} }
current_context = current_context.p;
} }
current_context = current_context.p; const indent = is_firefox ? ' ' : '\t';
} define_property(error, 'message', {
value:
error.message + `\n${component_stack.map((name) => `\n${indent}in ${name}`).join('')}\n`
});
define_property(error, 'component_stack', {
value: component_stack
});
const indent = is_firefox ? ' ' : '\t'; const stack = error.stack;
define_property(error, 'message', {
value: error.message + `\n${component_stack.map((name) => `\n${indent}in ${name}`).join('')}\n` // Filter out internal files from callstack
}); if (stack) {
define_property(error, 'component_stack', { const lines = stack.split('\n');
value: component_stack const new_lines = [];
}); for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const stack = error.stack; if (line.includes('svelte/src/internal')) {
continue;
// Filter out internal files from callstack }
if (stack) { new_lines.push(line);
const lines = stack.split('\n');
const new_lines = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('svelte/src/internal')) {
continue;
} }
new_lines.push(line); define_property(error, 'stack', {
value: new_lines.join('\n')
});
} }
define_property(error, 'stack', {
value: new_lines.join('\n')
});
} }
propagate_error(error, effect); propagate_error(error, effect);

@ -4,5 +4,5 @@
* The current version, as set in package.json. * The current version, as set in package.json.
* @type {string} * @type {string}
*/ */
export const VERSION = '5.28.0'; export const VERSION = '5.28.1';
export const PUBLIC_VERSION = '5'; export const PUBLIC_VERSION = '5';

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_placement',
message: 'A `:global` selector cannot be inside a pseudoclass',
position: [28, 35]
}
});

@ -0,0 +1,4 @@
<style>
/* invalid */
:is(:global) { color: red }
</style>

@ -0,0 +1,13 @@
import { test } from '../../test';
import { flushSync } from 'svelte';
export default test({
html: `<button>delete</button><p>test</p>`,
async test({ assert, target }) {
const btn = target.querySelector('button');
flushSync(() => btn?.click());
assert.htmlEqual(target.innerHTML, '<button>delete</button>');
}
});

@ -0,0 +1,13 @@
<script>
let obj = $state({test: 0})
let keys = $derived(Object.keys(obj));
</script>
<button onclick={() => delete obj.test}>
delete
</button>
{#each keys as key}
<p>{key}</p>
{/each}

@ -0,0 +1,11 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['client'],
test({ assert, target }) {
flushSync();
assert.htmlEqual(target.innerHTML, '<p>error occurred</p>');
}
});

@ -0,0 +1,15 @@
<script>
import Child from "./Child.svelte"
</script>
<svelte:boundary>
<p>This should be removed</p>
{#if true}
<Child />
{/if}
{#snippet failed()}
<p>error occurred</p>
{/snippet}
</svelte:boundary>
Loading…
Cancel
Save